diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..5487327b7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# top-most EditorConfig file +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.py] +indent_size = 4 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..63570efc3 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,27 @@ +module.exports = { + parser: "@babel/eslint-parser", + env: { + browser: true, + es6: true, + node: true, + }, + extends: ["eslint:recommended", "prettier"], + parserOptions: { + ecmaFeatures: { + experimentalObjectRestSpread: true, + jsx: true, + }, + ecmaVersion: 2021, + requireConfigFile: false, + sourceType: "module", + }, + rules: { + "no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + }, +} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..52b3760de --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,36 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + tests: + name: Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip wheel setuptools tox + - name: Run tox targets for ${{ matrix.python-version }} + run: | + ENV_PREFIX=$(tr -C -d "0-9" <<< "${{ matrix.python-version }}") + TOXENV=$(tox --listenvs | grep "^py$ENV_PREFIX" | tr '\n' ',') python -m tox diff --git a/.gitignore b/.gitignore index 39f2bcc37..c02765c74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,23 @@ *.pyc *~ .*.swp -\#*# -/secrets.py .DS_Store ._* +.coverage +.tox /FeinCMS.egg-info /MANIFEST /_build /build /dist +/docs/_build +/secrets.py +/tests/.coverage +/tests/.tox +/tests/htmlcov +\#*# +htmlcov +node_modules +test.zip +tests/test.zip +venv diff --git a/.mailmap b/.mailmap index 80149a59f..68466507e 100644 --- a/.mailmap +++ b/.mailmap @@ -3,6 +3,7 @@ Daniel Renz Simon Schürpf Simon Schürpf Martin J. Laubach +Martin J. Laubach Maarten van Gompel (proycon) Bojan Mihelac Antoni Aloy @@ -10,3 +11,6 @@ Nico Echaniz Bjorn Post Skylar Saveland Stephan Jaekel +Simon Bächler +Simon Bächler +Matthias Kestenholz diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..b4c083047 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,40 @@ +exclude: ".yarn/|yarn.lock|\\.min\\.(css|js)$" +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-added-large-files + - id: check-builtin-literals + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: check-toml + - id: check-yaml + - id: detect-private-key + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace + - repo: https://github.com/adamchainz/django-upgrade + rev: 1.28.0 + hooks: + - id: django-upgrade + args: [--target-version, "3.2"] + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.13.0" + hooks: + - id: ruff + args: [--unsafe-fixes] + - id: ruff-format + - repo: https://github.com/biomejs/pre-commit + rev: "v2.2.4" + hooks: + - id: biome-check + args: [--unsafe] + verbose: true + - repo: https://github.com/tox-dev/pyproject-fmt + rev: v2.6.0 + hooks: + - id: pyproject-fmt + - repo: https://github.com/abravalheri/validate-pyproject + rev: v0.24.1 + hooks: + - id: validate-pyproject diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..1c0ca47e9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +feincms/static/feincms/jquery-1.11.3.min.js +feincms/static/feincms/jquery-ui-1.10.3.custom.min.js +feincms/static/feincms/js.cookie.js diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..bcb0c684e --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,17 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py +# python: +# install: +# - requirements: docs/requirements.txt +# - method: pip +# path: . diff --git a/.tx/config b/.tx/config new file mode 100644 index 000000000..492a3add3 --- /dev/null +++ b/.tx/config @@ -0,0 +1,8 @@ +[main] +host = https://www.transifex.com +type = PO + +[feincms.djangopo] +file_filter = feincms/locale//LC_MESSAGES/django.po +source_file = feincms/locale/en/LC_MESSAGES/django.po +source_lang = en diff --git a/AUTHORS b/AUTHORS index 0ccb2a826..0d8f2f02a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,44 +5,110 @@ The authors of FeinCMS are: * Chris Adams * Simon Meers * Bojan Mihelac +* Simon Bächler * Bjorn Post * Stephan Jaekel * Julien Phalip * Daniel Renz -* Skylar Saveland -* Simon Schürpf +* Stefan Reinhard * Matt Dawson -* Maarten van Gompel (proycon) +* Simon Schürpf +* Skylar Saveland +* Peter Schmidt +* Marc Egli +* Psyton +* Simon Schmid +* Greg Turner +* Charlie Denton +* saboter * Greg Taylor +* Maarten van Gompel (proycon) * Bjarni Thorisson -* Simon Schmid -* Stefan Reinhard * Antoni Aloy +* Julian Bez +* Urs Breton +* Jonas +* Vítor Figueiró +* Fabian Germann +* Marc Tamlyn * Martin Mahner +* Max Peterson * Nico Echaniz +* Sander van Leeuwen +* Sebastian Walter * Toby White -* Vítor Figueiró -* Gabriel Kovacs +* Afonso Fernández Nogueira * Brian Macdonald -* Andrew D. Ball +* Eric Delord +* Emmanuelle Delescolle +* Maarten Draijer +* adsworth * Torkn -* Jonas +* Andrew D. Ball +* Gabriel Kovacs * Wil Tan +* Perry Roper +* Marco Fucci +* Denis Popov +* Fabian Vogler +* Cellarosi Marco +* Raphael Jasjukaitis +* Michael Bashkirov * Håvard Grimelid -* Simon Bächler +* Richard A +* Michael Kutý * Mikhail Korobov +* tayg * Maciek Szczesniak -* Darryl Woods -* Marc Egli -* George Karpenkov -* Harro van der Klauw -* Max Peterson -* Mikkel Hoegh -* Wouter van der Graaf -* Paul Garner +* Vaclav Klecanda +* valmynd +* Richard Bolt * Rico Moorman +* sperrygrove +* Saurabh Kumar * Sebastian Hillig -* Alen Mujezinovic -* Denis Martinez +* svleeuwen * Silvan Spross +* Artur Barseghyan +* Jay Yu +* Andrin Heusser +* Andrey Popelo +* Andi Albrecht +* Sumit Datta +* Sun Liwen * Tobias Haffner +* Alex Kamedov +* Valtron +* Wim Feijen +* Wouter van der Graaf +* antiflu +* feczo +* George Karpenkov +* Giorgos Logiotatidis +* Erik Stein +* Gwildor Sok +* Harro van der Klauw +* Anshuman Bhaduri +* Jimmy Ye +* Jonas Svensson +* Domas Lapinskas +* Kevin Etienne +* Laurent Paoletti +* Livio Lunin +* Denis Martinez +* i-trofimtschuk +* Marco Cellarosi +* Mark Renton +* David Evans +* ilmarsm +* Mason Hugus +* Darryl Woods +* Daniele Procida +* niklaushug +* Mikkel Hoegh +* Alen Mujezinovic +* Olekasnadr Gula +* Paul Garner +* Dan Büschlen +* Piet Delport +* Riccardo Coroneo diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 000000000..d72d9d44d --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,247 @@ +.. _changelog: + +Change log +========== + +Next version +~~~~~~~~~~~~ + +- Added a tinymce 7 integration for the richtext content type. + + +v24.8.1 (2024-08-07) +~~~~~~~~~~~~~~~~~~~~ + +- Fixed another edge case: JPEGs cannot be saved as RGBA. +- Added Django 5.1 to the CI. + + +v24.7.1 (2024-07-10) +~~~~~~~~~~~~~~~~~~~~ + +- Fixed the read the docs build. +- Disabled the CKEditor 4 version nag. + + +v24.4.2 (2024-04-18) +~~~~~~~~~~~~~~~~~~~~ + +- Fixed the filters to work with Django 5. + + +v24.4.1 (2024-04-16) +~~~~~~~~~~~~~~~~~~~~ + +- Forwarded cookies set by ``ApplicationContent`` apps to the final response. +- Added support for webp image formats to the media library. + + +v24.4.0 (2024-04-08) +~~~~~~~~~~~~~~~~~~~~ + +- Fetched the CSRF token value from the input field instead of from the cookie. + This allows making the CSRF cookie ``httponly``. Thanks to Samuel Lim for the + contribution! + + +v23.12.0 (2023-12-22) +~~~~~~~~~~~~~~~~~~~~~ + +- Added Python 3.12, Django 5.0. +- Closed images after reading their dimensions. Raised the logging level to + exception when thumbnailing fails. Thanks to Jeroen Pulles for those two + contributions! + + +`v23.8.0`_ (2023-08-07) +~~~~~~~~~~~~~~~~~~~~~~~ + +.. _v23.8.0: https://github.com/feincms/feincms/compare/v23.1.0...v23.8.0 + +- Made the filter argument of content base's ``get_queryset`` method optional. + This enables easier interoperability of FeinCMS content types with feincms3 + plugins. +- Added Python 3.11. +- Fixed the Pillow resampling constant. + + +`v23.1.0`_ (2023-03-09) +~~~~~~~~~~~~~~~~~~~~~~~ + +.. _v23.1.0: https://github.com/feincms/feincms/compare/v22.4.0...v23.1.0 + +- Fixed a place where ``ACTION_CHECKBOX_NAME`` was imported from the wrong + place. +- Dropped the ``is_dst`` argument to ``timezone.make_aware``. +- Added Django 4.1 and 4.2 to the CI matrix. + + +`v22.4.0`_ (2022-06-02) +~~~~~~~~~~~~~~~~~~~~~~~ + +.. _v22.4.0: https://github.com/feincms/feincms/compare/v22.3.0...v22.4.0 + +- Changed the ``template_key`` field type to avoid boring migrations because of + changing choices. + + +`v22.3.0`_ (2022-05-17) +~~~~~~~~~~~~~~~~~~~~~~~ + +.. _v22.3.0: https://github.com/feincms/feincms/compare/v22.2.0...v22.3.0 + +- The ``render()`` methods of bundled content types have been changed to return + a tuple instead of a HTML fragment in FeinCMS v22.0.0. This was backwards + incompatible in some scenarios. Those methods have been changed to return a + tuple subclass which automatically renders a HTML fragment if evaluated in a + string context. + + +`v22.2.0`_ (2022-05-06) +~~~~~~~~~~~~~~~~~~~~~~~ + +.. _v22.2.0: https://github.com/feincms/feincms/compare/v22.1.0...v22.2.0 + +- Dropped support for Python < 3.8. +- Fixed the thumbnailing support of the ``MediaFileForeignKey``. It has been + broken since Django switched to template-based widget rendering. + + +`v22.1.0`_ (2022-03-31) +~~~~~~~~~~~~~~~~~~~~~~~ + +.. _v22.1.0: https://github.com/feincms/feincms/compare/v22.0.0...v22.1.0 + +- Fixed the ``feincms_render_level`` render recursion protection. +- Wrapped the recursive saving of pages in a transaction, so if anything fails + we have a consistent state. +- Dropped more compatibility code for Django 1.x. +- Made ``medialibrary_orphans`` work again. +- Removed the ``six`` dependency since we're Python 3-only now. +- Updated the pre-commit hooks, cleaned up the JavaScript a bit. + + +`v22.0.0`_ (2022-01-07) +~~~~~~~~~~~~~~~~~~~~~~~ + +.. _v22.0.0: https://github.com/feincms/feincms/compare/v1.20.0...v22.0.0 + +- **Possibly backwards incompatible** Changed all bundled content types' + ``render()`` methods to return the ``(template_name, context)`` tuple instead + of rendering content themselves. +- Dropped compatibility guarantees with Python < 3.6, Django < 3.2. +- Added pre-commit. +- The default view was changed to accept the path as a ``path`` keyword + argument, not only as a positional argument. +- Changed the item editor action buttons CSS to not use transitions so that the + sprite buttons look as they should. + + +`v1.20.0`_ (2021-03-22) +~~~~~~~~~~~~~~~~~~~~~~~ + +- Changed ``#main`` to the more specific ``#feincmsmain`` so that it doesn't + collide with Django's admin panel markup. +- Stopped the JavaScript code from constructing invalid POST action URLs in the + change form. +- Renamed the main branch to main. +- Switched to a declarative setup. +- Switched to GitHub actions. +- Sorted imports. +- Reformated the JavaScript code using prettier. +- Added Python up to 3.9, Django up to the main branch (the upcoming 4.0) to + the CI list. + + +`v1.19.0`_ (2021-03-04) +~~~~~~~~~~~~~~~~~~~~~~~ + +- Fixed a bug where the thumbnailer would try to save JPEGs as RGBA. +- Reformatted the code using black, again. +- Added Python 3.8, Django 3.1 to the build. +- Added the Django 3.2 `.headers` property to the internal dummy response used + in the etag request processor. +- Added a workaround for ``AppConfig``-autodiscovery related crashes. (Because + ``feincms.apps`` now has more meanings). Changed the documentation to prefer + ``feincms.content.application.models.*`` to ``feincms.apps.*``. +- Updated the TinyMCE CDN URL to an version which doesn't show JavaScript + alerts. +- Added missing ``on_delete`` values to the django-filer content types. + + +`v1.18.0`_ (2020-01-21) +~~~~~~~~~~~~~~~~~~~~~~~ + +- Added a style checking job to the CI matrix. +- Dropped compatibility with Django 1.7. + + +`v1.17.0`_ (2019-11-21) +~~~~~~~~~~~~~~~~~~~~~~~ + +- Added compatibility with Django 3.0. + + +`v1.16.0`_ (2019-02-01) +~~~~~~~~~~~~~~~~~~~~~~~ + +- Reformatted everything using black. +- Added a fallback import for the ``staticfiles`` template tag library + which will be gone in Django 3.0. + + +`v1.15.0`_ (2018-12-21) +~~~~~~~~~~~~~~~~~~~~~~~ + +- Actually made use of the timeout specified as + ``FEINCMS_THUMBNAIL_CACHE_TIMEOUT`` instead of the hardcoded value of + seven days. +- Reverted the deprecation of navigation extension autodiscovery. +- Fixed the item editor JavaScript and HTML to work with Django 2.1's + updated inlines. +- Fixed ``TranslatedObjectManager.only_language`` to evaluate callables + before filtering. +- Changed the ``render`` protocol of content types to allow returning a + tuple of ``(ct_template, ct_context)`` which works the same way as + `feincms3's template renderers + `__. + + +`v1.14.0`_ (2018-08-16) +~~~~~~~~~~~~~~~~~~~~~~~ + +- Added a central changelog instead of creating release notes per + release because development is moving more slowly owing to the stable + nature of FeinCMS. +- Fixed history (revision) form, recover form and breadcrumbs when + FeinCMS is used with Reversion 2.0.x. This accommodates refactoring + that took place in `Reversion 1.9 and 2.0 + `_. + If you are upgrading Reversion (rather than starting a new project), + please be aware of the significant interface changes and database + migrations in that product, and attempt upgrading in a development + environment before upgrading a live site. +- Added ``install_requires`` back to ``setup.py`` so that dependencies + are installed automatically again. Note that some combinations of e.g. + Django and django-mptt are incompatible -- look at the `Travis CI + build configuration + `_ to find + out about supported combinations. +- Fixed a few minor compatibility and performance problems. +- Added a new ``FEINCMS_THUMBNAIL_CACHE_TIMEOUT`` setting which allows + caching whether a thumb exists instead of calling ``storage.exists()`` + over and over (which might be slow with remote storages). +- Fixed random reordering of applications by using an ordered dictionary + for apps. +- Increased the length of the caption field for media file translations. +- Fixed ``feincms.contrib.tagging`` to actually work with Django + versions after 1.9.x. + + +.. _v1.14.0: https://github.com/feincms/feincms/compare/v1.13.0...v1.14.0 +.. _v1.15.0: https://github.com/feincms/feincms/compare/v1.14.0...v1.15.0 +.. _v1.16.0: https://github.com/feincms/feincms/compare/v1.15.0...v1.16.0 +.. _v1.17.0: https://github.com/feincms/feincms/compare/v1.16.0...v1.17.0 +.. _v1.18.0: https://github.com/feincms/feincms/compare/v1.17.0...v1.18.0 +.. _v1.19.0: https://github.com/feincms/feincms/compare/v1.18.0...v1.19.0 +.. _v1.20.0: https://github.com/feincms/feincms/compare/v1.19.0...v1.20.0 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 000000000..ddf1d89ba --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,19 @@ +======================= +Contributing to FeinCMS +======================= + +Submission guidelines +===================== + +If you are creating a pull request, fork the repository and make any changes +in your own feature branch. + +Write and run tests. + + +Code of Conduct +=============== + +This project adheres to the +`Open Code of Conduct `_. +By participating, you are expected to honor this code. diff --git a/LICENSE b/LICENSE index 4268a6f6f..87d53481d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009-2010, FEINHEIT GmbH and individual contributors. +Copyright (c) 2009-2014, FEINHEIT GmbH and individual contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/MANIFEST.in b/MANIFEST.in index 25d01403e..b8a1e6a53 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,17 +1,8 @@ include AUTHORS +include CONTRIBUTING.rst include LICENSE +include MANIFEST.in include README.rst -recursive-include feincms/media *.png -recursive-include feincms/media *.jpg -recursive-include feincms/media *.gif -recursive-include feincms/media *.js -recursive-include feincms/media *.css -recursive-include feincms/static *.png -recursive-include feincms/static *.jpg -recursive-include feincms/static *.gif -recursive-include feincms/static *.js -recursive-include feincms/static *.css -recursive-include feincms/locale *.po -recursive-include feincms/locale *.mo -recursive-include feincms/templates *.html -recursive-include feincms/templates *.txt +recursive-include feincms/static * +recursive-include feincms/locale * +recursive-include feincms/templates * diff --git a/README.rst b/README.rst index 03016ef3a..cb0ebc95e 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,12 @@ +**NOTE! If you're starting a new project you may want to take a look at feincms3 (https://feincms3.readthedocs.io/). FeinCMS is still maintained and works well, but feincms3 is where current development is happening.** + ======================================== FeinCMS - An extensible Django-based CMS ======================================== +.. image:: https://github.com/feincms/feincms/workflows/Tests/badge.svg + :target: https://github.com/feincms/feincms + When was the last time, that a pre-built software package you wanted to use got many things right, but in the end, you still needed to modify the core parts of the code just because it wasn't (easily) possible to @@ -39,27 +44,27 @@ else which I haven't thought of yet). It provides helper functions, which provide ordered lists of page content blocks. That's all. -Adding your own content types is extremely easy. Do you like textile +Adding your own content types is extremely easy. Do you like markdown that much, that you'd rather die than using a rich text editor? Then add the following code to your project, and you can go on using the CMS without being forced to use whatever the developers deemed best: -:: +.. code-block:: python + from markdown2 import markdown from feincms.module.page.models import Page - from django.contrib.markup.templatetags.markup import textile from django.db import models - class TextilePageContent(models.Model): + class MarkdownPageContent(models.Model): content = models.TextField() class Meta: abstract = True def render(self, **kwargs): - return textile(self.content) + return markdown(self.content) - Page.create_content_type(TextilePageContent) + Page.create_content_type(MarkdownPageContent) That's it. Not even ten code lines for your own page content type. @@ -72,26 +77,11 @@ Getting started Visit these sites ----------------- -* FeinCMS Website: http://www.feinheit.ch/labs/feincms-django-cms/ -* Read the documentation: http://www.feinheit.ch/media/labs/feincms/ -* See the google groups page at http://groups.google.com/group/django-feincms +* FeinCMS Website: http://www.feincms.org/ +* Read the documentation: https://feincms-django-cms.readthedocs.io/ +* See the Google Groups page at http://groups.google.com/group/django-feincms * FeinCMS on github: https://github.com/feincms/feincms/ -Optional Packages ------------------ - -* `pytidylib `_ can be - installed to perform HTML validation and cleanup using `HTML Tidy - `_ while editing content. Install pytidylib and - add ``FEINCMS_TIDY_HTML = True`` to your settings.py. - -* Alternately, `lxml `_ can be installed to perform - silent HTML cleanup to remove non-standard markup while editing content. - Install lxml and add ``cleanse=True`` when you register ``RichTextContent`` - or ``SectionContent``:: - - RichTextContentType = Page.create_content_type(RichTextContent, cleanse=True) - Repository branches ------------------- @@ -99,9 +89,9 @@ The FeinCMS repository on github has several branches. Their purpose and rewinding policies are described below. * ``maint``: Maintenance branch for the second-newest version of FeinCMS. -* ``master``: Stable version of FeinCMS. +* ``main``: Stable version of FeinCMS. -``master`` and ``maint`` are never rebased or rewound. +``main`` and ``maint`` are never rebased or rewound. * ``next``: Upcoming version of FeinCMS. This branch is rarely rebased if ever, but this might happen. A note will be sent to the official diff --git a/biome.json b/biome.json new file mode 100644 index 000000000..915669572 --- /dev/null +++ b/biome.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.2.3/schema.json", + "assist": { "actions": { "source": { "organizeImports": "off" } } }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "a11y": { + "noSvgWithoutTitle": "off" + }, + "complexity": { + "noImportantStyles": "off" + }, + "correctness": { + "noInnerDeclarations": "off", + "noUndeclaredVariables": "warn", + "noUnknownTypeSelector": "warn", + "noUnusedImports": "error", + "noUnusedVariables": "error", + "useHookAtTopLevel": "error" + }, + "security": { + "noDangerouslySetInnerHtml": "warn" + }, + "style": { + "noDescendingSpecificity": "warn", + "noParameterAssign": "off", + "useForOf": "warn", + "useArrayLiterals": "error" + }, + "suspicious": { + "noArrayIndexKey": "warn", + "noAssignInExpressions": "off" + } + } + }, + "javascript": { + "formatter": { + "semicolons": "asNeeded" + }, + "globals": ["django", "CKEDITOR"] + }, + "css": { + "formatter": { + "enabled": true + }, + "linter": { + "enabled": true + } + }, + "json": { + "formatter": { + "enabled": false + } + } +} diff --git a/docs/_theme/nature/static/nature.css_t b/docs/_theme/nature/static/nature.css_t deleted file mode 100644 index 03b0379d0..000000000 --- a/docs/_theme/nature/static/nature.css_t +++ /dev/null @@ -1,229 +0,0 @@ -/** - * Sphinx stylesheet -- default theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: Arial, sans-serif; - font-size: 100%; - background-color: #111; - color: #555; - margin: 0; - padding: 0; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 230px; -} - -hr{ - border: 1px solid #B1B4B6; -} - -div.document { - background-color: #eee; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 30px 30px; - font-size: 0.8em; -} - -div.footer { - color: #555; - width: 100%; - padding: 13px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #444; - text-decoration: underline; -} - -div.related { - background-color: #6BA81E; - line-height: 32px; - color: #fff; - text-shadow: 0px 1px 0 #444; - font-size: 0.80em; -} - -div.related a { - color: #E2F3CC; -} - -div.sphinxsidebar { - font-size: 0.75em; - line-height: 1.5em; -} - -div.sphinxsidebarwrapper{ - padding: 20px 0; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: Arial, sans-serif; - color: #222; - font-size: 1.2em; - font-weight: normal; - margin: 0; - padding: 5px 10px; - background-color: #ddd; - text-shadow: 1px 1px 0 white -} - -div.sphinxsidebar h4{ - font-size: 1.1em; -} - -div.sphinxsidebar h3 a { - color: #444; -} - - -div.sphinxsidebar p { - color: #888; - padding: 5px 20px; -} - -div.sphinxsidebar p.topless { -} - -div.sphinxsidebar ul { - margin: 10px 20px; - padding: 0; - color: #000; -} - -div.sphinxsidebar a { - color: #444; -} - -div.sphinxsidebar input { - border: 1px solid #ccc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar input[type=text]{ - margin-left: 20px; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #005B81; - text-decoration: none; -} - -a:hover { - color: #E32E00; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: Arial, sans-serif; - background-color: #BED4EB; - font-weight: normal; - color: #212224; - margin: 30px 0px 10px 0px; - padding: 5px 0 5px 10px; - text-shadow: 0px 1px 0 white -} - -div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 150%; background-color: #C8D5E3; } -div.body h3 { font-size: 120%; background-color: #D8DEE3; } -div.body h4 { font-size: 110%; background-color: #D8DEE3; } -div.body h5 { font-size: 100%; background-color: #D8DEE3; } -div.body h6 { font-size: 100%; background-color: #D8DEE3; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - line-height: 1.5em; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.highlight{ - background-color: white; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 10px; - background-color: White; - color: #222; - line-height: 1.2em; - border: 1px solid #C6C9CB; - font-size: 1.2em; - margin: 1.5em 0 1.5em 0; - -webkit-box-shadow: 1px 1px 1px #d8d8d8; - -moz-box-shadow: 1px 1px 1px #d8d8d8; -} - -tt { - background-color: #ecf0f3; - color: #222; - padding: 1px 2px; - font-size: 1.2em; - font-family: monospace; -} diff --git a/docs/_theme/nature/static/pygments.css b/docs/_theme/nature/static/pygments.css deleted file mode 100644 index 652b76128..000000000 --- a/docs/_theme/nature/static/pygments.css +++ /dev/null @@ -1,54 +0,0 @@ -.c { color: #999988; font-style: italic } /* Comment */ -.k { font-weight: bold } /* Keyword */ -.o { font-weight: bold } /* Operator */ -.cm { color: #999988; font-style: italic } /* Comment.Multiline */ -.cp { color: #999999; font-weight: bold } /* Comment.preproc */ -.c1 { color: #999988; font-style: italic } /* Comment.Single */ -.gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ -.ge { font-style: italic } /* Generic.Emph */ -.gr { color: #aa0000 } /* Generic.Error */ -.gh { color: #999999 } /* Generic.Heading */ -.gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ -.go { color: #111 } /* Generic.Output */ -.gp { color: #555555 } /* Generic.Prompt */ -.gs { font-weight: bold } /* Generic.Strong */ -.gu { color: #aaaaaa } /* Generic.Subheading */ -.gt { color: #aa0000 } /* Generic.Traceback */ -.kc { font-weight: bold } /* Keyword.Constant */ -.kd { font-weight: bold } /* Keyword.Declaration */ -.kp { font-weight: bold } /* Keyword.Pseudo */ -.kr { font-weight: bold } /* Keyword.Reserved */ -.kt { color: #445588; font-weight: bold } /* Keyword.Type */ -.m { color: #009999 } /* Literal.Number */ -.s { color: #bb8844 } /* Literal.String */ -.na { color: #008080 } /* Name.Attribute */ -.nb { color: #999999 } /* Name.Builtin */ -.nc { color: #445588; font-weight: bold } /* Name.Class */ -.no { color: #ff99ff } /* Name.Constant */ -.ni { color: #800080 } /* Name.Entity */ -.ne { color: #990000; font-weight: bold } /* Name.Exception */ -.nf { color: #990000; font-weight: bold } /* Name.Function */ -.nn { color: #555555 } /* Name.Namespace */ -.nt { color: #000080 } /* Name.Tag */ -.nv { color: purple } /* Name.Variable */ -.ow { font-weight: bold } /* Operator.Word */ -.mf { color: #009999 } /* Literal.Number.Float */ -.mh { color: #009999 } /* Literal.Number.Hex */ -.mi { color: #009999 } /* Literal.Number.Integer */ -.mo { color: #009999 } /* Literal.Number.Oct */ -.sb { color: #bb8844 } /* Literal.String.Backtick */ -.sc { color: #bb8844 } /* Literal.String.Char */ -.sd { color: #bb8844 } /* Literal.String.Doc */ -.s2 { color: #bb8844 } /* Literal.String.Double */ -.se { color: #bb8844 } /* Literal.String.Escape */ -.sh { color: #bb8844 } /* Literal.String.Heredoc */ -.si { color: #bb8844 } /* Literal.String.Interpol */ -.sx { color: #bb8844 } /* Literal.String.Other */ -.sr { color: #808000 } /* Literal.String.Regex */ -.s1 { color: #bb8844 } /* Literal.String.Single */ -.ss { color: #bb8844 } /* Literal.String.Symbol */ -.bp { color: #999999 } /* Name.Builtin.Pseudo */ -.vc { color: #ff99ff } /* Name.Variable.Class */ -.vg { color: #ff99ff } /* Name.Variable.Global */ -.vi { color: #ff99ff } /* Name.Variable.Instance */ -.il { color: #009999 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_theme/nature/theme.conf b/docs/_theme/nature/theme.conf deleted file mode 100644 index 1cc400446..000000000 --- a/docs/_theme/nature/theme.conf +++ /dev/null @@ -1,4 +0,0 @@ -[theme] -inherit = basic -stylesheet = nature.css -pygments_style = tango diff --git a/docs/admin.rst b/docs/admin.rst index 2785d7bd5..537187750 100644 --- a/docs/admin.rst +++ b/docs/admin.rst @@ -24,21 +24,24 @@ for this to work. Usage is as follows:: from django.db import models + from mptt.fields import TreeForeignKey + from mptt.models import MPTTModel - class YourModel(models.Model): + class YourModel(MPTTModel): # model field definitions - class Meta: - ordering = ['tree_id', 'lft'] # The TreeEditor needs this ordering definition + parent = TreeForeignKey('self', null=True, blank=True, related_name='children') + class Meta: + ordering = ['tree_id', 'lft'] # The TreeEditor needs this ordering definition And inside your ``admin.py`` file:: from django.contrib import admin - from feincms.admin import editor + from feincms.admin import tree_editor from yourapp.models import YourModel - class YourModelAdmin(editor.TreeEditor): + class YourModelAdmin(tree_editor.TreeEditor): pass admin.site.register(YourModel, YourModelAdmin) @@ -48,7 +51,7 @@ All standard :class:`~django.contrib.admin.options.ModelAdmin` attributes such a :attr:`ModelAdmin.list_display`, :attr:`ModelAdmin.list_editable`, :attr:`ModelAdmin.list_filter` work as normally. The only exception to this rule is the column showing the tree structure (the second column in the image). -There, we always show the value of :attr:`Model.__unicode__` currently. +There, we always show the value of :attr:`Model.__str__` currently. AJAX checkboxes @@ -68,7 +71,7 @@ the page editor however. Usage:: from django.contrib import admin - from feincms.admin import editor + from feincms.admin import tree_editor import mptt class Category(models.Model): @@ -79,9 +82,9 @@ Usage:: # ... mptt.register(Category) - class CategoryAdmin(editor.TreeEditor): - list_display = ('__unicode__', 'active_toggle') - active_toggle = editor.ajax_editable_boolean('active', _('active')) + class CategoryAdmin(tree_editor.TreeEditor): + list_display = ('__str__', 'active_toggle') + active_toggle = tree_editor.ajax_editable_boolean('active', _('active')) @@ -95,7 +98,7 @@ The tabbed interface below is used to edit content and other properties of the edited object. A tab is shown for every region of the template or element, depending on whether templates are activated for the object in question [#f1]_. -Here's an screenshot of a content editing pane. The media file content is +Here's a screenshot of a content editing pane. The media file content is collapsed currently. New items can be added using the control bar at the bottom, and all content blocks can be reordered using drag and drop: @@ -147,11 +150,13 @@ settings on the content type model itself: * ``feincms_item_editor_form``: You can specify the base class which should be used for the content type - model. The default value is :class:`django.forms.ModelForm`. + model. The default value is :class:`django.forms.ModelForm`. If you want + to customize the form, chances are it is a better idea to set + ``feincms_item_editor_inline`` instead. * ``feincms_item_editor_includes``: - If you need additional Javascript or CSS files or need to perform additional + If you need additional JavaScript or CSS files or need to perform additional initialization on your content type forms, you can specify template fragments which are included in predefined places into the item editor. @@ -176,15 +181,15 @@ settings on the content type model itself: editors such as TinyMCE react badly to being dragged around - they are still visible, but the content disappears and nothing is clickable anymore. Because of this you might want to run routines before and after moving content types - around. This is achieved by adding your javascript functions to + around. This is achieved by adding your JavaScript functions to the ``contentblock_move_handlers.poorify`` array for handlers to be executed before moving and ``contentblock_move_handlers.richify`` for handlers to be executed after moving. Please note that the item editor executes all handlers on every drag and drop, it is your responsibility to ensure that code is only executed if it has to. - Take a look at the ``mediafile`` and ``richtext`` item editor include files - to understand how this should be done. + Take a look at the ``richtext`` item editor include files to understand how + this should be done. * ``feincms_item_editor_inline``: @@ -194,12 +199,17 @@ settings on the content type model itself: content type. The custom inline should inherit from ``FeinCMSInline`` or be configured the same way. + If you override ``fieldsets`` or ``fields`` you **must** include ``region`` and + ``ordering`` even though they aren't shown in the administration + interface. + Putting it all together ======================= -It is possible to build a limited, but fully functional page CMS using not -more than the following code: +It is possible to build a limited, but fully functional page CMS administration +interface using only the following code (``urls.py`` and ``views.py`` are +missing): ``models.py``:: @@ -222,15 +232,15 @@ more than the following code: ``admin.py``:: from django.contrib import admin - from feincms.admin import editor + from feincms.admin import item_editor, tree_editor from myapp.models import Page - class PageAdmin(editor.ItemEditor, editor.TreeEditor): + class PageAdmin(item_editor.ItemEditor, tree_editor.TreeEditor): fieldsets = [ (None, { 'fields': ['active', 'title', 'slug'], }), - editor.FEINCMS_CONTENT_FIELDSET, + item_editor.FEINCMS_CONTENT_FIELDSET, ] list_display = ['active', 'title'] prepopulated_fields = {'slug': ('title',)} diff --git a/docs/advanced/base.rst b/docs/advanced/base.rst index 7658da38a..3b50bca08 100644 --- a/docs/advanced/base.rst +++ b/docs/advanced/base.rst @@ -14,6 +14,9 @@ manage content with the :class:`~feincms.admin.item_editor.ItemEditor`. .. attribute:: Base.content + Beware not to name subclass field `content` as this will overshadow `ContentProxy` and you will + not be able to reference `ContentProxy`. + .. method:: Base.create_content_type(model, regions=None, [**kwargs]) .. method:: Base.content_type_for(model) @@ -23,5 +26,3 @@ manage content with the :class:`~feincms.admin.item_editor.ItemEditor`. .. method:: Base.replace_content_with(obj) .. method:: Base.append_content_from(obj) - - diff --git a/docs/advanced/caching.rst b/docs/advanced/caching.rst index 80229319a..cf4f2b6cd 100644 --- a/docs/advanced/caching.rst +++ b/docs/advanced/caching.rst @@ -31,20 +31,20 @@ of helper methods and variables, ready to be used in your templates. Here's an (incomplete) list of variables to use in {% cache %} blocks [#djangocache]_: - * feincms_page.cache_key -- a string describing the current page. + * feincms_page.cache_key -- DEPRECATED as of FeinCMS 1.11. + A string describing the current page. Depending on the extensions loaded, this varies with the page, the page's modification date, its language, etc. This is always a safe bet to use on page specific fragments. - + * LANGUAGE_CODE -- even if two requests are asking for the same page, the html code rendered might differ in translated elements in the navigation or elsewhere. If the fragment varies on language, include LANGUAGE_CODE in the cache specifier. - + * request.user.id -- different users might be allowed to see different views of the site. Add request.user.id to the cache specifier if this is the case. -.. [#djangocache] Please see the django documentation for detailed +.. [#djangocache] Please see the django documentation for detailed description of the {% cache %} template tag. - diff --git a/docs/advanced/designdecisions.rst b/docs/advanced/designdecisions.rst index 272eb58af..420410989 100644 --- a/docs/advanced/designdecisions.rst +++ b/docs/advanced/designdecisions.rst @@ -14,21 +14,21 @@ struggling with rich text editors for a long time. To be honest, I do not think it was a good idea to add that many features to the rich text editor. Resizing images uploaded into a rich text editor is a real pain, and what if you'd like to reuse these images or display -them using a lightbox script or something similar? You have to ressort -to writing loads of javascript code which will only work on one +them using a lightbox script or something similar? You have to resort +to writing loads of JavaScript code which will only work on one browser. You cannot really filter the HTML code generated by the user to kick out ugly HTML code generated by copy-pasting from word. The -user will upload 10mb JPEGs and resize them to 50x50 pixels by -himself. +user will upload 10mb JPEGs and resize them to 50x50 pixels in the rich +text editor. All of this convinced me that offering the user a rich text editor -with too much capabilites is a really bad idea. The rich text editor +with too much capabilities is a really bad idea. The rich text editor in FeinCMS only has bold, italic, bullets, link and headlines activated (and the HTML code button, because that's sort of inevitable -- sometimes the rich text editor messes up and you cannot fix it other than going directly into the HTML code. Plus, if someone really -knows what he's doing, I'd still like to give him the power to shot -his own foot). +knows what he's doing, I'd still like to give them the power to shot +their own foot). If this does not seem convincing you can always add your own rich text content type with a different configuration (or just override the rich @@ -47,12 +47,12 @@ for images or something...). A page's content could look like this: * Rich Text * Floated image * Rich Text -* Youtube Video Link, embedding code is automatically generated from the link +* YouTube Video Link, embedding code is automatically generated from the link * Rich Text It's of course easier for the user to start with only a single rich text field, but I think that the user already has too much confusing -possibilites with an enhanced rich text editor. Once the user grasps +possibilities with an enhanced rich text editor. Once the user grasps the concept of content blocks which can be freely added, removed and reordered using drag/drop, I'd say it's much easier to administer the content of a webpage. Plus, the content blocks can have their own diff --git a/docs/advanced/index.rst b/docs/advanced/index.rst index 07a47972e..2134545dd 100644 --- a/docs/advanced/index.rst +++ b/docs/advanced/index.rst @@ -15,6 +15,5 @@ design considerations and how to optimize your code, this section is for you. base utils - prefilledattributes designdecisions caching diff --git a/docs/advanced/prefilledattributes.rst b/docs/advanced/prefilledattributes.rst deleted file mode 100644 index f1855d404..000000000 --- a/docs/advanced/prefilledattributes.rst +++ /dev/null @@ -1,50 +0,0 @@ -.. _tools-utils-prefilledattributes: - -Prefetching data for a list of objects from the database -======================================================== - -If you want to display several CMS objects on one site, it can happen quickly -that the number of SQL queries needed to generate the page goes up to intolerable -heights. The same queries are executed over and over for every object in the list. -This section describes how the bundled tools can be used to reduce the amount of -SQL queries needed. - -The two functions ``prefilled_attribute`` and ``prefill_entry_list`` help you avoid -massive amounts of database queries when displaying a list of CMS items with -content objects. This is especially useful if f.e. your blog content is derived -from FeinCMS and you want to show a list of recent blog entries. - -:: - - from feincms.content.image.models import ImageContent - from feincms.content.richtext.models import RichTextContent - from feincms.models import Base - from feincms.utils import prefilled_attribute, prefill_entry_list - - class Author(models.Model): - # ... - - class Entry(Base): - authors = models.ManyToManyField - - author_list = prefilled_attr('authors') - richtexts = prefilled_attr('richtextcontent_set') - images = prefilled_attr('imagecontent_set') - - Entry.create_content_type(RichTextContent) - Entry.create_content_type(ImageContent) - -Then, inside your view function or inside a template tag, call -``prefill_entry_list`` with the attribute names:: - - prefill_entry_list(queryset, 'authors', 'richtextcontent_set', 'imagecontent_set') - - -If you want to use the prefilled attributes mechanism with generic views, you -cannot use the ``prefill_entry_list`` function when passing the queryset to the -generic view, because the method needs to evaluate the queryset. The earliest -point where you have access to your queryset again is inside the template. The -``feincms_prefill_entry_list`` template tag can be used for the same purpose:: - - {% load feincms_tags %} - {% feincms_prefill_entry_list object_list "authors,richtextcontent_set,imagecontent_set" %} diff --git a/docs/advanced/utils.rst b/docs/advanced/utils.rst index ee45edc8b..6edf49d60 100644 --- a/docs/advanced/utils.rst +++ b/docs/advanced/utils.rst @@ -15,8 +15,3 @@ MyClass = get_object('module.MyClass') myfunc = get_object('anothermodule.module2.my_function', fail_silently=True) - -.. function:: collect_dict_values(data) - - Converts a list of 2-tuples to a dict. - diff --git a/docs/api/admin.rst b/docs/api/admin.rst deleted file mode 100644 index 9843d278a..000000000 --- a/docs/api/admin.rst +++ /dev/null @@ -1,25 +0,0 @@ -Admin classes -============= - -ItemEditor ----------- - -.. automodule:: feincms.admin.item_editor - :members: - :noindex: - - -TreeEditor ----------- - -.. automodule:: feincms.admin.tree_editor - :members: - :noindex: - - -FilterSpec classes for ``list_filter`` customization ----------------------------------------------------- - -.. automodule:: feincms.admin.filterspecs - :members: - :noindex: diff --git a/docs/api/blog.rst b/docs/api/blog.rst deleted file mode 100644 index cd6f97cd3..000000000 --- a/docs/api/blog.rst +++ /dev/null @@ -1,26 +0,0 @@ -Blog module -=========== - - -.. automodule:: feincms.module.blog.models - :members: - :noindex: - - -Extensions ----------- - -Tagging -******* - -.. automodule:: feincms.module.blog.extensions.tags - :members: - :noindex: - - -Blog entry translations -*********************** - -.. automodule:: feincms.module.blog.extensions.translations - :members: - :noindex: diff --git a/docs/api/commands.rst b/docs/api/commands.rst deleted file mode 100644 index 671daf7e4..000000000 --- a/docs/api/commands.rst +++ /dev/null @@ -1,41 +0,0 @@ -Management commands -=================== - -Database schema checker ------------------------ - -.. automodule:: feincms.management.checker - :members: - :noindex: - - -Content-type specific management commands ------------------------------------------ - -.. automodule:: feincms.management.commands.update_rsscontent - :members: - :noindex: - - -Page tree rebuilders --------------------- - -Those should not normally be used. Older versions of MPTT sometimes -got confused with repeated saves and tree-structure changes. These -management commands helped cleaning up the mess. - -.. automodule:: feincms.management.commands.rebuild_mptt - :members: - :noindex: - -.. automodule:: feincms.management.commands.rebuild_mptt_direct - :members: - :noindex: - - -Miscellaneous commands ----------------------- - -.. automodule:: feincms.management.commands.feincms_validate - :members: - :noindex: diff --git a/docs/api/contenttypes.rst b/docs/api/contenttypes.rst deleted file mode 100644 index 985d92c22..000000000 --- a/docs/api/contenttypes.rst +++ /dev/null @@ -1,94 +0,0 @@ -Content types -============= - -ApplicationContent ------------------- - -.. automodule:: feincms.content.application.models - :members: - :noindex: - -CommentsContent ---------------- - -.. automodule:: feincms.content.comments.models - :members: - :noindex: - -ContactFormContent ------------------- - -.. automodule:: feincms.content.contactform.models - :members: - :noindex: - -FileContent ------------ - -.. automodule:: feincms.content.file.models - :members: - :noindex: - -ImageContent ------------- - -.. automodule:: feincms.content.image.models - :members: - :noindex: - -MediaFileContent ----------------- - -.. automodule:: feincms.content.medialibrary.models - :members: - :noindex: - -RawContent ----------- - -.. automodule:: feincms.content.raw.models - :members: - :noindex: - -RichTextContent ---------------- - -.. automodule:: feincms.content.richtext.models - :members: - :noindex: - -RSSContent ----------- - -.. automodule:: feincms.content.rss.models - :members: - :noindex: - -SectionContent --------------- - -.. automodule:: feincms.content.section.models - :members: - :noindex: - -TableContent ------------- - -.. automodule:: feincms.content.table.models - :members: - :noindex: - -TemplateContent ---------------- - -.. automodule:: feincms.content.template.models - :members: - :noindex: - -VideoContent ------------- - -.. automodule:: feincms.content.video.models - :members: - :noindex: - diff --git a/docs/api/contextprocessors.rst b/docs/api/contextprocessors.rst deleted file mode 100644 index ec08b0211..000000000 --- a/docs/api/contextprocessors.rst +++ /dev/null @@ -1,6 +0,0 @@ -Context processors -================== - -.. automodule:: feincms.context_processors - :members: - :noindex: diff --git a/docs/api/contrib.rst b/docs/api/contrib.rst deleted file mode 100644 index 100331a64..000000000 --- a/docs/api/contrib.rst +++ /dev/null @@ -1,18 +0,0 @@ -Contrib -======= - - -Model and form fields ---------------------- - -.. automodule:: feincms.contrib.fields - :members: - :noindex: - - -Tagging -------- - -.. automodule:: feincms.contrib.tagging - :members: - :noindex: diff --git a/docs/api/core.rst b/docs/api/core.rst deleted file mode 100644 index cbc5e4e53..000000000 --- a/docs/api/core.rst +++ /dev/null @@ -1,18 +0,0 @@ -FeinCMS core -============ - - -General functions ------------------ - -.. automodule:: feincms - :members: - :noindex: - - -Base models ------------ - -.. automodule:: feincms.models - :members: - :noindex: diff --git a/docs/api/medialibrary.rst b/docs/api/medialibrary.rst deleted file mode 100644 index 841c7674a..000000000 --- a/docs/api/medialibrary.rst +++ /dev/null @@ -1,6 +0,0 @@ -Media library -============= - -.. automodule:: feincms.module.medialibrary.models - :members: - :noindex: diff --git a/docs/api/page.rst b/docs/api/page.rst deleted file mode 100644 index 78a1f88af..000000000 --- a/docs/api/page.rst +++ /dev/null @@ -1,109 +0,0 @@ -Page module -=========== - - -.. automodule:: feincms.module.page.models - :members: - :noindex: - - -Sitemap module --------------- - -.. automodule:: feincms.module.page.sitemap - :members: - :noindex: - - -Extensions ----------- - -Date-based publishing -********************* - -.. automodule:: feincms.module.page.extensions.datepublisher - :members: - :noindex: - - -Page excerpts -************* - -.. automodule:: feincms.module.page.extensions.excerpt - :members: - :noindex: - - -Navigation extensions -********************* - -.. automodule:: feincms.module.page.extensions.navigation - :members: - :noindex: - - -Related pages -************* - -.. automodule:: feincms.module.page.extensions.relatedpages - :members: - :noindex: - - -Symlinked page content -********************** - -.. automodule:: feincms.module.page.extensions.symlinks - :members: - :noindex: - - -Flexible page titles -******************** - -.. automodule:: feincms.module.page.extensions.titles - :members: - :noindex: - - -Page translations -***************** - -.. automodule:: feincms.module.page.extensions.translations - :members: - :noindex: - - -Extensions not specific to the page module ------------------------------------------- - -Creation and modification timestamps -************************************ - -.. automodule:: feincms.module.extensions.changedate - :members: - :noindex: - - -Content type count denormalization -********************************** - -.. automodule:: feincms.module.extensions.ct_tracker - :members: - :noindex: - - -Featured items -************** - -.. automodule:: feincms.module.extensions.featured - :members: - :noindex: - - -Search engine optimization fields -********************************* - -.. automodule:: feincms.module.extensions.seo - :members: - :noindex: diff --git a/docs/api/settings.rst b/docs/api/settings.rst deleted file mode 100644 index 998491c5d..000000000 --- a/docs/api/settings.rst +++ /dev/null @@ -1,7 +0,0 @@ -Settings -======== - - -.. automodule:: feincms.default_settings - :members: - :noindex: diff --git a/docs/api/shortcuts.rst b/docs/api/shortcuts.rst deleted file mode 100644 index d323c02f6..000000000 --- a/docs/api/shortcuts.rst +++ /dev/null @@ -1,6 +0,0 @@ -Shortcuts -========= - -.. automodule:: feincms.shortcuts - :members: - :noindex: diff --git a/docs/api/templatetags.rst b/docs/api/templatetags.rst deleted file mode 100644 index a594dd417..000000000 --- a/docs/api/templatetags.rst +++ /dev/null @@ -1,52 +0,0 @@ -Template tags -============= - -FeinCMS tags ------------- - -.. automodule:: feincms.templatetags.feincms_tags - :members: - :noindex: - - -Thumbnail filters ------------------ - -.. automodule:: feincms.templatetags.feincms_thumbnail - :members: - :noindex: - - -Page-module specific tags -------------------------- - -.. automodule:: feincms.module.page.templatetags.feincms_page_tags - :members: - :noindex: - - -``ApplicationContent`` tags ---------------------------- - -.. automodule:: feincms.templatetags.applicationcontent_tags - :members: - :noindex: - -.. automodule:: feincms.templatetags.fragment_tags - :members: - :noindex: - - -Tags not part of the public API -------------------------------- - -These tags aren't guaranteed to stay or to be kept backwards compatible in -any way. They should be considered for internal use only. - -.. automodule:: feincms.templatetags.feincms_admin_tags - :members: - :noindex: - -.. automodule:: feincms.templatetags.feincms_compat_tags - :members: - :noindex: diff --git a/docs/api/translations.rst b/docs/api/translations.rst deleted file mode 100644 index c55a1f019..000000000 --- a/docs/api/translations.rst +++ /dev/null @@ -1,6 +0,0 @@ -Translations -============ - -.. automodule:: feincms.translations - :members: - :noindex: diff --git a/docs/api/utils.rst b/docs/api/utils.rst deleted file mode 100644 index 4f3b36974..000000000 --- a/docs/api/utils.rst +++ /dev/null @@ -1,26 +0,0 @@ -Utilities -========= - -.. automodule:: feincms.utils - :members: - :noindex: - - -HTML utilities --------------- - -.. automodule:: feincms.utils.html.cleanse - :members: - :noindex: - -.. automodule:: feincms.utils.html.tidy - :members: - :noindex: - - -Template tag helpers --------------------- - -.. automodule:: feincms.utils.templatetags - :members: - :noindex: diff --git a/docs/api/views.rst b/docs/api/views.rst deleted file mode 100644 index 22500c1b8..000000000 --- a/docs/api/views.rst +++ /dev/null @@ -1,44 +0,0 @@ -Views and decorators -==================== - - -Standard views --------------- - -.. automodule:: feincms.views.base - :members: - :noindex: - - -Generic-views replacements --------------------------- - -All views in the ``feincms.views.generic`` module are almost the same -as their counterparts in ``django.views.generic`` (before class-based -views came along), except that they add a ``feincms_page`` object to -the context. - - -.. automodule:: feincms.views.generic.simple - :members: - :noindex: - -.. automodule:: feincms.views.generic.list_detail - :members: - :noindex: - -.. automodule:: feincms.views.generic.date_based - :members: - :noindex: - -.. automodule:: feincms.views.generic.create_update - :members: - :noindex: - - -Decorators ----------- - -.. automodule:: feincms.views.decorators - :members: - :noindex: diff --git a/docs/conf.py b/docs/conf.py index c85f09ef1..ec2f3cd0f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # FeinCMS documentation build configuration file, created by # sphinx-quickstart on Mon Aug 10 17:03:33 2009. @@ -11,36 +10,35 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.append(os.path.abspath('.')) -sys.path.append(os.path.abspath('..')) -os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings' # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc'] +extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8' +# source_encoding = 'utf-8' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'FeinCMS' -copyright = u'2009-2010, Feinheit GmbH and contributors' +project = "FeinCMS" +copyright = "2009-2010, Feinheit GmbH and contributors" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -48,153 +46,159 @@ # # The short X.Y version. sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) -import feincms +import feincms # noqa + -version = '.'.join(map(str, feincms.VERSION)) +version = ".".join(map(str, feincms.VERSION)) # The full version, including alpha/beta/rc tags. release = feincms.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. -#unused_docs = [] +# unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. -exclude_trees = ['_build'] +exclude_trees = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -html_theme_path = ['_theme'] -html_theme = 'nature' +# html_theme_path = ['_theme'] +# html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_use_modindex = True +# html_use_modindex = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' +# html_file_suffix = '' # Output file base name for HTML help builder. -htmlhelp_basename = 'FeinCMSdoc' +htmlhelp_basename = "FeinCMSdoc" # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -latex_paper_size = 'a4' +latex_paper_size = "a4" # The font size ('10pt', '11pt' or '12pt'). -latex_font_size = '10pt' +latex_font_size = "10pt" # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'FeinCMS.tex', u'FeinCMS Documentation', - u'Feinheit GmbH and contributors', 'manual'), + ( + "index", + "FeinCMS.tex", + "FeinCMS Documentation", + "Feinheit GmbH and contributors", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +# latex_use_modindex = True diff --git a/docs/contenttypes.rst b/docs/contenttypes.rst index 8295fc33c..e9fadb201 100644 --- a/docs/contenttypes.rst +++ b/docs/contenttypes.rst @@ -50,7 +50,7 @@ The minimal content type is an abstract Django model with a :func:`render` method, nothing else:: class TextileContent(models.Model): - content = models.TextField(_('content')) + content = models.TextField() class Meta: abstract = True @@ -76,28 +76,29 @@ in the rendered result. .. note:: + The examples on this page assume that you use the :class:`~feincms.module.page.models.Page` CMS base model. The principles outlined apply for all other CMS base types. -The complete code required to implement and include a custom textile content +The complete code required to implement and include a custom markdown content type is shown here:: + from markdown2 import markdown from feincms.module.page.models import Page - from django.contrib.markup.templatetags.markup import textile from django.db import models - class TextilePageContent(models.Model): + class MarkdownPageContent(models.Model): content = models.TextField() class Meta: abstract = True def render(self, **kwargs): - return textile(self.content) + return markdown(self.content) - Page.create_content_type(TextilePageContent) + Page.create_content_type(MarkdownPageContent) There are three field names you should not use because they are added @@ -125,9 +126,7 @@ key depending on the current domain. The two template tags ``feincms_render_regi and ``feincms_render_content`` pass the current rendering context as a keyword argument too. -The example above could be rewritten like this: - -:: +The example above could be rewritten like this:: {% load feincms_tags %} @@ -148,9 +147,7 @@ The example above could be rewritten like this: -Or even like this: - -:: +Or even like this:: {% load feincms_tags %} @@ -208,7 +205,7 @@ media files they need:: def media(self): return forms.Media( css={'all': ('gallery/gallery.css',),}, - js=('gallery/gallery.js'), + js=('gallery/gallery.js',), ) def render(self, **kwargs): @@ -222,6 +219,18 @@ provide the ``media`` property yourself. As with form and widget media definitio either ``STATIC_URL`` or ``MEDIA_URL`` (in this order) will be prepended to the media file path if it is not an absolute path already. +Alternatively, you can use the ``media_property`` function from django.forms +to implement the functionality, which then also supports inheritance +of media files:: + + from django.forms.widgets import media_property + + class MediaUsingContentType(models.Model): + class Media: + js = ('whizbang.js',) + + MediaUsingContentType.media = media_property(MediaUsingContentType) + .. _contenttypes-processfinalize: @@ -304,38 +313,39 @@ Application content Used to let the administrator freely integrate 3rd party applications into the CMS. Described in :ref:`integration-applicationcontent`. - -Comments content ----------------- -.. module:: feincms.content.comments.models -.. class:: CommentsContent() - -Comment list and form using ``django.contrib.comments``. - - -Contact form ------------- +Contact form content +-------------------- .. module:: feincms.content.contactform.models -.. class:: ContactForm() +.. class:: ContactFormContent() Simple contact form. Also serves as an example how forms might be used inside content types. - -Inline files and images ------------------------ +Inline files +------------ .. module:: feincms.content.file.models .. class:: FileContent() + +Simple content types holding just a file. +You should probably use the MediaFileContent though. + +Inline images +------------- .. module:: feincms.content.image.models .. class:: ImageContent() -These are simple content types holding just a file or an image with a +Simple content types holding just an image with a position. You should probably use the MediaFileContent though. +Additional arguments for :func:`~feincms.models.Base.create_content_type`: + +* ``POSITION_CHOICES`` + +* ``FORMAT_CHOICES`` Media library integration ------------------------- -.. module:: feincms.content.medialibrary.v2 +.. module:: feincms.module.medialibrary.contents .. class:: MediaFileContent() Mini-framework for arbitrary file types with customizable rendering @@ -358,8 +368,8 @@ Additional arguments for :func:`~feincms.models.Base.create_content_type`: a particular image media file with type ``download``: * ``content/mediafile/image_download.html`` - * ``content/mediafile/download.html`` * ``content/mediafile/image.html`` + * ``content/mediafile/download.html`` * ``content/mediafile/default.html`` The media file type is stored directly on @@ -373,7 +383,7 @@ Additional arguments for :func:`~feincms.models.Base.create_content_type`: Raw content ----------- -.. module:: feincms.content.raw.models +.. module:: feincms.contents .. class:: RawContent() Raw HTML code, f.e. for flash movies or javascript code. @@ -381,47 +391,62 @@ Raw HTML code, f.e. for flash movies or javascript code. Rich text --------- -.. module:: feincms.content.richtext.models +.. module:: feincms.contents .. class:: RichTextContent() -Rich text editor widget, stripped down to the essentials; no media support, only -a few styles activated. The necessary javascript files are not included, -you need to put them in the right place on your own. +Rich text editor widget, stripped down to the essentials; no media support, +only a few styles activated. -By default, ``RichTextContent`` expects a TinyMCE activation script at -``js/tiny_mce/tiny_mce.js``. This can be customized by overriding -``FEINCMS_RICHTEXT_INIT_TEMPLATE`` and ``FEINCMS_RICHTEXT_INIT_CONTEXT`` in -your ``settings.py`` file. +By default, ``RichTextContent`` uses the CDN-served version of TinyMCE 4.1. +This can be customized by overriding ``FEINCMS_RICHTEXT_INIT_TEMPLATE`` and +``FEINCMS_RICHTEXT_INIT_CONTEXT`` in your ``settings.py`` file. If you only want to provide a different path to the TinyMCE javascript file, you can do this as follows:: FEINCMS_RICHTEXT_INIT_CONTEXT = { 'TINYMCE_JS_URL': '/your_custom_path/tiny_mce.js', - } - -If you pass cleanse=True to the create_content_type invocation for your -RichTextContent types, the HTML code will be cleansed right before saving -to the database everytime the content is modified. + } Additional arguments for :func:`~feincms.models.Base.create_content_type`: * ``cleanse``: - Whether the HTML code should be cleansed of all tags and attributes - which are not explicitly whitelisted. The default is ``False``. + Either the dotted python path to a function or a function itself which + accepts a HTML string and returns a cleansed version of it. A library which + is often used for this purpose is + `feincms-cleanse `_. -RSS feeds ---------- -.. module:: feincms.content.rss.models -.. class:: RSSContent +CKEditor +~~~~~~~~ + +Add the following settings to activate `CKEditor `_:: + + FEINCMS_RICHTEXT_INIT_TEMPLATE = 'admin/content/richtext/init_ckeditor.html' + FEINCMS_RICHTEXT_INIT_CONTEXT = { + # See http://cdn.ckeditor.com/ for the latest version: + 'CKEDITOR_JS_URL': '//cdn.ckeditor.com/4.4.2/standard/ckeditor.js', + } + + +Other rich text libraries +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Other rich text widgets can be wired up for the RichTextContent. +You would have to write two functions: One which is called when +rich text editing functionality is added ("richify"), and another +one which is called when functionality is removed ("poorify"). +The second is necessary because rich text editors do not like +being dragged; when dragging a rich text content type, it is first +poorified and then richified again as soon as the content type has +been dropped into its final position. -A feed reader widget. This also serves as an example how to build a content -type that needs additional processing, in this case from a cron job. If an -RSS feed has been added to the CMS, ``manage.py update_rsscontent`` should -be run periodically (either through a cron job or through other means) to -keep the shown content up to date. The `feedparser` module is required. +To perform those operations + * Add a function adding the new rich text editor to + ``contentblock_init_handlers`` and to ``contentblock_move_handlers.richify`` + * Add a function removing the rich text editor to + ``contentblock_move_handlers.poorify`` Section content @@ -432,34 +457,22 @@ Section content Combined rich text editor, title and media file. -Table content -------------- -.. module:: feincms.content.table.models -.. class:: TableContent() - -The default configuration of the rich text editor does not include table -controls. Because of this, you can use this content type to provide HTML -table editing support. The data is stored in JSON format, additional -formatters can be easily written which produce the definitive HTML -representation of the table. - - Template content ---------------- -.. module:: feincms.content.table.template +.. module:: feincms.contents .. class:: TemplateContent() -This content scans all template directories for templates below -``content/template/`` and allows the user to select one of these templates -which are rendered using the Django template language. - -Template usage isn't restricted in any way. +This is a content type that just renders a template. The available +templates have to be specified when creating the content type:: -.. note:: + Page.create_content_type(TemplateContent, TEMPLATES=( + ('content/template/something1.html', _('Something 1')), + ('content/template/something2.html', _('Something 2')), + )) - You cannot use Django's cached template loader with ``TemplateContent`` - currently. The cached template loader has no way of enumerating - available templates in the filesystem. +Also note that a template content is not sandboxed or specially rendered. +Whatever a django template can do a TemplateContent snippet can do too, +so be careful whom you grant write permissions. Video inclusion code for youtube, vimeo etc. @@ -478,9 +491,7 @@ Restricting a content type to a subset of regions Imagine that you have developed a content type which really only makes sense in the sidebar, not in the main content area. It is very simple to restrict a content type to a subset of regions, the only thing you have to do is pass a -tuple of region keys to the create_content_type method: - -:: +tuple of region keys to the create_content_type method:: Page.create_content_type(SomeSidebarContent, regions=('sidebar',)) @@ -539,20 +550,16 @@ There are two recommended ways. The example use a ``RawContent`` content type an the Page CMS base class. You could take advantage of the fact that ``create_content_type`` returns the -created model: - -:: +created model:: from feincms.module.page.models import Page - from feincms.content.raw.models import RawContent + from feincms.contents import RawContent PageRawContent = Page.create_content_type(RawContent) -Or you could use :func:`content_type_for`: - -:: +Or you could use :func:`content_type_for`:: - from feincms.content.raw.models import RawContent + from feincms.contents import RawContent PageRawContent = Page.content_type_for(RawContent) diff --git a/docs/contributing.rst b/docs/contributing.rst index 7f9ca1991..4feed80a0 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -12,9 +12,9 @@ The FeinCMS repository on github has several branches. Their purpose and rewinding policies are described below. * ``maint``: Maintenance branch for the second-newest version of FeinCMS. -* ``master``: Stable version of FeinCMS. +* ``main``: Stable version of FeinCMS. -``master`` and ``maint`` are never rebased or rewound. +``main`` and ``maint`` are never rebased or rewound. * ``next``: Upcoming version of FeinCMS. This branch is rarely rebased if ever, but this might happen. A note will be sent to the official diff --git a/docs/deprecation.rst b/docs/deprecation.rst new file mode 100644 index 000000000..12c6d8aaa --- /dev/null +++ b/docs/deprecation.rst @@ -0,0 +1,128 @@ +.. _deprecation: + +============================ +FeinCMS Deprecation Timeline +============================ + + +This document outlines when various pieces of FeinCMS will be removed or +altered in backward incompatible way. Before a feature is removed, a warning +will be issued for at least two releases. + + +1.6 +=== + +* The value of ``FEINCMS_REVERSE_MONKEY_PATCH`` has been changed to ``False``. +* Deprecated page manager methods have been removed (``page_for_path_or_404``, + ``for_request_or_404``, ``best_match_for_request``, ``from_request``) - + ``Page.objects.for_request()``, ``Page.objects.page_for_path`` and + ``Page.objects.best_match_for_path`` should cover all use cases. +* Deprecated page methods have been removed (``active_children``, + ``active_children_in_navigation``, ``get_siblings_and_self``) +* Request and response processors have to be imported from + :mod:`feincms.module.page.processors`. Additionally, they must be registered + individually by using ``register_request_processor`` and + ``register_response_processor``. +* Prefilled attributes have been removed. Use Django's ``prefetch_related`` + or ``feincms.utils.queryset_transform`` instead. +* ``feincms.views.base`` has been moved to ``feincms.views.legacy``. Use + ``feincms.views.cbv`` instead. +* ``FEINCMS_FRONTEND_EDITING``'s default has been changed to ``False``. +* The code in :mod:`feincms.module.page.models` has been split up. The admin + classes are in :mod:`feincms.module.page.modeladmin`, the forms in + :mod:`feincms.module.page.forms` now. Analogous changes have been made + to :mod:`feincms.module.medialibrary.models`. + + +1.7 +=== + +* The monkeypatch to make Django's :func:`django.core.urlresolvers.reverse` + applicationcontent-aware will be removed. Use + :func:`feincms.content.application.models.app_reverse` and the corresponding + template tag instead. + +* The module :mod:`feincms.content.medialibrary.models` will be replaced by + the contents of :mod:`feincms.content.medialibrary.v2`. The latter uses + Django's ``raw_id_fields`` support instead of reimplementing it badly. + +* The legacy views inside :mod:`feincms.views.legacy` will be removed. + + +1.8 +=== + +* The module ``feincms.admin.editor`` will be removed. The model admin classes + have been available in :mod:`feincms.admin.item_editor` and + :mod:`feincms.admin.tree_editor` since FeinCMS v1.0. + +* Cleansing the HTML of a rich text content will still be possible, but the + cleansing module :mod:`feincms.utils.html.cleanse` will be removed. When + creating a rich text content, the ``cleanse`` argument must be a callable + and cannot be ``True`` anymore. The cleansing function has been moved into + its own package, + `feincms-cleanse `_. + +* Registering extensions using shorthand notation will be not be possible in + FeinCMS v1.8 anymore. Use the following method instead:: + + Page.register_extensions( + 'feincms.module.page.extensions.navigation', + 'feincms.module.extensions.ct_tracker', + ) + +* ``feincms_navigation`` and ``feincms_navigation_extended`` will be removed. + Their functionality is provided by ``feincms_nav`` instead. + +* The function-based generic views aren't available in Django after v1.4 + anymore. :mod:`feincms.views.generic` and + :func:`feincms.views.decorators.add_page_to_extra_context` will be removed + as well. + +* The module :mod:`feincms.content.medialibrary.v2`, which is only an alias for + :mod:`feincms.content.medialibrary.models` starting with FeinCMS v1.7 will be + removed. + +* ``Page.setup_request()`` does not do anything anymore and will be removed. + + +1.9 +=== + +* Fields added through page extensions which haven't been explicitly added + to the page model admin using ``modeladmin.add_extension_options`` will + disappear from the admin interface. The automatic collection of fields + will be removed. + +* All extensions should inherit from ``feincms.extensions.Extension``. + Support for ``register(cls, admin_cls)``-style functions will be removed + in FeinCMS v1.9. + +* The ``_feincms_extensions`` attribute on the page model and on models + inheriting ``ExtensionsMixin`` is gone. + + +1.10 +==== + +No deprecations. + + +1.11 +==== + +* ``RSSContent`` and ``update_rsscontent`` have been deprecated. + +* The automatic discovery of subclasses of ``NavigationExtension`` has been + replaced with an explicit mechanism of defining navigation extensions. + +* ``Page.cache_key`` has never been used by FeinCMS itself and will therefore + be removed in a future release. Comparable functionality has been available + for a long time with ``Page.path_to_cache_key``. + + +1.12 +==== + +* TODO update this diff --git a/docs/extensions.rst b/docs/extensions.rst new file mode 100644 index 000000000..55fa96dcf --- /dev/null +++ b/docs/extensions.rst @@ -0,0 +1,77 @@ +.. _extensions: + +Extensions +========== + +The extensions mechanism has been refactored to remove the need to make models +know about their related model admin classes. The new module +:py:mod:`feincms.extensions` contains mixins and base classes - their purpose +is as follows: + +.. class:: feincms.extensions.ExtensionsMixin + + This mixin provides the ``register_extensions`` method which is the place + where extensions are registered for a certain model. Extensions can be + specified in the following ways: + + - Subclasses of :py:class:`~feincms.extensions.Extension` + - Dotted Python module paths pointing to a subclass of the aforementioned + extension class + - Dotted Python module paths pointing to a module containing either a class + named ``Extension`` or a function named ``register`` (for legacy + extensions) + + +.. class:: feincms.extensions.Extension + + This is the base class for your own extension. It has the following methods + and properties: + + .. attribute:: model + + The model class. + + .. method:: handle_model(self) + + The method which modifies the Django model class. The model class is + available as ``self.model``. + + .. method:: handle_modeladmin(self, modeladmin) + + This method receives the model admin instance bound to the model. This + method could be called more than once, especially when using more than + one admin site. + + +.. class:: feincms.extensions.ExtensionModelAdmin() + + This is a model admin subclass which knows about extensions, and lets the + extensions do their work modifying the model admin instance after it has + been successfully initialized. It has the following methods and properties: + + .. method:: initialize_extensions(self) + + This method is automatically called at the end of initialization and + loops through all registered extensions and calls their + ``handle_modeladmin`` method. + + .. method:: add_extension_options(self, \*f) + + This is a helper to add fields and fieldsets to a model admin instance. + Usage is as follows:: + + modeladmin.add_extension_options('field1', 'field2') + + Or:: + + modeladmin.add_extension_options(_('Fieldset title'), { + 'fields': ('field1', 'field2'), + }) + + +.. note:: + + Only model and admin instances which inherit from + :class:`~feincms.extensions.ExtensionsMixin` and + :class:`~feincms.extensions.ExtensionModelAdmin` can be extended + this way. diff --git a/docs/faq.rst b/docs/faq.rst index 9825d7e58..e6f323788 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -30,40 +30,3 @@ too big, it might be time to reconsider whether you really want to use the extension mechanism or if it might not be easier to start freshly, only using the editor admin classes, feincms.models.Base and maybe parts of the included PageManager... - - - -I get a ``mptt.AlreadyRegistered`` exception when I try using the :mod:`~feincms.module.page` module! -===================================================================================================== - -This happens when the Django model loading code encounters an ``ImportError`` -when it loads the :mod:`~feincms.module.page` module for the first time. The -module is added to a list of postponed modules, and the import is retried -after all other applications have been successfully imported. This will -cause the ``mptt.register`` call to be executed twice though, which is why -you see this exception. We could catch this exception and go on as if -nothing had happened, but this hides a deeper problem somewhere which should -be fixed for good instead of papering over the issue. - -There are several ways how you might find out what's going wrong. Raise the -debugging level, try importing the page module (and other modules) from the -shell you get when you use ``./manage.py shell`` are some possibilities how -you might debug this problem. - - - -I run ``syncdb`` and get a message about missing columns in the page table -========================================================================== - -You enabled the page module (added :mod:`feincms.module.page` to -``INSTALLED_APPS``), run syncdb, and afterwards registered a few -extensions. The extensions you activated -(:mod:`~feincms.module.page.extensions.datepublisher` and -:mod:`~feincms.module.page.extensions.translations`) add new fields to -the page model, but your first ``syncdb`` did not know about them and -therefore did not create the columns for those extensions. - -You can either remove the line ``Page.register_extensions(...)`` from -your code or drop the page_page table and re-run ``syncdb``. If you want -to keep the pages you've already created, you need to figure out the -correct ALTER TABLE statements for your database yourself. diff --git a/docs/index.rst b/docs/index.rst index 5ef18147f..b5110b395 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,30 +15,28 @@ to a region (f.e. the sidebar, the main content region or something else which I haven't thought of yet). It provides helper functions, which provide ordered lists of page content blocks. That's all. -Adding your own content types is extremely easy. Do you like textile +Adding your own content types is extremely easy. Do you like markdown that much, that you'd rather die than using a rich text editor? Then add the following code to your project, and you can go on using the -CMS without being forced to use whatever the developers deemed best: - -:: +CMS without being forced to use whatever the developers deemed best:: + from markdown2 import markdown from feincms.module.page.models import Page - from django.contrib.markup.templatetags.markup import textile from django.db import models - class TextilePageContent(models.Model): + class MarkdownPageContent(models.Model): + content = models.TextField() + class Meta: abstract = True - content = models.TextField() - def render(self, **kwargs): - return textile(self.content) + return markdown(self.content) - Page.create_content_type(TextilePageContent) + Page.create_content_type(MarkdownPageContent) -That's it. Not even ten lines for your own page content type. +That's it. Only ten lines of code for your own page content type. @@ -51,47 +49,38 @@ Contents installation page contenttypes + extensions admin integration medialibrary templatetags + settings migrations versioning advanced/index faq contributing + deprecation -API Documentation -================= - -.. toctree:: - :maxdepth: 3 +.. include:: ../CHANGELOG.rst - api/core - api/admin - api/page - api/medialibrary - api/blog - api/contenttypes - api/contextprocessors - api/contrib - api/settings - api/shortcuts - api/templatetags - api/translations - api/utils - api/views - api/commands - - -Releases -======== +Old release notes +================= .. toctree:: :maxdepth: 1 + releases/1.13 + releases/1.12 + releases/1.11 + releases/1.10 + releases/1.9 + releases/1.8 + releases/1.7 + releases/1.6 + releases/1.5 releases/1.4 releases/1.3 releases/1.2 diff --git a/docs/installation.rst b/docs/installation.rst index 2257f8a16..e607bccf2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -7,53 +7,65 @@ Installation instructions Installation ============ -This document describes the steps needed to get FeinCMS up and running. +This document describes the steps needed to install FeinCMS. -FeinCMS is based on Django, so you need a working Django_ installation -first. The minimum support version of Django_ is the 1.2 line of releases. +FeinCMS requires a working installation of Django_ version 1.7 or better +See the Django_ documentation for how to install and configure Django. -You can download a stable release of FeinCMS using ``easy_install``:: +You can download a stable release of FeinCMS using ``pip``. Pip will install +feincms and its dependencies. Dependencies which are automatically installed +are: feedparser_, Pillow_ and django-mptt_. For outdated versions of +Django the best place to find supported combinations of library versions is the +`tox build configuration +`_. - $ sudo easy_install feincms + $ pip install feincms -Please note that the package installable with ``easy_install`` only -contains the files needed to run FeinCMS. It does not include documentation, -tests or the example project which comes with the development version, -which you can download using the Git_ version control system:: +In order to install documentation and tests install from the Git_ repository +instead:: $ git clone git://github.com/feincms/feincms.git -In addition, you will need a django-mptt_ installation. +Please be aware that feincms3 is being developed as a separate, new project. +For new CMS projects I’m more likely to use django-content-editor and feincms3. +You can read more about feincms3 on https://feincms3.readthedocs.io/en/latest/ -Finally, some content types or extensions require recent versions of -lxml_, django-tagging_, feedparser_ and the python imaging library PIL_ -(PIL_ is actually a dependency of Django_'s ImageField). +If you are looking to implement a blog, check out elephantblog_. + +You will also need a Javascript WYSIWYG editor of your choice (Not included). +TinyMCE_ and CKEditor_ work out of the box and are recommended. .. _Django: http://www.djangoproject.com/ .. _Git: http://git-scm.com/ .. _Subversion: http://subversion.tigris.org/ .. _django-mptt: http://github.com/django-mptt/django-mptt/ -.. _django-tagging: http://code.google.com/p/django-tagging/ -.. _lxml: http://codespeak.net/lxml/ .. _feedparser: http://www.feedparser.org/ -.. _PIL: http://www.pythonware.com/products/pil/ +.. _Pillow: https://pypi.python.org/pypi/Pillow/ +.. _elephantblog: http://github.com/feincms/feincms-elephantblog +.. _TinyMCE: http://www.tinymce.com/ +.. _CKEditor: http://ckeditor.com/ Configuration ============= -There isn't much left to do apart from adding ``feincms`` to ``INSTALLED_APPS``. +There isn't much left to do apart from adding a few entries to +``INSTALLED_APPS``. Most commonly you'll want to add:: + + feincms, + mptt, + feincms.module.page, + feincms.module.medialibrary + The customized administration interface needs some media and javascript -libraries which you have to make available to the browser. If you use Django 1.3's -``django.contrib.staticfiles`` application, the media files will be picked up -automatically by the ``collectstatic`` management command. If you use ``/static/`` -as ``STATIC_URL``, all is fine. Otherwise you have to set ``FEINCMS_ADMIN_MEDIA`` -to the path where the FeinCMS media files can be found. +libraries which you have to make available to the browser. FeinCMS uses +Django's ``django.contrib.staticfiles`` application for this purpose. The media +files will be picked up automatically by the ``collectstatic`` management +command. -If you use an older version of Django, publish the files in the folder -``feincms/static/feincms/`` somewhere on your site and set ``FEINCMS_ADMIN_MEDIA`` -to the location. +If your website is multi-language you have to define ``LANGUAGES`` in the +settings_. Please note that the ``feincms`` module will not create or need any database tables, but you need to put it into ``INSTALLED_APPS`` because otherwise the @@ -64,3 +76,5 @@ activities. The most common use of a CMS is to manage a hierarchy of pages and this is the most advanced module of FeinCMS too. Please proceed to :ref:`page` to find out how you can get the page module up and running. + +.. _settings: https://docs.djangoproject.com/en/dev/ref/settings/#languages diff --git a/docs/integration.rst b/docs/integration.rst index 956389c77..47de52696 100644 --- a/docs/integration.rst +++ b/docs/integration.rst @@ -5,33 +5,35 @@ Integrating 3rd party apps into your site ========================================= With FeinCMS come a set of standard views which you might want to check -out before starting to write your own. Included is a standard view for -pages, and a set of generic view drop-in replacements which know about -the CMS. +out before starting to write your own. Default page handler ==================== -The default CMS handler view is ``feincms.views.base.handler``. You can +The default CMS handler view is ``feincms.views.cbv.handler``. You can add the following as last line in your ``urls.py`` to make a catch-all -for any pages which were not matched before: +for any pages which were not matched before:: -:: + from feincms.views import Handler + handler = Handler.as_view() - urlpatterns += patterns('', - url(r'^$', base.handler, name='feincms_home'), - url(r'^(.*)/$', base.handler, name='feincms_handler'), - ) + urlpatterns += [ + url(r'^$', handler, name='feincms_home'), + url(r'^(.*)/$', handler, name='feincms_handler'), + ] Note that this default handler can also take a keyword parameter ``path`` to specify which url to render. You can use that functionality to -implement a default page by adding another entry to your ``urls.py``: +implement a default page by adding another entry to your ``urls.py``:: -:: + from feincms.views import Handler + handler = Handler.as_view() - url(r'^$', 'feincms.views.base.handler', {'path': '/rootpage'}, + ... + url(r'^$', handler, {'path': '/rootpage'}, name='feincms_home') + ... Please note that it's easier to include ``feincms.urls`` at the bottom @@ -39,23 +41,26 @@ of your own URL patterns like this:: # ... - urlpatterns += patterns('', + urlpatterns += [ url(r'', include('feincms.urls')), - ) + ] +The URLconf entry names ``feincms_home`` and ``feincms_handler`` must +both exist somewhere in your project. The standard ``feincms.urls`` +contains definitions for both. If you want to provide your own view, +it's your responsability to create correct URLconf entries. -Generic views -============= + +Generic and custom views +======================== If you use FeinCMS to manage your site, chances are that you still want -to use generic views for certain parts. You probably still need a +to use generic and/or custom views for certain parts. You probably still need a ``feincms_page`` object inside your template to generate the navigation and -render regions not managed by the generic views. By simply replacing -:mod:`django.views.generic` with :mod:`feincms.views.generic` in your -``urls.py``. The page which -most closely matches the current request URI will be passed into the -template by automatically adding ``feincms_page`` to the ``extra_context`` -generic view argument. +render regions not managed by the generic views. The best way to ensure +the presence of a ``feincms_page`` instance in the template context is +to add ``feincms.context_processors.add_page_if_missing`` to your +``TEMPLATE_CONTEXT_PROCESSORS`` setting. .. _integration-applicationcontent: @@ -63,32 +68,11 @@ generic view argument. Integrating 3rd party apps ========================== -The :class:`~feincms.content.application.models.ApplicationContent` will -help you with this. - -The plugin/content type needs a URLconf and uses resolve and a patched -reverse to integrate the application into the CMS. The advantages are -that there is no modification of the ROOT_URLCONF necessary when -moving the integration point for the 3rd party application around. On -the downside, the application's template has less control over the -base template and views inside the 3rd party app cannot be reversed -from outside the ApplicationContent renderer. The bigger flexibility -in choosing the integration point comes with a cost when it comes to -rendering the content from the 3rd party app. - -.. warning:: - - The monkey patch for ``django.core.urlresolvers.reverse`` contained - in the ``ApplicationContent`` code has to happen "early enough". If - ``reverse`` is unable to determine URLs for views integrated through - ``ApplicationContent`` you have to move the application content code - import to an earlier place (f.e. on the first line of your main - ``models.py`` or even in your ``urls.py`` (barring circular import - problems). - - This might change if we find a better way to solve this or if - Django allows us to poke deeper into the URL resolving/reversing - machinery. +Third party apps such as django-registration can be integrated in the CMS +too. :class:`~feincms.content.application.models.ApplicationContent` lets you +delegate a subset of your page tree to a third party application. The only +thing you need is specifying a URLconf file which is used to determine which +pages exist below the integration point. Adapting the 3rd party application for FeinCMS @@ -111,180 +95,232 @@ can be too easily violated. An example ``urls.py`` follows:: - from django.conf.urls.defaults import * + from django.conf.urls import include, url + from django.views.generic.detail import DetailView + from django.views.generic.list import ListView from news.models import Entry - entry_dict = {'queryset': Entry.objects.all()} - - urlpatterns = patterns('', - url(r'^$', - 'django.views.generic.list_detail.object_list', - entry_dict, - name='entry_list'), - url(r'^(?P\d{4})/(?P\d{2})/(?P\d{2})/(?P[^/]+)/', - 'django.views.generic.date_based.object_detail', - dict(entry_dict, **{'date_field': 'published_date', 'month_format': '%m', 'slug_field': 'slug'}), - name='entry_detail'), - ) + urlpatterns = [ + url(r'^$', ListView.as_view( + queryset=Entry.objects.all(), + ), name='entry_list'), + url(r'^(?P[^/]+)/$', DetailView.as_view( + queryset=Entry.objects.all(), + ), name='entry_detail'), + ] + +Please note that you should not add the ``news/`` prefix here. You should +*not* reference this ``urls.py`` file anywhere in a ``include`` statement. -Please note that you should not add the ``news/`` prefix here unless -you know exactly what you are doing. Furthermore, this ``urls.py`` is -incomplete -- for a real world implementation, you'd need to add yearly, -monthly and daily archive views too. Furthermore, you should *not* include -this ``urls.py`` file anywhere accessible from your ``ROOT_URLCONF``. -If you write your view methods yourself instead of using generic views, you -should not construct whole response objects, but return the content as a unicode -string. It does not hurt to encapsulate the content inside a response object, -it's simply not worth it because the application content will have to extract -the content from the response and throw the response object away anyway. +Registering the 3rd party application with FeinCMS' ``ApplicationContent`` +-------------------------------------------------------------------------- + +It's as simple as that:: + + from feincms.content.application.models import ApplicationContent + from feincms.module.page.models import Page + + Page.create_content_type(ApplicationContent, APPLICATIONS=( + ('news.urls', 'News application'), + )) + + +Writing the models +------------------ + +Because the URLconf entries ``entry_list`` and ``entry_detail`` aren't +reachable through standard means (remember, they aren't ``include``\d +anywhere) it's not possible to use standard ``reverse`` calls to +determine the absolute URL of a news entry. FeinCMS provides its own +``app_reverse`` function (see :ref:`integration-reversing-urls` for +details) mimicking the interface of Django's standard functionality:: -The :class:`~feincms.content.application.models.ApplicationContent` patches -the standard Django ``reverse`` function, so that ``reverse`` and the -``{% url %}`` template tag works as expected inside the application -content render method. Therefore, :meth:`News.get_absolute_url` is -absolutely standard. ``models.py``:: - from datetime import datetime from django.db import models + from feincms.content.application.models import app_reverse class Entry(models.Model): - published_date = models.DateField() title = models.CharField(max_length=200) slug = models.SlugField() description = models.TextField(blank=True) class Meta: - get_latest_by = 'published_date' - ordering = ['-published_date'] + ordering = ['-id'] - def __unicode__(self): + def __str__(self): return self.title - @models.permalink def get_absolute_url(self): - return ('entry_detail', (), { - 'year': self.published_date.strftime('%Y'), - 'month': self.published_date.strftime('%m'), - 'day': self.published_date.strftime('%d'), + return app_reverse('entry_detail', 'news.urls', kwargs={ 'slug': self.slug, - }) + }) -Writing the templates for the application ------------------------------------------ +The only difference is that you do not only have to specify the view name +(``entry_detail``) but also the URLconf file (``news.urls``). The URLconf +string must correspond to the specification used in the ``APPLICATIONS`` +list in the ``create_content_type`` call. -Nothing special here. The only thing you have to avoid is adding ```` or -```` tags and such, because you're only rendering content for a single -content block, not the whole page. An example ``news/entry_detail.html`` follows:: +.. note:: -
-

{{ object.title }}

- {{ object.published_date|date:"d.m.Y" }} + Old FeinCMS versions only provided a monkey patched ``reverse`` + method with a slightly different syntax for reversing URLs. This + behavior has been removed some time ago. - {{ object.description|linebreaks }} -
-And an example ``news/entry_list.html``:: +Returning content from views +---------------------------- - {% for entry in object_list %} -
- {% ifchanged %}
{{ entry.published_date|date:"d.m.Y" }}
{% endifchanged %} -

{{ entry.title }}

-
- {% endfor %} +Three different types of return values can be handled by the application +content code: +* Unicode data (e.g. the return value of ``render_to_string``) +* ``HttpResponse`` instances +* A tuple consisting of two elements: A template instance, template name or list + and a context ``dict``. More on this later under + :ref:`integration-applicationcontent-inheritance20` -Registering and integrating the 3rd party application ------------------------------------------------------ -First, you need to create the content type:: +Unicode data is inserted verbatim into the output. ``HttpResponse`` instances +are returned directly to the client under the following circumstances: - from feincms.content.application.models import ApplicationContent - from feincms.module.page.models import Page +* The HTTP status code differs from ``200 OK`` (Please note that 404 errors may + be ignored if more than one content type with a ``process`` method exists on + the current CMS page.) +* The resource was requested by ``XmlHttpRequest`` (that is, ``request.is_ajax`` + returns ``True``) +* The response was explicitly marked as ``standalone`` by the + :func:`feincms.views.decorators.standalone` view decorator + (made easier by mixing-in :class:`feincms.module.mixins.StandaloneView`) +* The mimetype of the response was not ``text/plain`` or ``text/html`` - Page.create_content_type(ApplicationContent, APPLICATIONS=( - ('news.urls', 'News application'), - )) +Otherwise, the content of the response is unpacked and inserted into the +CMS output as unicode data as if the view returned the content directly, not +wrapped into a ``HttpResponse`` instance. -Your base template does not have to be structured differently just because -you are using application contents now. You must use the bundled FeinCMS -template tags though, because the application content needs the request -object:: +If you want to customize this behavior, provide your own subclass of +``ApplicationContent`` with an overridden ``send_directly`` method. The +described behavior is only a sane default and might not fit everyone's +use case. - {% extends "base.html" %} +.. note:: - {% load feincms_tags %} + The string or response returned should not contain ```` or ```` + tags because this would invalidate the HTML code returned by FeinCMS. - {% block content %} - {% feincms_render_region feincms_page "main" request %} - {% endblock %} -Please note that this necessitates the use of -``django.core.context_processors.request``:: +.. _integration-applicationcontent-inheritance20: - TEMPLATE_CONTEXT_PROCESSORS = ( - 'django.core.context_processors.auth', - 'django.core.context_processors.debug', - 'django.core.context_processors.i18n', - 'django.core.context_processors.media', - 'django.core.context_processors.request', - ) +Letting the application content use the full power of Django's template inheritance +----------------------------------------------------------------------------------- +If returning a simple unicode string is not enough and you'd like to modify +different blocks in the base template, you have to ensure two things: -The 3rd party application might know how to handle more than one URL (the example -news application does). These subpages won't necessarily exist as page instances -in the tree, the standard view knows how to handle this case. +* Use the class-based page handler. This is already the default if you include + ``feincms.urls`` or ``feincms.views.cbv.urls``. +* Make sure your application views use the third return value type described + above: A tuple consisting of a template and a context ``dict``. +The news application views would then look as follows. Please note the absence +of any template rendering calls: -.. _integration-applicationcontent-morecontrol: +``views.py``:: -Letting the application content control more than one region in the parent template ------------------------------------------------------------------------------------ + from django.shortcuts import get_object_or_404 + from news.models import Entry -The output of the third party app is not strictly constrained to the region; -you can pass additional fragments around, for example to extend the page title -with content from the 3rd party application. Suppose we'd like to add the news -title to the title tag. Add the following lines to your ``news/entry_detail.html``:: + def entry_list(request): + # Pagination should probably be added here + return 'news/entry_list.html', {'object_list': Entry.objects.all()} - {% load applicationcontent_tags %} - {% fragment request "title" %}{{ object.translation.title }} - {% endfragment %} + def entry_detail(request, slug): + return 'news/entry_detail', {'object': get_object_or_404(Entry, slug=slug)} -And read the fragment inside your base template:: +``urls.py``:: - {% extends "base.html" %} + from django.conf.urls import url - {% load applicationcontent_tags feincms_page_tags %} + from news.views import entry_list, entry_detail - {% block title %}{% get_fragment request "title" %} - {{ feincms_page.title }} - {{ block.super }}{% endblock %} + urlpatterns = [ + url(r'^$', entry_list, name='entry_list'), + url(r'^(?P[^/]+)/$', entry_detail, name='entry_detail'), + ] - {% block content %} - {% feincms_render_region feincms_page "main" request %} - {% endblock %} +The two templates referenced, ``news/entry_list.html`` and +``news/entry_detail.html``, should now extend a base template. The recommended +notation is as follows:: -Returning responses from the embedded application without wrapping them inside the CMS template ------------------------------------------------------------------------------------------------ + {% extends feincms_page.template.path|default:"base.html" %} -If the 3rd party application returns a response with status code different from -200, the standard view :func:`feincms.views.base.handler` returns -the response verbatim. The same is true if the 3rd party application returns -a response and ``request.is_ajax()`` is ``True`` or if the application content -returns a HttpResponse with the ``standalone`` attribute set to True. + {% block ... %} + {# more content snipped #} -For example, an application can return an non-html export file -- in that case -you don't really want the CMS to decorate the data file with the web html templates: -:: +This ensures that the the selected CMS template is still used when rendering +content. + +.. note:: + + Older versions of FeinCMS only offered fragments for a similar purpose. They + are still supported, but it's recommended you switch over to this style instead. + +.. warning:: + + If you add two application content blocks on the same page and both use this + mechanism, the later 'wins'. + +.. _integration-reversing-urls: + +More on reversing URLs +---------------------- + +Application content-aware URL reversing is available both for Python and +Django template code. + +The function works almost like Django's own ``reverse()`` method except +that it resolves URLs from application contents. The second argument, +``urlconf``, has to correspond to the URLconf parameter passed in the +``APPLICATIONS`` list to ``Page.create_content_type``:: + + from feincms.content.application.models import app_reverse + app_reverse('mymodel-detail', 'myapp.urls', args=...) + +or:: + + app_reverse('mymodel-detail', 'myapp.urls', kwargs=...) + + +The template tag has to be loaded from the ``applicationcontent_tags`` +template tag library first:: - from feincms.views.decorators import standalone + {% load applicationcontent_tags %} + {% app_reverse "mymodel_detail" "myapp.urls" arg1 arg2 %} + +or:: + + {% load applicationcontent_tags %} + {% app_reverse "mymodel_detail" "myapp.urls" name1=value1 name2=value2 %} + +Storing the URL in a context variable is supported too:: + + {% load applicationcontent_tags %} + {% app_reverse "mymodel_detail" "myapp.urls" arg1 arg2 as url %} + +Inside the app (in this case, inside the views defined in ``myapp.urls``), +you can also pass the current request instance instead of the URLconf +name. - @standalone - def my_view(request): - ... - xls_data = ... whatever ... - return HttpResponse(xls_data, content_type="application/msexcel") +If an application has been added several times to the same page tree, +``app_reverse`` tries to find the best match. The logic is contained inside +``ApplicationContent.closest_match``, and can be overridden by subclassing +the application content type. The default implementation only takes the current +language into account, which is mostly helpful when you're using the +translations page extension. Additional customization possibilities @@ -305,8 +341,8 @@ need them. All of these must be specified in the ``APPLICATIONS`` argument to Page.create_content_type(ApplicationContent, APPLICATIONS=( ('registration', 'Account creation and management', { 'urls': 'yourapp.registration_urls', - }), - ) + }), + ) * ``admin_fields``: Adding more fields to the application content interface: @@ -323,15 +359,15 @@ need them. All of these must be specified in the ``APPLICATIONS`` argument to required=False, initial=form.instance.parameters.get('exclusive_subpages', True), help_text=_('Exclude everything other than the application\'s content when rendering subpages.'), - ), - } + ), + } Page.create_content_type(ApplicationContent, APPLICATIONS=( ('registration', 'Account creation and management', { 'urls': 'yourapp.registration_urls', 'admin_fields': registration_admin_fields, - }), - ) + }), + ) The form fields will only be visible after saving the ``ApplicationContent`` for the first time. They are stored inside a JSON-encoded field. The values @@ -369,17 +405,18 @@ need them. All of these must be specified in the ``APPLICATIONS`` argument to Letting 3rd party apps define navigation entries ------------------------------------------------ -Short answer: You need the ``navigation`` extension module. Activate it like -this:: +Short answer: You need the ``feincms.module.page.extensions.navigation`` +extension module. Activate it like this:: - Page.register_extensions('navigation') + Page.register_extensions('feincms.module.page.extensions.navigation') Please note however, that this call needs to come after all ``NavigationExtension`` subclasses have been processed, because otherwise they will not be available for selection in the page administration! (Yes, this is -lame and yes, this is going to change as soon as I find the time to whip up a -better solution.) +lame and yes, this is going to change as soon as we find a +better solution. In the meantime, stick your subclass definition before +the register_extensions call.) Because the use cases for extended navigations are so different, FeinCMS does not go to great lengths trying to cover them all. What it does though @@ -394,27 +431,50 @@ entries, you could do it as follows: #. Assign this navigation extension to the CMS page where you want these navigation entries to appear You don't need to do anything else as long as you use the built-in -``feincms_navigation`` template tag -- it knows how to handle extended navigations. +``feincms_nav`` template tag -- it knows how to handle extended navigations. :: - from feincms.module.page.extensions.navigation import NavigationExtension, PagePretender + from feincms.module.page.extensions import navigation - class BlogCategoriesNavigationExtension(NavigationExtension): + class BlogCategoriesNavigationExtension(navigation.NavigationExtension): name = _('blog categories') def children(self, page, **kwargs): for category in Category.objects.all(): - yield PagePretender( + yield navigation.PagePretender( title=category.name, url=category.get_absolute_url(), - ) + ) - class PassthroughExtension(NavigationExtension): + class PassthroughExtension(navigation.NavigationExtension): name = 'passthrough extension' def children(self, page, **kwargs): for p in page.children.in_navigation(): yield p - Page.register_extensions('navigation') + class MyExtension(navigation.Extension): + navigation_extensions = [ + BlogCategoriesNavigationExtension, + PassthroughExtension, + # Alternatively, a dotted Python path also works. + ] + + Page.register_extensions(MyExtension) + +Note that the objects returned should at least try to mimic a real page so +navigation template tags as ``siblings_along_path_to`` and friends continue +to work, ie. at least the following attributes should exist: + +:: + + title = '(whatever)' + url = '(whatever)' + + # Attributes that MPTT assumes to exist + parent_id = page.id + tree_id = page.tree_id + level = page.level+1 + lft = page.lft + rght = page.rght diff --git a/docs/medialibrary.rst b/docs/medialibrary.rst index 71a56d591..4be37efdc 100644 --- a/docs/medialibrary.rst +++ b/docs/medialibrary.rst @@ -16,24 +16,21 @@ of the page module -- you can use it with any CMS base model. To activate the media library and use it together with the page module, it is best to first get the page module working with a few content types. Afterwards, add :mod:`feincms.module.medialibrary` to your ``INSTALLED_APPS`` setting, and -create a content type for a media file as follows: - -:: +create a content type for a media file as follows:: from feincms.module.page.models import Page - from feincms.content.medialibrary.models import MediaFileContent + from feincms.content.medialibrary.contents import MediaFileContent - Page.create_content_type(MediaFileContent, POSITION_CHOICES=( - ('block', _('block')), - ('left', _('left')), - ('right', _('right')), - )) + Page.create_content_type(MediaFileContent, TYPE_CHOICES=( + ('default', _('default')), + ('lightbox', _('lightbox')), + )) -The set of positions given here is just an example, you can define the methods -like you want. You should think about positions that make sense for different -media file types, however, because the choice cannot be customized per file -type. +``TYPE_CHOICES`` has nothing to do with file types -- it's about choosing +the presentation type for a certain media file, f.e. whether the media file +should be presented inline, in a lightbox, floated, or simply as a download +link. Configuration @@ -57,29 +54,34 @@ Rendering media file contents A set of recognition functions will be run on the file name to determine the file type. Using combinations of the name and type, the default render method tries to find a template for rendering the -:class:`~feincms.content.medialibrary.models.MediaFileContent`. - -The default set of pre-defined content types and recognition functions is: - -:: - - MediaFile.register_filetypes( - ( 'image', _('Image'), - lambda f: re.compile(r'\.(jpg|jpeg|gif|png)$', re.IGNORECASE).search(f) ), - ( 'pdf', _('PDF document'), lambda f: f.lower().endswith('.pdf') ), - ( 'txt', _('Text'), lambda f: f.lower().endswith('.txt') ), - ( 'other', _('Binary'), lambda f: True ), # Must be last - ) +:class:`~feincms.content.medialibrary.models.MediaFileBase`. + +The default set of pre-defined content types and recognition functions is:: + + MediaFileBase.register_filetypes( + ('image', _('Image'), lambda f: re.compile(r'\.(bmp|jpe?g|jp2|jxr|gif|png|tiff?)$', re.IGNORECASE).search(f)), + ('video', _('Video'), lambda f: re.compile(r'\.(mov|m[14]v|mp4|avi|mpe?g|qt|ogv|wmv)$', re.IGNORECASE).search(f)), + ('audio', _('Audio'), lambda f: re.compile(r'\.(au|mp3|m4a|wma|oga|ram|wav)$', re.IGNORECASE).search(f)), + ('pdf', _('PDF document'), lambda f: f.lower().endswith('.pdf')), + ('swf', _('Flash'), lambda f: f.lower().endswith('.swf')), + ('txt', _('Text'), lambda f: f.lower().endswith('.txt')), + ('rtf', _('Rich Text'), lambda f: f.lower().endswith('.rtf')), + ('zip', _('Zip archive'), lambda f: f.lower().endswith('.zip')), + ('doc', _('Microsoft Word'), lambda f: re.compile(r'\.docx?$', re.IGNORECASE).search(f)), + ('xls', _('Microsoft Excel'), lambda f: re.compile(r'\.xlsx?$', re.IGNORECASE).search(f)), + ('ppt', _('Microsoft PowerPoint'), lambda f: re.compile(r'\.pptx?$', re.IGNORECASE).search(f)), + ('other', _('Binary'), lambda f: True), # Must be last + ) You can add to that set by calling ``MediaFile.register_filetypes()`` with your new file types similar to the above. -If we've got an example file ``2009/06/foobar.jpg`` and a selected position of -``left``, the templates tried to render the media file are the following: +If we've got an example file ``2009/06/foobar.jpg`` and a presentation type of +``inline``, the templates tried to render the media file are the following: -* ``content/mediafile/image_left.html`` +* ``content/mediafile/image_inline.html`` * ``content/mediafile/image.html`` -* ``content/mediafile/left.html`` +* ``content/mediafile/inline.html`` * ``content/mediafile/default.html`` You are of course free to do with the file what you want inside the template, @@ -88,7 +90,7 @@ into an element that's floated to the left. Media file metadata -============================ +=================== Sometimes, just storing media files is not enough. You've got captions and copyrights which you'd like to store alongside the media file. This media @@ -98,3 +100,31 @@ therefore the copyright can only be entered once, not once per language. The default image template ``content/mediafile/image.html`` demonstrates how the values of those fields can be retrieved and used. + + +Using the media library in your own apps and content types +========================================================== + +There are a few helpers that allow you to have a nice raw_id selector and +thumbnail preview in your own apps and content types that have a ForeignKey to +:class:`~feincms.module.medialibrary.models.MediaFile`. + +To have a thumbnail preview in your ModelAdmin and Inline class:: + + from feincms.module.medialibrary.fields import MediaFileForeignKey + + class ImageForProject(models.Model): + project = models.ForeignKey(Project) + mediafile = MediaFileForeignKey( + MediaFile, related_name='+', + limit_choices_to={'type': 'image'}) + + +For the maginfying-glass select widget in your content type inherit your inline +from FeinCMSInline:: + + class MyContentInline(FeinCMSInline): + raw_id_fields = ('mediafile',) + + class MyContent(models.Model): + feincms_item_editor_inline = MyContentInline diff --git a/docs/migrations.rst b/docs/migrations.rst index e8d951307..ce17272c3 100644 --- a/docs/migrations.rst +++ b/docs/migrations.rst @@ -1,35 +1,36 @@ .. _migrations: -================================================= -Database migration support for FeinCMS with South -================================================= +====================================== +Database migration support for FeinCMS +====================================== -If you don't know what South_ is you should probably go and read about -it right now! -.. _South: http://south.aeracode.org/ +FeinCMS itself does not come with any migrations. It is recommended that you +add migrations for FeinCMS models yourself inside your project. -FeinCMS itself does not come with any migrations. It does not have to: Its -core models haven't changed for several versions now. This does not mean South -isn't supported! You are free to use South to manage FeinCMS' models which -is a very useful technique especially if you are using :ref:`page-extensions`. +Django's builtin migrations +=========================== -The following steps should be sufficient to get up and running with South -in your project: +This guide assumes that you are using both the page and the medialibrary +module from FeinCMS. Simply leave out medialibrary if unused. -* Put a copy of South somewhere on your ``PYTHONPATH``, with ``pip``, ``hg`` - or whatever pleases you most. -* Add ``'south'`` to ``INSTALLED_APPS``. -* Create a new folder in your app with an empty ``__init__.py`` file inside, - e.g. ``yourapp/migrate/``. -* Add the following configuration variable to your ``settings.py``:: +* Create a new folder named ``migrate`` in your app with an empty + ``__init__.py`` inside. +* Add the following configuration to your ``settings.py``:: - SOUTH_MIGRATION_MODULES = { - 'page': 'yourapp.migrate.page', - 'medialibrary': 'yourapp.migrate.medialibrary', # if you are using the medialibrary - # which comes with FeinCMS - } + MIGRATION_MODULES = { + 'page': 'yourapp.migrate.page', + 'medialibrary': 'yourapp.migrate.medialibrary', + } -* Run ``./manage.py convert_to_south page`` and ``./manage.py convert_to_south medialibrary`` -* That's it! +.. warning:: + + You **must not** use ``migrations`` as folder name for the FeinCMS + migrations, otherwise Django **will** get confused. + +* Create initial migrations and apply them:: + + ./manage.py makemigrations medialibrary + ./manage.py makemigrations page + ./manage.py migrate diff --git a/docs/page.rst b/docs/page.rst index 3842763e3..18e19c94d 100644 --- a/docs/page.rst +++ b/docs/page.rst @@ -10,7 +10,7 @@ FeinCMS is primarily a system to work with lists of content blocks which you can assign to arbitrary other objects. You do not necessarily have to use it with a hierarchical page structure, but that's the most common use case of course. Being able to put content together in small manageable -pieces is interesting for other uses too, i.e. for weblog entries where you +pieces is interesting for other uses too, e.g. for weblog entries where you have rich text content interspersed with images, videos or maybe even galleries. @@ -21,27 +21,29 @@ To activate the page module, you need to follow the instructions in :ref:`installation` and afterwards add :mod:`feincms.module.page` to your :data:`INSTALLED_APPS`. -Before proceeding with ``manage.py syncdb``, it might be a good idea to take +Before proceeding with ``manage.py makemigrations`` and ``./manage.py migrate``, +it might be a good idea to take a look at :ref:`page-extensions` -- the page module does have the minimum of features in the default configuration and you will probably want to enable several extensions. You need to create some content models too. No models are created by default, because there is no possibility to unregister models. A sane default might -be to create :class:`~feincms.content.image.models.ImageContent` and +be to create :class:`~feincms.content.medialibrary.models.MediaFileContent` and :class:`~feincms.content.richtext.models.RichTextContent` models; you can do this by adding the following lines somewhere into your project, for example in a -``models.py`` file that will be processed anyway: +``models.py`` file that will be processed anyway:: -:: - - from django.utils.translation import ugettext_lazy as _ + from django.utils.translation import gettext_lazy as _ from feincms.module.page.models import Page - from feincms.content.richtext.models import RichTextContent - from feincms.content.image.models import ImageContent + from feincms.contents import RichTextContent + from feincms.module.medialibrary.contents import MediaFileContent - Page.register_extensions('datepublisher', 'translations') # Example set of extensions + Page.register_extensions( + 'feincms.extensions.datepublisher', + 'feincms.extensions.translations' + ) # Example set of extensions Page.register_templates({ 'title': _('Standard template'), @@ -49,15 +51,14 @@ by adding the following lines somewhere into your project, for example in a 'regions': ( ('main', _('Main content area')), ('sidebar', _('Sidebar'), 'inherited'), - ), - }) + ), + }) Page.create_content_type(RichTextContent) - Page.create_content_type(ImageContent, POSITION_CHOICES=( - ('block', _('block')), - ('left', _('left')), - ('right', _('right')), - )) + Page.create_content_type(MediaFileContent, TYPE_CHOICES=( + ('default', _('default')), + ('lightbox', _('lightbox')), + )) It will be a good idea most of the time to register the @@ -67,24 +68,27 @@ content type dropdown will contain content types in the same order as they were registered. Please note that you should put these statements into a ``models.py`` file -which is executed at Django startup time, i.e. into a ``models.py`` file -contained in ``INSTALLED_APPS``. +of an app contained in ``INSTALLED_APPS``. That file is executed at Django startup time. Setting up the admin interface ============================== The customized admin interface code is contained inside the :class:`ModelAdmin` -subclass, so you do not need to do anything special here. You only need to set -:data:`~feincms.settings.FEINCMS_ADMIN_MEDIA` as described in the installation documentation. +subclass, so you do not need to do anything special here. If you use the :class:`~feincms.content.richtext.models.RichTextContent`, you -need to download `TinyMCE `_ and configure FeinCMS' +need to download `TinyMCE `_ and configure FeinCMS' richtext support:: FEINCMS_RICHTEXT_INIT_CONTEXT = { - 'TINYMCE_JS_URL': '/your_custom_path/tiny_mce.js', - } + 'TINYMCE_JS_URL': STATIC_URL + 'your_custom_path/tiny_mce.js', + } + +If you want to use a different admin site, or want to apply customizations to +the admin class used, add the following setting to your site-wide settings:: + + FEINCMS_USE_PAGE_ADMIN = False Wiring up the views @@ -94,9 +98,9 @@ Just add the following lines to your ``urls.py`` to get a catch-all URL pattern: :: - urlpatterns += patterns('', + urlpatterns += [ url(r'', include('feincms.urls')), - ) + ] If you want to define a page as home page for the whole site, you can give it @@ -124,10 +128,12 @@ attributes added. gallery = models.ForeignKey(Gallery) class Meta: - abstract = True + abstract = True # Required by FeinCMS, content types must be abstract def render(self, **kwargs): return render_to_string('gallery/gallerycontent.html', { + 'content': self, # Not required but a convention followed by + # all of FeinCMS' bundled content types 'images': self.gallery.image_set.order_by('?')[:5], }) @@ -156,28 +162,50 @@ cluttering up the core page model for those who do not need them. The extensions are standard python modules with a :func:`register` method which will be called upon registering the extension. The :func:`register` method receives the :class:`~feincms.module.page.models.Page` class itself and the model admin class -:class:`~feincms.module.page.models.PageAdmin` as arguments. The extensions can +:class:`~feincms.module.page.modeladmins.PageAdmin` as arguments. The extensions can be activated as follows:: - Page.register_extensions('navigation', 'titles', 'translations') + Page.register_extensions( + 'feincms.module.page.extensions.navigation', + 'feincms.module.page.extensions.titles', + 'feincms.extensions.translations') The following extensions are available currently: -* :mod:`~feincms.module.page.extension.changedate` --- Creation and modification dates +* :mod:`feincms.extensions.changedate` --- Creation and modification dates Adds automatically maintained creation and modification date fields to the page. -* :mod:`~feincms.module.page.extension.datepublisher` --- Date-based publishing +* :mod:`feincms.extensions.ct_tracker` --- Content type cache + + Helps reduce database queries if you have three or more content types by + caching in the database which content types are available on each page. + If this extension is used, ``Page._ct_inventory`` has to be nullified + after adding and/or removing content blocks, otherwise changes might not + be visible in the frontend. Saving the page instance accomplishes this. + + +* :mod:`feincms.extensions.datepublisher` --- Date-based publishing Adds publication date and end date fields to the page, thereby enabling the administrator to define a date range where a page will be available to website visitors. -* :mod:`~feincms.module.page.extension.navigation` --- Navigation extensions +* :mod:`feincms.page.extensions.excerpt` --- Page summary + + Add a brief excerpt summarizing the content of this page. + + +* :mod:`feincms.extensions.featured` --- Simple featured flag for a page + + Lets administrators set a featured flag that lets you treat that page special. + + +* :mod:`feincms.module.page.extensions.navigation` --- Navigation extensions Adds navigation extensions to the page model. You can define subclasses of ``NavigationExtension``, which provide submenus to the navigation generation @@ -185,28 +213,45 @@ The following extensions are available currently: this extension. -* :mod:`~feincms.module.page.extension.seo` --- Search engine optimization +* :mod:`feincms.module.page.extensions.navigationgroups` --- Navigation groups + + Adds a navigation group field to each page which can be used to distinguish + between the header and footer (or meta) navigation. Filtering is achieved + by passing the ``group`` argument to ``feincms_nav``. + + +* :mod:`feincms.module.page.extensions.relatedpages` --- Links related content + + Add a many-to-many relationship field to relate this page to other pages. + + +* :mod:`feincms.extensions.seo` --- Search engine optimization Adds fields to the page relevant for search engine optimization (SEO), currently only meta keywords and description. -* :mod:`~feincms.module.page.extension.symlinks` --- Symlinked content extension +* :mod:`feincms.module.page.extensions.sites` --- Limit pages to sites + + Allows to limit a page to a certain site and not display it on other sites. + + +* :mod:`feincms.module.page.extensions.symlinks` --- Symlinked content extension Sometimes you want to reuse all content from a page in another place. This extension lets you do that. -* :mod:`~feincms.module.page.extension.titles` --- Additional titles +* :mod:`feincms.module.page.extensions.titles` --- Additional titles Adds additional title fields to the page model. You may not only define a single title for the page to be used in the navigation, the tag and inside the content area, you are not only allowed to define different titles - for the three uses but also enabld to define titles and subtitles for the + for the three uses but also enabled to define titles and subtitles for the content area. -* :mod:`~feincms.module.page.extension.translations` --- Page translations +* :mod:`feincms.extensions.translations` --- Page translations Adds a language field and a recursive translations many to many field to the page, so that you can define the language the page is in and assign @@ -216,12 +261,19 @@ The following extensions are available currently: the Django i18n tools are initialized with the language given on the page object. + While it is not required by FeinCMS itself it's still recommended to add + :class:`django.middleware.locale.LocaleMiddleware` to the + ``MIDDLEWARE_CLASSES``; otherwise you will see strange language switching + behavior in non-FeinCMS managed views (such as third party apps not integrated + using :class:`feincms.content.application.models.ApplicationContent` or + Django's own administration tool). + You need to have defined ``settings.LANGUAGES`` as well. + .. note:: These extension modules add new fields to the ``Page`` class. If you add or - remove page extensions after you've run ``syncdb`` for the first time you - have to change the database schema yourself, or use :ref:`migrations`. + remove page extensions you make and apply new migrations. Using page request processors @@ -232,28 +284,35 @@ request as parameters and returns either None (or nothing) or a HttpResponse. All registered request processors are run before the page is actually rendered. If the request processor indeed returns a :class:`HttpResponse`, further rendering of the page is cut short and this response is returned immediately to the client. +It is also possible to raise an exception which will be handled like all exceptions +are handled in Django views. This allows for various actions dependent on page and request, for example a -simple user access check can be implemented like this: - -:: +simple user access check can be implemented like this:: def authenticated_request_processor(page, request): - if not request.user.is_authenticated(): - return HttpResponseForbidden() + if not request.user.is_authenticated: + raise django.core.exceptions.PermissionDenied - Page.register_request_processors(authenticated_request_processor) + Page.register_request_processor(authenticated_request_processor) + +``register_request_processor`` has an optional second argument named ``key``. +If you register a request processor with the same key, the second processor +replaces the first. This is especially handy to replace the standard request +processors named ``path_active`` (which checks whether all ancestors of +a given page are active too) and ``redirect`` (which issues HTTP-level redirects +if the ``redirect_to`` page field is filled in). Using page response processors ============================== -Analogous to a request processor, a reponse processor runs after a page +Analogous to a request processor, a response processor runs after a page has been rendered. It needs to accept the page, the request and the response as parameters and may change the response (or throw an exception, but try not to). -A reponse processor is the right place to tweak the returned http response +A response processor is the right place to tweak the returned http response for whatever purposes you have in mind. :: @@ -261,7 +320,41 @@ for whatever purposes you have in mind. def set_random_header_response_processor(page, request, response): response['X-Random-Number'] = 42 - Page.register_response_processors(set_random_header_response_processor) + Page.register_response_processor(set_random_header_response_processor) + +``register_response_processor`` has an optional second argument named ``key``, +exactly like ``register_request_processor`` above. It behaves in the same way. + + +WYSIWYG Editors +=============== + +TinyMCE 3 is configured by default to only allow for minimal formatting. This has proven +to be the best compromise between letting the client format text without destroying the +page design concept. You can customize the TinyMCE settings by creating your own +init_richtext.html that inherits from `admin/content/richtext/init_tinymce.html`. +You can even set your own CSS and linklist files like so:: + + FEINCMS_RICHTEXT_INIT_CONTEXT = { + 'TINYMCE_JS_URL': STATIC_URL + 'your_custom_path/tiny_mce.js', + 'TINYMCE_CONTENT_CSS_URL': None, # add your css path here + 'TINYMCE_LINK_LIST_URL': None # add your linklist.js path here + } + +FeinCMS is set up to use TinyMCE_ 3 but you can use CKEditor_ instead if you prefer +that one. Change the following settings:: + + FEINCMS_RICHTEXT_INIT_TEMPLATE = 'admin/content/richtext/init_ckeditor.html' + FEINCMS_RICHTEXT_INIT_CONTEXT = { + 'CKEDITOR_JS_URL': STATIC_URL + 'path_to_your/ckeditor.js', + } + +Alternatively, you can also use TinyMCE_ 4 by changing the following setting:: + + FEINCMS_RICHTEXT_INIT_TEMPLATE = 'admin/content/richtext/init_tinymce4.html' + +.. _TinyMCE: http://www.tinymce.com/ +.. _CKEditor: http://ckeditor.com/ ETag handling @@ -291,22 +384,18 @@ Sitemaps ======== To create a sitemap that is automatically populated with all pages in your -Feincms site, add the following to your top-level urls.py: - -:: +Feincms site, add the following to your top-level urls.py:: from feincms.module.page.sitemap import PageSitemap sitemaps = {'pages' : PageSitemap} - urlpatterns += patterns('', + urlpatterns += [ url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}), - ) + ] This will produce a default sitemap at the /sitemap.xml url. A sitemap can be -further customised by passing it appropriate parameters, like so: - -:: +further customised by passing it appropriate parameters, like so:: sitemaps = {'pages': PageSitemap(max_depth=2)} @@ -317,7 +406,12 @@ The following parameters can be used to modify the behaviour of the sitemap: in the site map. * ``max_depth`` -- if set to a non-negative integer, will limit the sitemap generated to this page hierarchy depth. -* ``changefreq`` -- should be a string or callable specifiying the page update frequency, +* ``changefreq`` -- should be a string or callable specifying the page update frequency, according to the sitemap protocol. - - +* ``queryset`` -- pass in a query set to restrict the Pages to include + in the site map. +* ``filter`` -- pass in a callable that transforms a queryset to filter + out the pages you want to include in the site map. +* ``extended_navigation`` -- if set to True, adds pages from any navigation + extensions. If using PagePretender, make sure to include title, url, + level, in_navigation and optionally modification_date. diff --git a/docs/releases/1.10.rst b/docs/releases/1.10.rst new file mode 100644 index 000000000..2c58d37d0 --- /dev/null +++ b/docs/releases/1.10.rst @@ -0,0 +1,125 @@ +========================== +FeinCMS 1.10 release notes +========================== + +Welcome to FeinCMS 1.10! + + +Full compatibility with the app-loading refactor in Django 1.7 +============================================================== + +FeinCMS 1.10 is compatible with Django 1.7. It shouldn't even be necessary +to change your FeinCMS-using code. + +The only change is that we cannot test for name clashes in content types +anymore. This only concerns you if you were using more than one +``feincms.models.Base`` subclass inside the same app using the same content +types. You should be using the ``class_name`` argument to +``create_content_type`` already anyway. The technical reason for this change +is that the name clash test requires the app registry to be ready (which it +isn't yet when creating content types). + +.. note:: + + If for some reason FeinCMS 1.10 does not work with Django 1.7 for you, + please provide a description of the problems you're seeing in the + `issue tracker <https://github.com/feincms/feincms/issues>`_. + + +TinyMCE 4.1 as default rich text configuration +============================================== + +FeinCMS now uses TinyMCE 4.1 as the default rich text editor instead of the +outdated 3.x line of releases. The previous version is still officially +supported as is CKEditor. Also, the TinyMCE configuration has been changed to +hide the menubar by default. + + +Home-grown schema checking support has been deactivated +======================================================= + +Not many words needed. Use South or Django 1.7's own migrations support +instead. + +This functionality has been deactivated without a deprecation period because +compared to real database migration solutions it looks more like a bug than a +feature. Also, it interfered with initial migrations -- the page table's +schema was introspected because of other ``post_syncdb`` signals before the +table itself even existed under certain circumstances, which meant that +migrations failed with the only way out being ``syncdb --all``. + +To continue using this functionality, set ``FEINCMS_CHECK_DATABASE_SCHEMA`` +to ``True``. The functionality will be completely removed in the next release. + + +A better mechanism for assigning pages to different menu bars +============================================================= + +The new extension :mod:`feincms.module.page.extensions.navigationgroups` +allows assigning pages to different regions, and asking ``feincms_nav`` to +only return pages belonging to one of those. + +The default configuration of the extension defines two regions, ``default`` +and ``footer``. This can be easily changed by subclassing the extension class +and overriding the ``groups`` attribute and registering this extension class +instead of the original. + +An example for getting a list of pages for the footer region follows:: + + {% load feincms_page_tags %} + {% feincms_nav feincms_page level=2 group='footer' as footer_nav %} + + +Backwards-incompatible changes +============================== + +Shouldn't have any. + + +Removal of deprecated features +------------------------------ + +None. + + +New deprecations +================ + +None. + + + +Notable features and improvements +================================= + +* The bundled versions of jQuery and jQuery UI have been updated to 1.11.1 + and 1.10.3 respectively. + +* ``feincms_languagelinks`` does not return page URLs leading to inactive + pages anymore. + +* The application content type form does not mysteriously forget values + anymore. + +* ``Page.get_original_translation`` returns the current page instead of + crashing if there is no original translation. + +* ``feincms_nav`` returns an empty list instead of crashing if the page + argument is ``None``. + +* Settings are documented again. + + +Bugfixes +======== + +* Bulk deletion of pages in the tree editor shouldn't lead to MPTT data + corruption anymore. This didn't happen often before either, but shouldn't + happen anymore at all. + + +Compatibility with Django and other apps +======================================== + +FeinCMS 1.10 requires Django 1.4 or better. The testsuite is successfully run +against Django 1.4, 1.5, 1.6 and the upcoming 1.7. diff --git a/docs/releases/1.11.rst b/docs/releases/1.11.rst new file mode 100644 index 000000000..9dff918cc --- /dev/null +++ b/docs/releases/1.11.rst @@ -0,0 +1,139 @@ +========================== +FeinCMS 1.11 release notes +========================== + +Welcome to FeinCMS 1.11! + + +Template inheritance with application contents +============================================== + +FeinCMS adds a decorator and a ``TemplateResponse`` subclass which can be +returned from apps embedded through ``ApplicationContent``. The template +response's template will override the template used by FeinCMS' main view and +the context will be merged. A selection of HTTP response headers +(currently *Cache-Control*, *Last-Modified* and *Expires*) will also be copied +to the main response. The following two examples are fully equivalent:: + + from django.template.response import TemplateResponse + from feincms.content.application.models import UnpackTemplateResponse + from feincms.views.decorators import unpack + + @unpack + def app_detail(request, ...): + return TemplateResponse(request, 'template.html', {...}) + + # or + + def app_detail(request, ...): + return UnpackTemplateResponse(request, 'template.html', {...}) + +The response class can also be easily used with Django's class-based views:: + + class MyListView(generic.ListView): + response_class = UnpackTemplateResponse + +This mechanism supersedes returning a tuple of ``(template_name, context)``. +This is still supported, but lacks the possibility to override HTTP response +headers. + + +Explicit definition of navigation extensions is now possible +============================================================ + +The auto-discovery of navigation extensions always was fragile and had to +happen before the navigation extension itself was registered with the page +class. This has been fixed; it's now possible to explicitly define the list +of navigation extensions which should be available:: + + from feincms.module.page.extensions import navigation + from feincms.module.page.models import Page + from myapp.navigation_extensions import MyappNavigationExtension + + class NavigationExtension(navigation.Extension): + navigation_extensions = [ + MyappNavigationExtension, + ] + + Page.register_extensions( + NavigationExtension, + ) + +The previous method has been deprecated and will stop working in future +versions of FeinCMS. + + +Backwards-incompatible changes +============================== + +* FeinCMS requires a minimum of Django 1.6. + +* The example project has been removed, because it did not really demonstrate + a best practices FeinCMS setup. A standard installation of FeinCMS will + often include additional libraries such as + `feincms-oembed <https://github.com/feincms/feincms-oembed>`_, + `form-designer <https://github.com/feincms/form_designer>`_ and additional + modules. + + +Removal of deprecated features +------------------------------ + +There were no deprecated features to be removed. + + +New deprecations +================ + +* ``RSSContent`` and ``update_rsscontent`` have been deprecated, those being + the only reason why ``FeinCMS`` depends on ``feedparser``. This will allow + us to remove this dependency. Users should switch to + `feincms-syndication <https://github.com/feincms/feincms-syndication>`_ + instead. + +* The automatic discovery of subclasses of ``NavigationExtension`` has been + replaced with an explicit mechanism of defining navigation extensions. + +* ``Page.cache_key`` has never been used by FeinCMS itself and will therefore + be removed in a future release. Comparable functionality has been available + for a long time with ``Page.path_to_cache_key``. + + +Notable features and improvements +================================= + +* Fix the inconsistent filtering of pages inside ``feincms_nav``. Navigation + extensions always came last, but the last release of FeinCMS added navigation + group filtering afterwards. This has been fixed. The workaround for the + previous behavior was to add the matching navigation group to page pretenders + as well. + +* Support for importing PIL as `import Image` has been removed. + +* The builtin and mostly broken frontend editing support has been removed. This + is not a decision against frontend editing / on site editing in general, it + is more about creating a space for new ideas and new implementations. + +* The home-grown schema checking support has been removed. Real migrations + should be used instead. + +* We are logging more stuff. + +* The admin CSS has been updated in preparation for Django's (hopefully!) + upcoming + `django-flat-theme <https://pypi.python.org/pypi/django-flat-theme>`_ merge. + + +Bugfixes +======== + +* ``{% feincms_nav %}`` now filters by navigation group before applying + navigation extensions for internal consistency. + +* ``{% page_is_active %}`` correctly handles page pretenders now. + + +Compatibility with Django and other apps +======================================== + +FeinCMS 1.11 requires Django 1.6 or better. diff --git a/docs/releases/1.12.rst b/docs/releases/1.12.rst new file mode 100644 index 000000000..4b41d3506 --- /dev/null +++ b/docs/releases/1.12.rst @@ -0,0 +1,118 @@ +========================== +FeinCMS 1.12 release notes +========================== + +Welcome to FeinCMS 1.12! + +.. warning:: + + This is a cleanup release. Lots of changes ahead! Please report problems + in our issue tracker on Github_! + +.. _Github: https://github.com/feincms/feincms/issues + + +Template content requires explicit list of templates +==================================================== + +The template refactor in Django removed the ability to enumerate +templates in template folders. Because of that templates must now +be explicitly specified when creating the content type:: + + Page.create_content_type(TemplateContent, TEMPLATES=[ + ('content/template/something1.html', 'something'), + ('content/template/something2.html', 'something else'), + ('base.html', 'makes no sense'), + ]) + +Also, you need to add a model migration which renames the old +``filename`` field to the new ``template`` field and prepends +``content/template/`` to all filenames:: + + # -*- coding: utf-8 -*- + from __future__ import unicode_literals + + from django.db import models, migrations + + + class Migration(migrations.Migration): + + dependencies = [ + ('page', 'WHATEVER IS APPROPRIATE'), + ] + + operations = [ + migrations.RenameField('TemplateContent', 'filename', 'template'), + migrations.RunSQL( + "UPDATE page_page_templatecontent" + " SET template='content/template/' || template;", + "UPDATE page_page_templatecontent" + " SET template=REPLACE(template, 'content/template/', '');" + ), + ] + + +The blog module has been completely removed +============================================ + +If you need a blog, have a look at Elephantblog_ instead. + +.. _Elephantblog: https://github.com/feincms/feincms-elephantblog + + +Caching of pages in various page manager methods has been removed +================================================================= + +Some methods such as `Page.objects.for_request` automatically cached +the page instances. This behavior lead to non-obvious problems and has +therefore been removed. + + +Backwards-incompatible changes +============================== + +* FeinCMS requires Django 1.7 or better. + +* Django has removed comments support a long time ago, which meant + that our bundled comments content in ``feincms.content.comments`` + was broken for some time. It has been completely removed. + +* ``feincms.content.rss`` has been removed, use ``feincms-syndication`` + instead. + + +Removal of deprecated features +------------------------------ + +South is not supported anymore? Django 1.7 and better only? + +``feincms.views.cbv`` has been removed. Use ``feincms.urls`` and +``feincms.views`` directly instead. + + +New deprecations +================ + +* None. + + +Notable features and improvements +================================= + +* Rich text cleaning using Tidy has been removed. + +* ``FEINCMS_JQUERY_NO_CONFLICT`` is gone. Either use ``django.jQuery`` or + ``feincms.jQuery`` explicitly. + +* Some support has been added for ``django-filer``. + +Bugfixes +======== + +* Too many to list. + + +Compatibility with Django and other apps +======================================== + +FeinCMS 1.12 requires Django 1.7 or better. diff --git a/docs/releases/1.13.rst b/docs/releases/1.13.rst new file mode 100644 index 000000000..a768743ce --- /dev/null +++ b/docs/releases/1.13.rst @@ -0,0 +1,51 @@ +========================== +FeinCMS 1.13 release notes +========================== + +Welcome to FeinCMS 1.13! + + +Compatibility with Django 1.10 +============================== + +The biggest feature of FeinCMS 1.13 is being compatible with Django 1.10 +and Django 1.11. + + +Backwards-incompatible changes +============================== + +* None. + + +Removal of deprecated features +------------------------------ + +* None. + + +New deprecations +================ + +* None. + + +Notable features and improvements +================================= + +* Some support has been added for ``django-filer``. + + +Bugfixes +======== + +* Too many to list. + + +Compatibility with Django and other apps +======================================== + +FeinCMS 1.13 requires Django 1.7 or better. + + +.. _django-mptt: https://github.com/django-mptt/django-mptt diff --git a/docs/releases/1.2.rst b/docs/releases/1.2.rst index 283a596f2..66b782905 100644 --- a/docs/releases/1.2.rst +++ b/docs/releases/1.2.rst @@ -31,7 +31,7 @@ FeinCMS 1.2 sports several large changes, including: * A new content type, ``TemplateContent`` has been added which can be used to render templates residing on the hard disk. -* The ``TreeEditor`` javascript code has been rewritten, reintroducing +* The ``TreeEditor`` JavaScript code has been rewritten, reintroducing drag-drop for reordering pages, but this time in a well-performing way not sluggish as before. diff --git a/docs/releases/1.4.rst b/docs/releases/1.4.rst index 58fc0f4fb..b44a41b6c 100644 --- a/docs/releases/1.4.rst +++ b/docs/releases/1.4.rst @@ -62,7 +62,7 @@ Apart from all these new features a few cleanups have been made: library have a look at ``MediaFile.reconfigure``. * Support for the ``show_on_top`` option for the ``ItemEditor`` has been - completely removed. This functionatliy has been deprecated since 1.2. + completely removed. This functionality has been deprecated since 1.2. * A few one-line Page manager methods which were too similar to each other have been deprecated. They will be removed in the next release of FeinCMS. diff --git a/docs/releases/1.5.rst b/docs/releases/1.5.rst new file mode 100644 index 000000000..f6e20e887 --- /dev/null +++ b/docs/releases/1.5.rst @@ -0,0 +1,130 @@ +========================= +FeinCMS 1.5 release notes +========================= + + +Explicit reversing of URLs from ``ApplicationContent``-embedded apps +==================================================================== + +URLs from third party apps embedded via ``ApplicationContent`` have +been traditionally made reversable by an ugly monkey patch of +``django.core.urlresolvers.reverse``. This mechanism has been deprecated +and will be removed in future FeinCMS versions. To reverse an URL +of a blog entry, instead of this:: + + # deprecated + from django.core.urlresolvers import reverse + reverse('blog.urls/blog_detail', kwargs={'year': 2011, 'slug': 'some-slug'}) + +do this:: + + from feincms.content.application.models import app_reverse + app_reverse('blog_detail', 'blog.urls', kwargs={'year': 2011, 'slug': 'some-slug'}) + +If you do not want to use the monkey patching behavior anymore, set +``FEINCMS_REVERSE_MONKEY_PATCH = False`` in your settings file. + +The new method is accessible inside a template too:: + + {% load applicationcontent_tags %} + + {# You have to quote the view name and the URLconf as in Django's future {% url %} tag. #} + {% app_reverse "blog_detail" "blog.urls" year=2011 slug='some-slug' %} + + +Inheritance 2.0 +=============== + +It's possible to use Django's template inheritance from third party +applications embedded through ``ApplicationContent`` too. To use this +facility, all you have to do is return a tuple consisting of the +template and the context. Instead of:: + + def my_view(request): + # ... + return render_to_response('template.html', {'object': ...}) + +simply use:: + + def my_view(request): + # ... + return 'template.html', {'object': ...} + +.. note:: + + ``template.html`` should extend a base template now. + + +Better support of ``InlineModelAdmin`` options for content types +================================================================ + +The ``FeinCMSInline`` only differs from a stock StackedInline in +differing defaults of ``form``, ``extra`` and ``fk_name``. All inline +options should be supported now, especially ``raw_id_fields`` and +``fieldsets``. + + + + +Minor changes +============= + +* The main CMS view is now based on Django's class-based generic + views. Inheritance 2.0 will not work with the old views. You don't + have to do anything if you use ``feincms.urls`` (as is recommended). + +* Request and response processors have been moved out of the + ``Page`` class into their own module, ``feincms.module.page.processors``. + They are still accessible for some time at the old place. + +* ``django.contrib.staticfiles`` is now a mandatory dependency for + the administration interface. + +* The ``active`` and ``in_navigation`` booleans on the ``Page`` + class now default to ``True``. + +* The minimum version requirements have changed. Django versions older than + 1.3 aren't supported anymore, django-mptt must be 0.4 upwards. + +* The mptt tree rebuilders have been removed; django-mptt offers tree + rebuilding functionality itself. + +* ``django-queryset-transform`` has been imported under ``feincms.utils`` + and is used for speeding up various aspects of the media library. The + prefilled attributes have been deprecated, because + ``django-queryset-transform`` can be used to do everything they did, + and better. + +* ``PageManager.active_filters`` has been converted from a list to a + ``SortedDict``. This means that replacing or removing individual + filters has become much easier than before. If you only used the + public methods for registering new filters you don't have to change + anything. + +* The same has been done with the request and response processors. + +* The ``TemplateContent`` has been changed to use the ``filesystem`` and + the ``app_directories`` template loaders directly. It can be used + together with the cached template loader now. + +* The tree editor has received a few usability fixes with (hopefully) + more to come. + +* ``Page.setup_request`` can be called repeatedly without harm now. + The return value of the first call is cached and returned on + subsequent calls which means that request processors are run + at most once. + +* Extensions such as ``translations`` and ``datepublisher`` which were + only usable with the page module have been made more generic and are + available for other FeinCMS-derived models too. + +* Media files from the medialibrary can be exported and imported in + bulk. + +* When creating a new translation of a page, content is only copied + from the original translation when the new page does not have any + content yet. Furthermore the user is notified that some content-copying + has happened. + +* A few bugs have been fixed. diff --git a/docs/releases/1.6.rst b/docs/releases/1.6.rst new file mode 100644 index 000000000..7980982b4 --- /dev/null +++ b/docs/releases/1.6.rst @@ -0,0 +1,202 @@ +========================= +FeinCMS 1.6 release notes +========================= + +Welcome to FeinCMS 1.6! + + +Backwards-incompatible changes +============================== + + +Reversing application content URLs +---------------------------------- + +The default value of ``FEINCMS_REVERSE_MONKEY_PATCH`` has been changed to +``False``. Support for monkey-patching the ``reverse()`` method to support +the old ``'urlconf/viewname'`` notation will be removed in the 1.7 release. + + +Improvements to the bundled file and image contents +--------------------------------------------------- + +* ``ImageContent``, ``FileContent`` and ``VideoContent`` now have pretty + icons out-of-the-box. + +* ``ImageContent`` now accepts optional ``FORMAT_CHOICES`` for use with + FeinCMS' bundled thumbnailers, as well as ``caption`` and ``alt_text`` fields. + + .. note:: + + If you are upgrading from an earlier version of FeinCMS, you'll have to + add the new database columns yourself or use a migration tool like South + to do it for you. Instructions for MySQL and the page module follow:: + + ALTER TABLE page_page_imagecontent ADD COLUMN `alt_text` varchar(255) NOT NULL; + ALTER TABLE page_page_imagecontent ADD COLUMN `caption` varchar(255) NOT NULL; + + If you want to use ``FORMAT_CHOICES``:: + + ALTER TABLE page_page_imagecontent ADD COLUMN `format` varchar(64) NOT NULL; + +* ``FileContent`` now displays the size of the file in the default template, + and uses ``span`` elements to allow styling of the title / size. + + +Removal of deprecated features +------------------------------ + +* Deprecated page manager methods have been removed. You should use + ``Page.objects.for_request`` instead of the following manager methods: + + * ``Page.objects.page_for_path_or_404()`` + * ``Page.objects.for_request_or_404()`` + * ``Page.objects.best_match_for_request()`` + * ``Page.objects.from_request()`` + +* Deprecated page methods have been removed: + + * ``Page.active_children()``: Use ``Page.children.active()`` instead. + * ``Page.active_children_in_navigation()``: Use + ``Page.children.in_navigation()`` instead. + * ``Page.get_siblings_and_self()``: You probably wanted + ``self.parent.children.active()`` or + ``self.get_siblings(include_self=True).active()`` anyway. + +* The shortcuts ``Page.register_request_processors()`` and + ``Page.register_response_processors()`` to register several request or response + processors at once have been removed in favor of their counterparts which + only allow one processor at a time, but allow for replacing FeinCMS' included + processors, ``require_path_active_request_processor`` and + ``redirect_request_processor``. + +* It is not possible anymore to access the request and response processors as + methods of the ``Page`` class. The processors are all in + ``feincms.module.page.processors`` now. + +* The deprecated support for prefilled attributes has been removed. Use + Django's own ``prefetch_related`` or ``feincms.utils.queryset_transform`` + instead. + +* The deprecated ``feincms.views.base`` module has been removed. The code has + been moved to ``feincms.views.legacy`` during the FeinCMS v1.5 cycle. + + +New deprecations +---------------- + +* The view decorator ``feincms.views.decorators.add_page_to_extra_context`` + has been deprecated as it was mostly used with function-based generic views, + which have been deprecated in Django as well. Use Django's class-based generic + views and the ``feincms.context_processors.add_page_if_missing`` context + processor if you need similar functionality instead. + +* The content type ``feincms.content.medialibrary.models.MediaFileContent`` has + been deprecated since FeinCMS v1.4. The whole module has been deprecated now + and will be replaced with the contents of ``feincms.content.medialibrary.v2`` + in FeinCMS v1.7. The ``v2`` module will stay around for another release or + two so that code using ``v2`` will continue working with FeinCMS v1.8 (at + least). + +* The template tag ``feincms_navigation`` has been superseded by ``feincms_nav`` + which fixes a few problems with the old code and is generally much more + maintainable. The old version will stay around for one more version and will + be removed for FeinCMS v1.8. The only difference (apart from the bugfixes and + the slightly different syntax) is that ``feincms_nav`` unconditionally uses + navigation extensions. Additionally, ``feincms_navigation`` uses + ``feincms_nav``'s implementation behind the scenes, which means that the + ``extended`` argument does not have an effect anymore (it's always active). + +* The HTML cleaning support in ``feincms.utils.html.cleanse`` which could be + easily used in the ``RichTextContent`` by passing ``cleanse=True`` has been + copied into its own Python package, + `feincms-cleanse <http://pypi.python.org/pypi/feincms-cleanse>`_. You should + start passing a callable to ``cleanse`` right now. The existing support for + cleansing will only be available up to FeinCMS v1.7. + +* FeinCMS v1.8 will not support shorthands anymore when registering extensions. + Always provide the full python path to the extension file (or pass callables) + to ``feincms.models.Base.register_extensions``. That is, + ``Page.register_extensions('feincms.module.extensions.ct_tracker')`` should + be used instead of ``Page.register_extensions('ct_tracker')``. While it is + a bit more work it will make it much more explicit what's going on. + + +Compatibility with Django and other apps +---------------------------------------- + +FeinCMS 1.6 requires Django 1.4. If you want to use django-reversion with FeinCMS +you have to use django-reversion 1.6 or newer. + + +Notable features and improvements +================================= + +* The bundled content types take additional steps to ensure that the main view + context is available in content types' templates. If you only use the rendering + tags (``feincms_render_region`` and ``feincms_render_content``) you can take + advantage of all variables from your context processors in content types' + templates too. Furthermore, those templatetags have been simplified by using + Django's ``template.Library.simple_tag`` method now, which means that filters + etc. are supported as template tag arguments now. + +* ``MediaFile`` does no longer auto-rotate images on upload. It really is not a + media library's job to magically modify user content; if needed, it should be + done in an image filter (like sorl). Also, reading through the image data + seems to have a side effect on some external storage engines which then would + only save half the image data, see issue #254. Additionally, FeinCMS does not + try anymore to detect whether uploaded files really are images, and only looks + at the file extension by default. We did not peek at the contents of other file + types either. + +* A new model field has been added, ``feincms.contrib.richtext.RichTextField``. + This is a drop-in replacement for Django's ``models.TextField`` with the + difference that it adds the CSS classes required by rich text fields in the + item editor. + +* The value of ``FEINCMS_FRONTEND_EDITING`` defaults to ``False`` now. + +* Frontend editing can now safely be used with caching. This is accomplished + by saving state in a cookie instead of creating sessions all the time. + +* The ``SectionContent`` content type has been updated and does properly + use ``raw_id_fields`` for the media files instead of the hack which was used + before. + +* It is now possible to specify a different function for generating thumbnails + in the media library administration. Set the setting + ``FEINCMS_MEDIALIBRARY_THUMBNAIL`` to a function taking a media file instance + and returning a URL to a thumbnail image or nothing if the file type cannot + be handled by the thumbnailer. + +* Thumbnails generated by the bundled ``|thumbnail`` and ``|cropscale`` template + filters are stored separately from the uploaded files now. This change means + that all thumbnails will be automatically regenerated after a FeinCMS update. + If you need the old behavior for some reason, set the setting + ``FEINCMS_THUMBNAIL_DIR`` to an empty string. The default setting is ``'_thumbs/'``. + +* All templates and examples have been converted to the new ``{% url %}`` + syntax. + +* Custom comment models are now supported in the ``CommentsContent``. + +* Media files are now removed from the disk too if a media file entry is + removed from the database. + +* The modules :mod:`feincms.module.page.models` and + :mod:`feincms.module.medialibrary.models` have been split up. Admin code has + been moved into ``modeladmin.py`` files, form code into ``forms.py``. + + +Bugfixes +======== + +* The core page methods support running with ``APPEND_SLASH = False`` now. + Many content types using forms do not, however. + +* The MPTT attributes aren't hardcoded in the tree editor anymore. Custom names + for the ``left``, ``right``, ``level`` and ``tree_id`` attributes are now + supported. Models which do not use ``id`` as their primary key are supported + now as well. + +* FeinCMS uses timezone-aware datetimes now. diff --git a/docs/releases/1.7.rst b/docs/releases/1.7.rst new file mode 100644 index 000000000..aa878d539 --- /dev/null +++ b/docs/releases/1.7.rst @@ -0,0 +1,147 @@ +========================= +FeinCMS 1.7 release notes +========================= + +Welcome to FeinCMS 1.7! + + +Extensions-mechanism refactor +============================= + +The extensions mechanism has been refactored to remove the need to make models +know about their related model admin classes. The new module +:py:mod:`feincms.extensions` contains mixins and base classes - their purpose +is as follows: :ref:`extensions`. + + +View code refactor +================== + +Made views, content type and request / response processors reusable. + +The legacy views at :py:mod:`feincms.views.legacy` were considered unhelpful +and were removed. + + +Backwards-incompatible changes +============================== + + +Page manager methods behavior +----------------------------- + +Previously, the following page manager methods sometimes returned inactive +objects or did not raise the appropriate (and asked for) +:py:class:`~django.http.Http404` exception: + +- ``Page.objects.page_for_path`` +- ``Page.objects.best_match_for_path`` +- ``Page.objects.for_request`` + +The reason for that was that only the page itself was tested for activity +in the manager method, and none of its ancestors. The check whether all +ancestors are active was only conducted later in a request processor. This +request processor was registered by default and was always run when +``Page.objects.for_request`` was called with ``setup=True``. + +However, request processors do not belong into the model layer. The necessity +of running code belonging to a request-response cycle to get the correct answer +from a manager method was undesirable. This has been rectified, those manager +methods check the ancestry directly. The now redundant request processor +``require_path_active_request_processor`` has been removed. + + +Reversing application content URLs +---------------------------------- + +The support for monkey-patching applicationcontent-awareness into Django's +:py:func:`django.core.urlresolvers.reverse` has been removed. + + +Removal of deprecated features +------------------------------ + +* The old media library content type module + :py:mod:`feincms.content.medialibrary.models` has been replaced with the + contents of :py:mod:`feincms.content.medialibrary.v2`. The model field + ``position`` has been renamed to ``type``, instead of ``POSITION_CHOICES`` + you should use ``TYPE_CHOICES`` now. The code has been simplified and + hacks to imitate ``raw_id_fields`` have been replaced by working stock + code. The ``v2`` module will stay around for another release and will be + removed in FeinCMS v1.8. The now-unused template + ``admin/content/mediafile/init.html`` has been deleted. + + +New deprecations +---------------- + +* ``Page.setup_request()`` does not do anything anymore and will be removed + in FeinCMS v1.8. + + +Notable features and improvements +================================= + +* A lazy version of :py:func:`~feincms.content.application.models.app_reverse` + is now available, + :py:func:`~feincms.content.application.models.app_reverse_lazy`. + +* Because of the extensions refactor mentioned above, all + ``register_extension`` methods have been removed. Additionally, the model + admin classes are not imported inside the ``models.py`` files anymore. + +* The setting ``FEINCMS_USE_PAGE_ADMIN`` can be set to false to prevent + registration of the page model with the administration. This is especially + useful if you only want to reuse parts of the page module. + +* Various classes in :py:mod:`feincms.module.page` do not hardcode the page + class anymore; hooks are provided to use your own models instead. Please + refer to the source for additional information. + +* ``Page.redirect_to`` can also contain the primary key of a page now, which + means that the redirect target stays correct even if the page URL changes. + +* Before, page content was copied automatically when creating a translation + of an existing page. This behavior can be deactivated by unchecking a + checkbox now. + +* Work has begun to make the page forms, model admin classes and managers + work with an abstract page model so that it will be easier to work with + several page models in a single Django site. + + +Bugfixes +======== + +* It should be possible to store FeinCMS models in a secondary database, as + long as the base model and all content types are stored in the same + database. + +* Changing templates in the item editor where the templates do not share + common regions does not result in orphaned content blocks anymore. + +* :py:func:`feincms.utils.get_object` knows how to import modules, not only + objects inside modules now. + +* The order and priority values for pages have been fixed when generating + sitemaps. + +* Various ``save`` and ``delete`` methods now come with ``alters_data=True`` + to prevent their use in templates. + +* Only one translation is permitted per language when using + :py:mod:`feincms.translations`. + +* FeinCMS can now be used without :py:mod:`django.contrib.sites`. + +* If the fieldset of a content inline has been customized, the fieldset is + not processed again to make sure that all form fields are actually shown. + If you use dynamically generated fields in a content inline such as the + application content does, you must not customize the fieldsets attribute + of the ``FeinCMSInline``. + + +Compatibility with Django and other apps +======================================== + +FeinCMS 1.7 requires Django 1.4 or better. diff --git a/docs/releases/1.8.rst b/docs/releases/1.8.rst new file mode 100644 index 000000000..d8867277b --- /dev/null +++ b/docs/releases/1.8.rst @@ -0,0 +1,136 @@ +========================= +FeinCMS 1.8 release notes +========================= + +Welcome to FeinCMS 1.8! + + +FeinCMS finally got continuous integration +========================================== + +Have a look at the status page here: + +`Travis CI <https://travis-ci.org/feincms/feincms>`_ + + +Preliminary Python 3.3 support +============================== + +The testsuite runs through on Python 3.3. + + +Singleton templates +=================== + +Templates can be defined to be singletons, which means that those templates +can only occur once on a whole site. The page module additionally allows +specifying that singleton templates must not have any children, and also +that the page cannot be deleted. + + +Dependencies are automatically installed +======================================== + +Now that distribute and setuptools have merged, ``setup.py`` has been +converted to use setuptools again which means that all dependencies +of FeinCMS should be installed automatically. + + +Backwards-incompatible changes +============================== + +* The template naming and order used in the section content has been changed + to be more similar to the media library. The naming is now + ``<mediafile type>_<section content type>``, additionally the media file type + is considered more important for template resolution than the section content + type. + +* The mechanism for finding the best application content match has been + massively simplified and also made customizable. The default implementation + of ``ApplicationContent.closest_match`` now only takes the current language + into account. + + +Removal of deprecated features +------------------------------ + +* The module ``feincms.admin.editor`` has been removed. Import the classes + from ``feincms.admin.item_editor`` or ``feincms.admin.tree_editor`` directly. + +* The HTML cleansing module :mod:`feincms.utils.html.cleanse` has been removed. + Use the standalone package + `feincms-cleanse <http://pypi.python.org/pypi/feincms-cleanse>`_ instead. + +* Registering extensions using shorthand notation is not possible anymore. + Always use the full python path to the extension module. + +* The two navigation template tags ``feincms_navigation`` and + ``feincms_navigation_extended`` have been removed. The improved + ``feincms_nav`` tag has been introduced with FeinCMS v1.6. + +* The function-based generic views in :mod:`feincms.views.generic` have been + removed. The decorator function + :func:`feincms.views.decorators.add_page_to_extra_context` is therefore + obsolete and has also been removed. + +* The old media library content type module + :py:mod:`feincms.content.medialibrary.models` has been replaced with the + contents of :py:mod:`feincms.content.medialibrary.v2`. The model field + ``position`` has been renamed to ``type``, instead of ``POSITION_CHOICES`` + you should use ``TYPE_CHOICES`` now. The code has been simplified and + hacks to imitate ``raw_id_fields`` have been replaced by working stock + code. The ``v2`` module will stay around for another release and will be + removed in FeinCMS v1.8. The now-unused template + ``admin/content/mediafile/init.html`` has been deleted. + +* ``Page.setup_request()`` has been removed because it has not been doing + anything for some time now. + + +New deprecations +================ + +* Page extensions should start explicitly adding their fields to the + administration interface using ``modeladmin.add_extension_options``. + FeinCMS v1.8 will warn about fields collected automatically, the next + release will not add unknown fields to the administration interface + anymore. + +* All extensions should inherit from ``feincms.extensions.Extension``. + Support for ``register(cls, admin_cls)``-style functions will be removed + in FeinCMS v1.9. + + +Notable features and improvements +================================= + +* The template tags ``feincms_render_region`` and ``feincms_render_content`` + do not require a request object anymore. If you omit the ``request`` + parameter, the request will not be passed to the ``render()`` methods. + +* The code is mostly `flake8 <https://pypi.python.org/pypi/flake8>`_ clean. + +* The new management command ``medialibrary_orphans`` can be used to find + files which aren't referenced in the media library anymore. + +* The test suite has been moved into its own top-level module. + + +Bugfixes +======== + +* The item and tree editor finally take Django permissions into account. + +* The datepublisher response processor should not crash during daylight + savings time changes anymore. + +* The undocumented attribute ``PageAdmin.unknown_fields`` has been removed + because it was modified at class level and not instance level which made + reuse harder than necessary. + + +Compatibility with Django and other apps +======================================== + +FeinCMS 1.8 requires Django 1.4 or better. The testsuite is successfully run +against Django 1.4, 1.5 and 1.6. Django 1.7 is not supported. diff --git a/docs/releases/1.9.rst b/docs/releases/1.9.rst new file mode 100644 index 000000000..b450ead3b --- /dev/null +++ b/docs/releases/1.9.rst @@ -0,0 +1,87 @@ +========================= +FeinCMS 1.9 release notes +========================= + +Welcome to FeinCMS 1.9! + + +Extensions in the item editor +============================= + +Extension fieldsets are now presented using a tabbed interface in the item +editor as well to raise their visibility. + + +A better way to mark pages as active in the navigation +====================================================== + +The new template tag ``{% page_is_active %}`` knows how to handle regular +pages and also page pretenders from navigation extensions. + +The following template snippet shows the recommended method of marking +pages as active:: + + {% feincms_nav feincms_page level=1 as toplevel %} + <ul> + {% for page in toplevel %} + {% page_is_active page as is_active %} + <li {% if is_active %}class="active"{% endif %}> + <a href="{{ page.get_navigation_url }}">{{ page.title }}</a> + <li> + {% endfor %} + </ul> + + +Backwards-incompatible changes +============================== + + +Removal of deprecated features +------------------------------ + +* The table content type has been removed. It has open bugs for more than two + years which weren't fixed, and now that we've moved on to newer versions of + jQuery, the table content didn't even load. + +* All extensions should inherit from ``feincms.extensions.Extension``. + The support for ``register(cls, admin_cls)``-style functions has been + removed. + +* Unknown page model fields (for example those added through page extensions) + aren't added to the administration interface anymore. Use + ``modeladmin.add_extension_options`` if you want extension fields to + appear. + +* The ``_feincms_extensions`` property on the page model (and on all models + inheriting ``ExtensionsMixin`` has been removed. It has been deprecated + since FeinCMS v1.7. + + +New deprecations +================ + + + +Notable features and improvements +================================= + +* The bundled versions of jQuery and jQuery UI have been updated to 1.9.1 + and 1.10.3 respectively. Custom confirmation boxes have been removed + and standard ones are used instead now. + +* The diff between 1.8 and 1.9 is large -- most of it because of coding style + cleanups. `flake8 <https://pypi.python.org/pypi/flake8>`_ runs should not + show any warnings. + +* Extension classes can be passed directly to ``register_extensions()`` now. + + +Bugfixes +======== + + +Compatibility with Django and other apps +======================================== + +FeinCMS 1.9 requires Django 1.4 or better. The testsuite is successfully run +against Django 1.4, 1.5 and 1.6. Django 1.7 is not supported. diff --git a/docs/settings.rst b/docs/settings.rst new file mode 100644 index 000000000..bc4f037ed --- /dev/null +++ b/docs/settings.rst @@ -0,0 +1,118 @@ +.. _settings: + +======== +Settings +======== + +FeinCMS has a few installation-wide settings which you might want to customize. + +The default settings can be found inside :mod:`feincms.default_settings`. +FeinCMS reads the settings from :mod:`feincms.settings` -- values should be +overridden by placing them in your project's settings. + + +Media library settings +====================== + +``FEINCMS_MEDIALIBRARY_UPLOAD_TO``: Defaults to ``medialibrary/%Y/%m``. Defines +the location of newly uploaded media files. + +``FEINCMS_MEDIALIBRARY_THUMBNAIL``: Defaults to +``feincms.module.medialibrary.thumbnail.default_admin_thumbnail``. The path to +a function which should return the URL to a thumbnail or ``None`` for the +mediafile instance passed as first argument. + +``FEINCMS_MEDIAFILE_OVERWRITE``: Defaults to ``False``. Set this to ``True`` +if uploads should replace previous files using the same path if possible. This +allows copy-pasted paths to work, but prevents using far future expiry headers +for media files. Also, it might not work with all storage backends. + + +Rich text settings +================== + +``FEINCMS_RICHTEXT_INIT_TEMPLATE``: Defaults to +``admin/content/richtext/init_tinymce4.html``. The template which contains the +initialization snippet for the rich text editor. Bundled templates are: + +* ``admin/content/richtext/init_tinymce.html`` for TinyMCE 3.x. +* ``admin/content/richtext/init_tinymce4.html`` for TinyMCE 4.x. +* ``admin/content/richtext/init_tinymce7.html`` for TinyMCE 7.x. +* ``admin/content/richtext/init_ckeditor.html`` for CKEditor. + +``FEINCMS_RICHTEXT_INIT_CONTEXT``: Defaults to +``{'TINYMCE_JS_URL': 'https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.9.11/tinymce.min.js'}``. +A dictionary which is passed to the template mentioned above. Please +refer to the templates directly to see all available variables. + + +Settings for the tree editor +============================ + +``FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS``: Defaults to ``False``. When this +setting is ``True``, the tree editor shows all objects on a single page, and +also shows all ancestors up to the roots in filtered lists. + + +``FEINCMS_TREE_EDITOR_OBJECT_PERMISSIONS``: Defaults to ``False``. Enables +checking of object level permissions. + + +Settings for the page module +============================ + +``FEINCMS_USE_PAGE_ADMIN``: Defaults to ``True``. The page model admin module +automatically registers the page model with the default admin site if this is +active. Set to ``False`` if you have to configure the page admin module +yourself. + +``FEINCMS_DEFAULT_PAGE_MODEL``: Defaults to ``page.Page``. The page model used +by :mod:`feincms.module.page`. + +``FEINCMS_ALLOW_EXTRA_PATH``: Defaults to ``False``. Activate this to allow +random gunk after a valid page URL. The standard behavior is to raise a 404 +if extra path elements aren't handled by a content type's ``process()`` method. + +``FEINCMS_TRANSLATION_POLICY``: Defaults to ``STANDARD``. How to switch +languages. + +* ``'STANDARD'``: The page a user navigates to sets the site's language + and overwrites whatever was set before. +* ``'EXPLICIT'``: The language set has priority, may only be overridden + by explicitely a language with ``?set_language=xx``. + +``FEINCMS_FRONTEND_LANGUAGES``: Defaults to None; set it to a list of allowed +language codes in the front end so to allow additional languages in the admin +back end for preparing those pages while not yet making the available to the +public. + +``FEINCMS_CMS_404_PAGE``: Defaults to ``None``. Set this if you want the page +handling mechanism to try and find a CMS page with that path if it encounters +a page not found situation. + +``FEINCMS_SINGLETON_TEMPLATE_CHANGE_ALLOWED``: Defaults to ``False``. Prevent +changing template within admin for pages which have been allocated a Template +with ``singleton=True`` -- template field will become read-only for singleton +pages. + +``FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED``: Defaults to ``False``. +Prevent admin page deletion for pages which have been allocated a Template with +``singleton=True``. + +``FEINCMS_MEDIAFILE_TRANSLATIONS``: Defaults to ``True``. Set to ``False`` if +you want FeinCMS to not translate ``MediaFile`` names, and instead just use the +filename directly. + + +Various settings +================ + +``FEINCMS_THUMBNAIL_DIR``: Defaults to ``_thumbs/``. Defines a prefix for media +file thumbnails. This allows you to easily remove all thumbnails without fear +of removing files belonging to image and file fields. + +``FEINCMS_THUMBNAIL_CACHE_TIMEOUT``: ``feincms_thumbnail`` template +filter library cache timeout. The default is to not cache anything for +backwards compatibility. If you use cloud storage AND +``feincms_thumbnail`` it is recommended to set the timeout to a large +value. diff --git a/docs/templatetags.rst b/docs/templatetags.rst index 2dfb24d82..42bb13e5b 100644 --- a/docs/templatetags.rst +++ b/docs/templatetags.rst @@ -54,19 +54,6 @@ the ``feincms_tags`` template tag library:: context = kwargs.get('context') -.. function:: feincms_prefill_entry_list: - - See :ref:`tools-utils-prefilledattributes`. - - :: - - {% load feincms_tags %} - - {% feincms_prefill_entry_list queryset "authors,richtextcontent_set" [region] %} - -.. function:: feincms_frontend_editing: - - Page module-specific template tags ================================== @@ -76,25 +63,57 @@ All page module-specific template tags are contained in ``feincms_page_tags``:: {% load feincms_page_tags %} -.. function:: feincms_navigation: +.. function:: feincms_nav: Return a list of pages to be used for the navigation level: 1 = toplevel, 2 = sublevel, 3 = sub-sublevel depth: 1 = only one level, 2 = subpages too + group: Only used with the ``navigationgroups`` extension If you set depth to something else than 1, you might want to look into - the tree_info template tag from the mptt_tags library. + the ``tree_info`` template tag from the mptt_tags library. Example:: {% load feincms_page_tags %} - {% feincms_navigation of feincms_page as sublevel level=2,depth=1 %} + {% feincms_nav feincms_page level=2 depth=1 as sublevel %} + {% for p in sublevel %} + <a href="{{ p.get_absolute_url }}">{{ p.title }}</a> + {% endfor %} + + Example for outputting only the footer navigation when using the + default configuration of the ``navigationgroups`` page extension:: + + {% load feincms_page_tags %} + + {% feincms_nav feincms_page level=2 depth=1 group='footer' as meta %} {% for p in sublevel %} <a href="{{ p.get_absolute_url }}">{{ p.title }}</a> {% endfor %} +.. function:: siblings_along_path_to: + + This is a filter designed to work in close conjunction with the + ``feincms_nav`` template tag describe above to build a + navigation tree following the path to the current page. + + Example:: + + {% feincms_nav feincms_page level=1 depth=3 as navitems %} + {% with navitems|siblings_along_path_to:feincms_page as navtree %} + {% recursetree navtree %} + * {{ node.short_title }} <br> + {% if children %} + <div style="margin-left: 20px">{{ children }}</div> + {% endif %} + {% endrecursetree %} + {% endwith %} + + For helper function converting a tree of pages into an HTML + representation please see the mptt_tags library's ``tree_info`` + and ``recursetree``. .. function:: feincms_parentlink: @@ -127,7 +146,7 @@ All page module-specific template tags are contained in ``feincms_page_tags``:: {% load feincms_page_tags %} - {% feincms_languagelinks for entry as links all,excludecurrent %} + {% feincms_languagelinks for feincms_page as links all,excludecurrent %} {% for key, name, link in links %} <a href="{% if link %}{{ link }}{% else %}/{{ key }}/{% endif %}">{% trans name %}</a> {% endfor %} @@ -182,18 +201,71 @@ All page module-specific template tags are contained in ``feincms_page_tags``:: {% load feincms_page_tags %} - {% feincms_navigation of feincms_page as main level=1 %} + {% feincms_nav feincms_page level=1 as main %} {% for entry in main %} <a {% if entry|is_equal_or_parent_of:feincms_page %}class="mark"{% endif %} href="{{ entry.get_absolute_url }}">{{ entry.title }}</a> {% endfor %} +.. function:: page_is_active: + + The advantage of ``page_is_active`` compared to the previous tags is that + it also nows how to handle page pretenders. If ``entry`` is a page + pretender, the template tag returns ``True`` if the current path starts + with the page pretender's path. If ``entry`` is a regular page, the logic + is the same as in ``is_equal_or_parent_of``. + + :: + + {% load feincms_page_tags %} + {% feincms_nav feincms_page level=1 as main %} + {% for entry in main %} + {% page_is_active entry as is_active %} + <a {% if is_active %}class="mark"{% endif %} + href="{{ entry.get_absolute_url }}">{{ entry.title }}</a> + {% endfor %} + + The values of ``feincms_page`` (the current page) and the current path + are pulled out of the context variables ``feincms_page`` and ``request``. + They can also be overriden if you so require:: + + {% page_is_active entry feincms_page=something path=request.path %} + + Application content template tags ================================= .. module:: feincms.templatetags.applicationcontent_tags: +.. function:: app_reverse: + + Returns an absolute URL for applications integrated with ApplicationContent + + The tag mostly works the same way as Django's own {% url %} tag:: + + {% load applicationcontent_tags %} + {% app_reverse "mymodel_detail" "myapp.urls" arg1 arg2 %} + + or:: + + {% load applicationcontent_tags %} + {% app_reverse "mymodel_detail" "myapp.urls" name1=value1 name2=value2 %} + + The first argument is a path to a view. The second argument is the URLconf + under which this app is known to the ApplicationContent. + + Other arguments are space-separated values that will be filled in place of + positional and keyword arguments in the URL. Don't mix positional and + keyword arguments. + + If you want to store the URL in a variable instead of showing it right away + you can do so too:: + + {% app_reverse "mymodel_detail" "myapp.urls" arg1 arg2 as url %} + + .. function:: fragment: .. function:: get_fragment: - See :ref:`integration-applicationcontent-morecontrol`. + Don't use those, read up on :ref:`integration-applicationcontent-inheritance20` + instead. diff --git a/docs/versioning.rst b/docs/versioning.rst index d79911dd7..b4374c9f1 100644 --- a/docs/versioning.rst +++ b/docs/versioning.rst @@ -12,25 +12,28 @@ with django-reversion_: * Add ``'reversion'`` to the list of installed applications. * Add ``'reversion.middleware.RevisionMiddleware'`` to ``MIDDLEWARE_CLASSES``. -* Call ``Page.register_with_reversion()`` after all content types have been - created (after all ``create_content_type`` invocations). +* Call ``Page.register_with_reversion(**kwargs)`` after all content types have been + created (after all ``create_content_type`` invocations). You can optionally + supply kwargs_ that will be passed to ``reversion.register()``. +* Add ``FEINCMS_USE_PAGE_ADMIN = False`` to your ``settings`` file. + +.. _kwargs: https://django-reversion.readthedocs.io/en/stable/api.html#registration-api Now, you need to create your own model admin subclass inheriting from both FeinCMS' ``PageAdmin`` and from reversions ``VersionAdmin``:: from django.contrib import admin - from feincms.module.page.models import Page, PageAdmin + from feincms.module.page.models import Page + from feincms.module.page.modeladmins import PageAdmin from reversion.admin import VersionAdmin - admin.site.unregister(Page) - class VersionedPageAdmin(PageAdmin, VersionAdmin): pass admin.site.register(Page, VersionedPageAdmin) The ``VersionedPageAdmin`` does not look like the ItemEditor -- it's -just raw Django inlines, without any additional javascript. Patches are +just raw Django inlines, without any additional JavaScript. Patches are welcome, but the basic functionality needed for versioning page content is there. @@ -43,3 +46,13 @@ Finally, you should ensure that initial revisions are created using You should ensure that you're using a reversion release which is compatible with your installed Django version. The reversion documentation contains an up-to-date list of compatible releases. + + The reversion support in FeinCMS requires at least django-reversion 1.6. + +.. note:: + + Only the Page module is versioned. MediaFiles are not. If a user deletes an + image from the MediaLibrary by accident it cannot be recovered. + Furthermore, if the position of a page has been moved within the tree, + the recovery will break the tree structure. + You can try to fix it using the ``rebuild_mptt`` command. diff --git a/example/README b/example/README deleted file mode 100644 index 4b33124e6..000000000 --- a/example/README +++ /dev/null @@ -1,3 +0,0 @@ -This is a really basic example how to use FeinCMS. - -Username/Password for the admin interface are admin and password. diff --git a/example/admin.py b/example/admin.py deleted file mode 100644 index 89fa569d7..000000000 --- a/example/admin.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.contrib import admin - -from feincms.admin import editor - -from example.models import Category - - -class CategoryAdmin(editor.TreeEditor): - list_display = ('name', 'slug') - list_filter = ('parent',) - prepopulated_fields = { - 'slug': ('name',), - } - -admin.site.register(Category, CategoryAdmin) diff --git a/example/blog_urls.py b/example/blog_urls.py deleted file mode 100644 index 9688cd7d2..000000000 --- a/example/blog_urls.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.conf.urls.defaults import * - -from feincms.module.blog.models import Entry - -info_dict = { - 'queryset': Entry.objects.all(), -} - -urlpatterns = patterns('', - url(r'^(?P<object_id>\d+)/', - 'feincms.views.generic.list_detail.object_detail', - info_dict, - name = 'blog_entry_details'), - url(r'^$', - 'feincms.views.generic.list_detail.object_list', - info_dict, - name = 'blog_entry_list'), -) \ No newline at end of file diff --git a/example/example.db b/example/example.db deleted file mode 100644 index 50bf3d9cd..000000000 Binary files a/example/example.db and /dev/null differ diff --git a/example/manage.py b/example/manage.py deleted file mode 100755 index 191a59a72..000000000 --- a/example/manage.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -import sys -sys.path.insert(0, '..') - -from django.core.management import execute_manager -try: - import settings # Assumed to be in the same directory. -except ImportError: - import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) - sys.exit(1) - -if __name__ == "__main__": - execute_manager(settings) diff --git a/example/models.py b/example/models.py deleted file mode 100644 index db3954216..000000000 --- a/example/models.py +++ /dev/null @@ -1,104 +0,0 @@ -from django import forms -from django.db import models -from django.utils.text import capfirst -from django.utils.translation import ugettext_lazy as _ - -from feincms.module.blog.models import Entry, EntryAdmin -from feincms.module.page.models import Page -from feincms.content.raw.models import RawContent -from feincms.content.image.models import ImageContent -from feincms.content.medialibrary.models import MediaFileContent -from feincms.content.application.models import ApplicationContent -from feincms.module.page.extensions.navigation import NavigationExtension, PagePretender -from feincms.content.application.models import reverse - -import mptt - - -Page.register_templates({ - 'key': 'base', - 'title': 'Base Template', - 'path': 'base.html', - 'regions': ( - ('main', 'Main region'), - ('sidebar', 'Sidebar', 'inherited'), - ), - }) -Page.create_content_type(RawContent) -MediaFileContent.default_create_content_type(Page) -Page.create_content_type(ImageContent, POSITION_CHOICES=( - ('default', 'Default position'), - )) - -def get_admin_fields(form, *args, **kwargs): - return { - 'exclusive_subpages': forms.BooleanField( - label=capfirst(_('exclusive subpages')), - required=False, - initial=form.instance.parameters.get('exclusive_subpages', False), - help_text=_('Exclude everything other than the application\'s content when rendering subpages.'), - ), - } - -Page.create_content_type(ApplicationContent, APPLICATIONS=( - ('blog_urls', 'Blog', {'admin_fields': get_admin_fields}), - ('whatever', 'Test Urls', {'urls': 'feincms.tests.applicationcontent_urls'}), - )) - - -Entry.register_regions( - ('main', 'Main region'), - ) -Entry.create_content_type(RawContent) -Entry.create_content_type(ImageContent, POSITION_CHOICES=( - ('default', 'Default position'), - )) - - -class BlogEntriesNavigationExtension(NavigationExtension): - """ - Extended navigation for blog entries. - - It would be added to 'Blog' page properties in admin. - """ - name = _('all blog entries') - - def children(self, page, **kwargs): - for entry in Entry.objects.all(): - yield PagePretender( - title=entry.title, - url=reverse('blog_urls/blog_entry_details', kwargs={'object_id': entry.id}), - ) - -Page.register_extensions('navigation') -Page.register_extensions('sites') - - -try: - from mptt.models import MPTTModel as base - mptt_register = False -except ImportError: - base = models.Model - mptt_register = True - -class Category(base): - name = models.CharField(max_length=20) - slug = models.SlugField() - parent = models.ForeignKey('self', blank=True, null=True, related_name='children') - - class Meta: - ordering = ['tree_id', 'lft'] - verbose_name = 'category' - verbose_name_plural = 'categories' - - def __unicode__(self): - return self.name - -if mptt_register: - mptt.register(Category) - -# add m2m field to entry so it shows up in entry admin -Entry.add_to_class('categories', models.ManyToManyField(Category, blank=True, null=True)) -EntryAdmin.list_filter += ('categories',) - - diff --git a/example/settings.py b/example/settings.py deleted file mode 100644 index f0d5a3e86..000000000 --- a/example/settings.py +++ /dev/null @@ -1,205 +0,0 @@ -# Django settings for example project. - -import os - -DEBUG = True -TEMPLATE_DEBUG = DEBUG - -ADMINS = ( - # ('Your Name', 'your_email@domain.com'), -) - -MANAGERS = ADMINS - -DATABASE_ENGINE = 'sqlite3' -DATABASE_NAME = os.path.join(os.path.dirname(__file__), 'example.db') - -DATABASES = {'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': DATABASE_NAME, -}} - -TIME_ZONE = 'America/Chicago' - -LANGUAGE_CODE = 'en-us' - -SITE_ID = int(os.environ.get('SITE_ID', 1)) - -USE_I18N = True - -MEDIA_ROOT = os.path.join(os.path.dirname(__file__), 'media/') -MEDIA_URL = '/media/' -STATIC_ROOT = os.path.join(os.path.dirname(__file__), 'static/') -STATIC_URL = '/static/' - -import django -if django.VERSION > (1, 4): - FEINCMS_ADMIN_MEDIA = '/static/feincms/' -else: - FEINCMS_ADMIN_MEDIA = '/feincms_media/' - ADMIN_MEDIA_PREFIX = '/static/admin/' - -SECRET_KEY = '_wn95s-apfd-442cby5m^_^ak6+5(fyn3lvnvtn7!si&o)1x^w' - -TEMPLATE_CONTEXT_PROCESSORS = ( - 'django.contrib.auth.context_processors.auth', - 'django.core.context_processors.debug', - 'django.core.context_processors.i18n', - 'django.core.context_processors.media', - 'django.core.context_processors.request', -) - -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', -) - -ROOT_URLCONF = 'example.urls' - -TEMPLATE_DIRS = ( -) - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.admin', - - 'feincms', - 'feincms.module.blog', - 'feincms.module.page', - 'feincms.module.medialibrary', - 'example', - - 'mptt', -) - -try: - # see http://nedbatchelder.com/code/coverage/ - import coverage - TEST_RUNNER = 'example.test_utils.CoverageRunner' - COVERAGE_MODULES = [ - 'feincms', - 'feincms._internal', - 'feincms.admin', - 'feincms.admin.editor', - 'feincms.admin.filterspecs', - 'feincms.admin.item_editor', - 'feincms.admin.tree_editor', - 'feincms.compat', - 'feincms.content', - 'feincms.content.application', - 'feincms.content.application.models', - 'feincms.content.comments', - 'feincms.content.comments.models', - 'feincms.content.contactform', - 'feincms.content.contactform.models', - 'feincms.content.file', - 'feincms.content.file.models', - 'feincms.content.image', - 'feincms.content.image.models', - 'feincms.content.medialibrary', - 'feincms.content.medialibrary.models', - 'feincms.content.raw', - 'feincms.content.raw.models', - 'feincms.content.richtext', - 'feincms.content.richtext.models', - 'feincms.content.rss', - 'feincms.content.rss.models', - 'feincms.content.section', - 'feincms.content.section.models', - 'feincms.content.table', - 'feincms.content.table.models', - 'feincms.content.template', - 'feincms.content.template.models', - 'feincms.content.video', - 'feincms.content.video.models', - 'feincms.context_processors', - 'feincms.contrib', - 'feincms.contrib.fields', - 'feincms.contrib.tagging', - 'feincms.default_settings', - #'feincms.management', - #'feincms.management.checker', - #'feincms.management.commands', - #'feincms.management.commands.feincms_validate', - #'feincms.management.commands.rebuild_mptt', - #'feincms.management.commands.rebuild_mptt_direct', - #'feincms.management.commands.update_rsscontent', - 'feincms.models', - 'feincms.module', - 'feincms.module.blog', - 'feincms.module.blog.admin', - 'feincms.module.blog.extensions', - 'feincms.module.blog.extensions.tags', - 'feincms.module.blog.extensions.translations', - 'feincms.module.blog.models', - 'feincms.module.extensions', - 'feincms.module.extensions.changedate', - 'feincms.module.extensions.ct_tracker', - 'feincms.module.extensions.featured', - 'feincms.module.extensions.seo', - 'feincms.module.medialibrary', - 'feincms.module.medialibrary.admin', - 'feincms.module.medialibrary.models', - 'feincms.module.page', - 'feincms.module.page.admin', - 'feincms.module.page.extensions', - 'feincms.module.page.extensions.datepublisher', - 'feincms.module.page.extensions.excerpt', - 'feincms.module.page.extensions.navigation', - 'feincms.module.page.extensions.relatedpages', - 'feincms.module.page.extensions.symlinks', - 'feincms.module.page.extensions.titles', - 'feincms.module.page.extensions.translations', - 'feincms.module.page.models', - 'feincms.module.page.sitemap', - 'feincms.module.page.templatetags', - 'feincms.module.page.templatetags.feincms_page_tags', - 'feincms.shortcuts', - 'feincms.templatetags', - 'feincms.templatetags.applicationcontent_tags', - 'feincms.templatetags.feincms_admin_tags', - 'feincms.templatetags.feincms_compat_tags', - 'feincms.templatetags.feincms_tags', - 'feincms.templatetags.feincms_thumbnail', - 'feincms.templatetags.fragment_tags', - 'feincms.templatetags.utils', - 'feincms.translations', - 'feincms.urls', - 'feincms.utils', - 'feincms.utils.html', - 'feincms.utils.html.cleanse', - 'feincms.utils.html.tidy', - 'feincms.utils.templatetags', - 'feincms.views', - 'feincms.views.base', - #'feincms.views.cbv', # Makes test-suite only runnable with Django 1.3 - #'feincms.views.cbv.urls', - #'feincms.views.cbv.views', - 'feincms.views.decorators', - 'feincms.views.generic', - 'feincms.views.generic.create_update', - 'feincms.views.generic.date_based', - 'feincms.views.generic.list_detail', - 'feincms.views.generic.simple', - ] -except ImportError: - # run without coverage support - pass - -LANGUAGES = ( - ('en', 'English'), - ('de', 'German'), - ) - -FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS = True diff --git a/example/templates/base.html b/example/templates/base.html deleted file mode 100644 index 41f991268..000000000 --- a/example/templates/base.html +++ /dev/null @@ -1,118 +0,0 @@ -{% load applicationcontent_tags feincms_tags feincms_page_tags %} -<html> -<head> - <title>{{ feincms_page.title }} - - - -

{{ feincms_page.title }}

- - - -
-
-

Main content

- {% block content %}{% feincms_render_region feincms_page "main" request %}{% endblock %} -
- - -
- - {% feincms_frontend_editing feincms_page request %} - - {% get_fragment request "something" %} - - diff --git a/example/templates/blog/entry_detail.html b/example/templates/blog/entry_detail.html deleted file mode 100644 index 65b6d4b8d..000000000 --- a/example/templates/blog/entry_detail.html +++ /dev/null @@ -1,14 +0,0 @@ -{% load i18n %} -{% load feincms_tags %} - -
- -

{{object.title}}

- - {% feincms_render_region object "main" request %} - -
- -

- {% trans "All entries" %} -

\ No newline at end of file diff --git a/example/templates/blog/entry_list.html b/example/templates/blog/entry_list.html deleted file mode 100644 index 39cba172b..000000000 --- a/example/templates/blog/entry_list.html +++ /dev/null @@ -1,6 +0,0 @@ -{% for object in object_list %} - -

{{ object.title }}

- - -{% endfor %} \ No newline at end of file diff --git a/example/templates/feincms_base.html b/example/templates/feincms_base.html deleted file mode 100644 index e69de29bb..000000000 diff --git a/example/test_utils.py b/example/test_utils.py deleted file mode 100644 index 412540d0b..000000000 --- a/example/test_utils.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -from django.conf import settings -from django.test.simple import DjangoTestSuiteRunner - -import coverage - - -class CoverageRunner(DjangoTestSuiteRunner): - def run_tests(self, *args, **kwargs): - run_with_coverage = hasattr(settings, 'COVERAGE_MODULES') - - if run_with_coverage: - coverage.use_cache(0) - coverage.start() - - result = super(CoverageRunner, self).run_tests(*args, **kwargs) - - if run_with_coverage: - coverage.stop() - print '' - print '----------------------------------------------------------------------' - print ' Unit Test Code Coverage Results' - print '----------------------------------------------------------------------' - coverage_modules = [] - for module in settings.COVERAGE_MODULES: - coverage_modules.append(__import__(module, globals(), - locals(), [''])) - coverage.report(coverage_modules, show_missing=1) - print '----------------------------------------------------------------------' - - return result diff --git a/example/urls.py b/example/urls.py deleted file mode 100644 index b25d6d764..000000000 --- a/example/urls.py +++ /dev/null @@ -1,40 +0,0 @@ -import os - -from django.conf.urls.defaults import * - -from django.contrib import admin -admin.autodiscover() - -urlpatterns = patterns('', - # Example: - # (r'^example/', include('example.foo.urls')), - - # This avoids breaking Django admin's localization JavaScript when using - # the FeinCMS frontend editing: - url(r'admin/page/page/jsi18n/', 'django.views.generic.simple.redirect_to', {'url': '/admin/jsi18n/'}), - - # Uncomment the admin/doc line below and add 'django.contrib.admindocs' - # to INSTALLED_APPS to enable admin documentation: - # (r'^admin/doc/', include('django.contrib.admindocs.urls')), - - #(r'^admin/', include(admin.site.urls)), - (r'^admin/', include(admin.site.urls) ), - - (r'^feincms_media/(?P.*)$', 'django.views.static.serve', - {'document_root': os.path.join(os.path.dirname(os.path.dirname(__file__)), 'feincms/static/feincms/')}), - - (r'^media/(?P.*)$', 'django.views.static.serve', - {'document_root': os.path.join(os.path.dirname(__file__), 'media/')}), -) - -import django -if django.VERSION > (1, 3): - urlpatterns += patterns('', (r'', include('feincms.views.cbv.urls'))) -else: - urlpatterns += patterns('', (r'', include('feincms.urls'))) - -try: - from django.contrib.staticfiles.urls import staticfiles_urlpatterns - urlpatterns += staticfiles_urlpatterns() -except ImportError: - pass diff --git a/feincms/__init__.py b/feincms/__init__.py index c53879862..8cd0f3219 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,47 +1,25 @@ -VERSION = (1, 4, 0) -__version__ = '.'.join(map(str, VERSION)) +VERSION = (25, 5, 1) +__version__ = ".".join(map(str, VERSION)) -class LazySettings(object): +class LazySettings: def _load_settings(self): - from feincms import default_settings from django.conf import settings as django_settings + from feincms import default_settings + for key in dir(default_settings): - if not key.startswith(('FEINCMS_', '_HACK_')): + if not key.startswith("FEINCMS_"): continue - setattr(self, key, getattr(django_settings, key, - getattr(default_settings, key))) + value = getattr(default_settings, key) + value = getattr(django_settings, key, value) + setattr(self, key, value) def __getattr__(self, attr): self._load_settings() del self.__class__.__getattr__ return self.__dict__[attr] -settings = LazySettings() - - -COMPLETELY_LOADED = False -def ensure_completely_loaded(): - """ - This method ensures all models are completely loaded - FeinCMS requires Django to be completely initialized before proceeding, - because of the extension mechanism and the dynamically created content - types. - - For more informations, have a look at issue #23 on github: - http://github.com/feincms/feincms/issues#issue/23 - """ - - global COMPLETELY_LOADED - if COMPLETELY_LOADED: - return True - - from django.core.management.validation import get_validation_errors - from StringIO import StringIO - get_validation_errors(StringIO(), None) - - COMPLETELY_LOADED = True - return True +settings = LazySettings() diff --git a/feincms/_internal.py b/feincms/_internal.py index 8e13f2594..ab72b9e17 100644 --- a/feincms/_internal.py +++ b/feincms/_internal.py @@ -4,6 +4,8 @@ http://mail.python.org/pipermail/python-dev/2008-January/076194.html """ +__all__ = ("monkeypatch_method", "monkeypatch_property") + def monkeypatch_method(cls): """ @@ -17,6 +19,7 @@ def (self, [...]): def decorator(func): setattr(cls, func.__name__, func) return func + return decorator @@ -32,24 +35,5 @@ def (self, [...]): def decorator(func): setattr(cls, func.__name__, property(func)) return func - return decorator - -def monkeypatch_class(name, bases, namespace): - """ - A metaclass to add a number of methods (or other attributes) to an - existing class, using a convenient class notation:: - - class (): - __metaclass__ = monkeypatch_class - def (...): ... - def (...): ... - ... - """ - - assert len(bases) == 1, "Exactly one base class required" - base = bases[0] - for name, value in namespace.iteritems(): - if name != "__metaclass__": - setattr(base, name, value) - return base + return decorator diff --git a/feincms/admin/__init__.py b/feincms/admin/__init__.py index e69de29bb..bb45c8662 100644 --- a/feincms/admin/__init__.py +++ b/feincms/admin/__init__.py @@ -0,0 +1,15 @@ +from django.contrib.admin.filters import FieldListFilter + +from .filters import CategoryFieldListFilter, ParentFieldListFilter + + +FieldListFilter.register( + lambda f: getattr(f, "parent_filter", False), + ParentFieldListFilter, + take_priority=True, +) +FieldListFilter.register( + lambda f: getattr(f, "category_filter", False), + CategoryFieldListFilter, + take_priority=True, +) diff --git a/feincms/admin/editor.py b/feincms/admin/editor.py deleted file mode 100644 index 9c9d30f8b..000000000 --- a/feincms/admin/editor.py +++ /dev/null @@ -1,3 +0,0 @@ -from feincms.admin.item_editor import ItemEditor, ItemEditorForm -from feincms.admin.tree_editor import TreeEditor, ajax_editable_boolean, \ - ajax_editable_boolean_cell, django_boolean_icon diff --git a/feincms/admin/filters.py b/feincms/admin/filters.py new file mode 100644 index 000000000..7ae5bdc5a --- /dev/null +++ b/feincms/admin/filters.py @@ -0,0 +1,120 @@ +# Thanks to http://www.djangosnippets.org/snippets/1051/ +# +# Authors: Marinho Brandao +# Guilherme M. Gondim (semente) + + +from operator import itemgetter + +import django +from django.contrib.admin.filters import ChoicesFieldListFilter +from django.db.models import Count +from django.utils.encoding import smart_str +from django.utils.safestring import mark_safe +from django.utils.translation import gettext as _ + +from feincms.utils import shorten_string + + +class ParentFieldListFilter(ChoicesFieldListFilter): + """ + Improved list_filter display for parent Pages by nicely indenting hierarchy + + In theory this would work with any mptt model which uses a "title" + attribute. + + my_model_field.page_parent_filter = True + """ + + def __init__(self, field, request, params, model, model_admin, field_path=None): + super().__init__(field, request, params, model, model_admin, field_path) + + parent_ids = ( + model.objects.exclude(parent=None) + .values_list("parent__id", flat=True) + .order_by("parent__id") + .distinct() + ) + parents = model.objects.filter(pk__in=parent_ids).values_list( + "pk", "title", "level" + ) + self.lookup_choices = [ + ( + pk, + "{}{}".format( + "  " * level, shorten_string(title, max_length=25) + ), + ) + for pk, title, level in parents + ] + + def choices(self, changelist): + yield { + "selected": self.lookup_val is None, + "query_string": changelist.get_query_string({}, [self.lookup_kwarg]), + "display": _("All"), + } + + # Pre Django 5 lookup_val would be a scalar, now it can do multiple + # selections and thus is a list. Deal with that. + lookup_vals = self.lookup_val + if lookup_vals is not None and django.VERSION < (5,): + lookup_vals = [lookup_vals] + + for pk, title in self.lookup_choices: + yield { + "selected": lookup_vals is not None and str(pk) in lookup_vals, + "query_string": changelist.get_query_string({self.lookup_kwarg: pk}), + "display": mark_safe(smart_str(title)), + } + + def title(self): + return _("Parent") + + +class CategoryFieldListFilter(ChoicesFieldListFilter): + """ + Customization of ChoicesFilterSpec which sorts in the user-expected format + + my_model_field.category_filter = True + """ + + def __init__(self, field, *args, **kwargs): + super().__init__(field, *args, **kwargs) + + # Restrict results to categories which are actually in use: + related_model = field.remote_field.model + related_name = field.related_query_name() + + self.lookup_choices = sorted( + ( + (i.pk, f"{i} ({i._related_count})") + for i in related_model.objects.annotate( + _related_count=Count(related_name) + ).exclude(_related_count=0) + ), + key=itemgetter(1), + ) + + def choices(self, changelist): + yield { + "selected": self.lookup_val is None, + "query_string": changelist.get_query_string({}, [self.lookup_kwarg]), + "display": _("All"), + } + + # Pre Django 5 lookup_val would be a scalar, now it can do multiple + # selections and thus is a list. Deal with that. + lookup_vals = self.lookup_val + if lookup_vals is not None and django.VERSION < (5,): + lookup_vals = [lookup_vals] + + for pk, title in self.lookup_choices: + yield { + "selected": lookup_vals is not None and str(pk) in lookup_vals, + "query_string": changelist.get_query_string({self.lookup_kwarg: pk}), + "display": mark_safe(smart_str(title)), + } + + def title(self): + return _("Category") diff --git a/feincms/admin/filterspecs.py b/feincms/admin/filterspecs.py deleted file mode 100644 index 872960f3a..000000000 --- a/feincms/admin/filterspecs.py +++ /dev/null @@ -1,113 +0,0 @@ -# encoding=utf-8 -# Thanks to http://www.djangosnippets.org/snippets/1051/ -# -# Authors: Marinho Brandao -# Guilherme M. Gondim (semente) - -try: - from django.contrib.admin.filters import FieldListFilter, ChoicesFieldListFilter - legacy = False -except ImportError: # Django up to 1.3 - from django.contrib.admin.filterspecs import ( - FilterSpec as FieldListFilter, - ChoicesFilterSpec as ChoicesFieldListFilter) - legacy = True - -from django.utils.encoding import smart_unicode -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext as _ - - -class ParentFieldListFilter(ChoicesFieldListFilter): - """ - Improved list_filter display for parent Pages by nicely indenting hierarchy - - In theory this would work with any mptt model which uses a "title" attribute. - - my_model_field.page_parent_filter = True - """ - - def __init__(self, f, request, params, model, model_admin, field_path=None): - from feincms.utils import shorten_string - - try: - super(ParentFieldListFilter, self).__init__(f, request, params, model, model_admin, field_path) - except TypeError: # Django 1.2 - super(ParentFieldListFilter, self).__init__(f, request, params, model, model_admin) - - parent_ids = model.objects.exclude(parent=None).values_list("parent__id", flat=True).order_by("parent__id").distinct() - parents = model.objects.filter(pk__in=parent_ids).values_list("pk", "title", "level") - self.lookup_choices = [(pk, "%s%s" % (" " * level, shorten_string(title, max_length=25))) for pk, title, level in parents] - - def choices(self, cl): - yield { - 'selected': self.lookup_val is None, - 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), - 'display': _('All') - } - - for pk, title in self.lookup_choices: - yield { - 'selected': pk == int(self.lookup_val or '0'), - 'query_string': cl.get_query_string({self.lookup_kwarg: pk}), - 'display': mark_safe(smart_unicode(title)) - } - - def title(self): - return _('Parent') - -class CategoryFieldListFilter(ChoicesFieldListFilter): - """ - Customization of ChoicesFilterSpec which sorts in the user-expected format - - my_model_field.category_filter = True - """ - - def __init__(self, f, request, params, model, model_admin, field_path=None): - try: - super(CategoryFieldListFilter, self).__init__(f, request, params, model, model_admin, field_path) - except TypeError: # Django 1.2 - super(CategoryFieldListFilter, self).__init__(f, request, params, model, model_admin) - - # Restrict results to categories which are actually in use: - self.lookup_choices = [ - (i.pk, unicode(i)) for i in f.related.parent_model.objects.exclude(**{ - f.related.var_name: None - }) - ] - self.lookup_choices.sort(key=lambda i: i[1]) - - def choices(self, cl): - yield { - 'selected': self.lookup_val is None, - 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), - 'display': _('All') - } - - for pk, title in self.lookup_choices: - yield { - 'selected': pk == int(self.lookup_val or '0'), - 'query_string': cl.get_query_string({self.lookup_kwarg: pk}), - 'display': mark_safe(smart_unicode(title)) - } - - def title(self): - return _('Category') - - -if legacy: - # registering the filter - FieldListFilter.filter_specs.insert(0, - (lambda f: getattr(f, 'parent_filter', False), ParentFieldListFilter) - ) - - FieldListFilter.filter_specs.insert(1, - (lambda f: getattr(f, 'category_filter', False), CategoryFieldListFilter) - ) -else: - FieldListFilter.register(lambda f: getattr(f, 'parent_filter', False), - ParentFieldListFilter, - take_priority=True) - FieldListFilter.register(lambda f: getattr(f, 'category_filter', False), - CategoryFieldListFilter, - take_priority=True) diff --git a/feincms/admin/item_editor.py b/feincms/admin/item_editor.py index d48df6f0b..8638c9089 100644 --- a/feincms/admin/item_editor.py +++ b/feincms/admin/item_editor.py @@ -1,29 +1,27 @@ # ------------------------------------------------------------------------ -# coding=utf-8 # ------------------------------------------------------------------------ -import re + import copy +import logging +import warnings -from django import forms, template -from django.contrib import admin -from django.core.exceptions import ImproperlyConfigured -from django.db.models import loading -from django.forms.models import modelform_factory -from django.http import Http404 -from django.shortcuts import render_to_response -from django.utils.encoding import force_unicode -from django.utils.functional import curry -from django.utils.translation import ugettext as _ +from django import forms from django.contrib.admin.options import InlineModelAdmin +from django.contrib.admin.utils import unquote +from django.contrib.auth import get_permission_codename +from django.http import Http404 -from feincms import settings, ensure_completely_loaded +from feincms.extensions import ExtensionModelAdmin from feincms.signals import itemeditor_post_save_related + # ------------------------------------------------------------------------ -FRONTEND_EDITING_MATCHER = re.compile(r'(\d+)\|(\w+)\|(\d+)') -FEINCMS_CONTENT_FIELDSET_NAME = 'FEINCMS_CONTENT' -FEINCMS_CONTENT_FIELDSET = (FEINCMS_CONTENT_FIELDSET_NAME, {'fields': ()}) +FEINCMS_CONTENT_FIELDSET_NAME = "FEINCMS_CONTENT" +FEINCMS_CONTENT_FIELDSET = (FEINCMS_CONTENT_FIELDSET_NAME, {"fields": ()}) + +logger = logging.getLogger(__name__) + # ------------------------------------------------------------------------ class ItemEditorForm(forms.ModelForm): @@ -35,6 +33,7 @@ class ItemEditorForm(forms.ModelForm): region = forms.CharField(widget=forms.HiddenInput()) ordering = forms.IntegerField(widget=forms.HiddenInput()) + # ------------------------------------------------------------------------ class FeinCMSInline(InlineModelAdmin): """ @@ -43,11 +42,12 @@ class FeinCMSInline(InlineModelAdmin): form = ItemEditorForm extra = 0 - fk_name = 'parent' - template = 'admin/feincms/content_inline.html' + fk_name = "parent" + template = "admin/feincms/content_inline.html" + # ------------------------------------------------------------------------ -class ItemEditor(admin.ModelAdmin): +class ItemEditor(ExtensionModelAdmin): """ The ``ItemEditor`` is a drop-in replacement for ``ModelAdmin`` with the speciality of knowing how to work with :class:`feincms.models.Base` @@ -57,201 +57,154 @@ class ItemEditor(admin.ModelAdmin): the standard ``ModelAdmin`` class. """ - def __init__(self, model, admin_site): - ensure_completely_loaded() + def get_inline_instances(self, request, *args, **kwargs): + inline_instances = super().get_inline_instances(request, *args, **kwargs) + self.append_feincms_inlines(inline_instances, request) + return inline_instances - super(ItemEditor, self).__init__(model, admin_site) - - # Add inline instances for FeinCMS content inlines - for inline_class in self.get_feincms_inlines(model): + def append_feincms_inlines(self, inline_instances, request): + """ + Append generated FeinCMS content inlines to native django inlines. + """ + for inline_class in self.get_feincms_inlines(self.model, request): inline_instance = inline_class(self.model, self.admin_site) - self.inline_instances.append(inline_instance) + inline_instances.append(inline_instance) + + def can_add_content(self, request, content_type): + perm = ".".join( + ( + content_type._meta.app_label, + get_permission_codename("add", content_type._meta), + ) + ) + return request.user.has_perm(perm) + + def get_feincms_inlines(self, model, request): + """Generate genuine django inlines for registered content types.""" + model._needs_content_types() - def get_feincms_inlines(self, model): - """ Generate genuine django inlines for registered content types. """ inlines = [] for content_type in model._feincms_content_types: - attrs = { - '__module__': model.__module__, - 'model': content_type, - } + if not self.can_add_content(request, content_type): + continue + + attrs = {"__module__": model.__module__, "model": content_type} - if hasattr(content_type, 'feincms_item_editor_inline'): + if hasattr(content_type, "feincms_item_editor_inline"): inline = content_type.feincms_item_editor_inline - attrs['form'] = inline.form + attrs["form"] = inline.form - if hasattr(content_type, 'feincms_item_editor_form'): - import warnings + if hasattr(content_type, "feincms_item_editor_form"): warnings.warn( - 'feincms_item_editor_form on %s is ignored because feincms_item_editor_inline is set too' % content_type, - RuntimeWarning) + "feincms_item_editor_form on %s is ignored because " + "feincms_item_editor_inline is set too" % content_type, + RuntimeWarning, + ) else: inline = FeinCMSInline - attrs['form'] = getattr(content_type, - 'feincms_item_editor_form', inline.form) - - name = '%sFeinCMSInline' % content_type.__name__ - inlines.append(type(name, (inline,), attrs)) + attrs["form"] = getattr( + content_type, "feincms_item_editor_form", inline.form + ) + + name = "%sFeinCMSInline" % content_type.__name__ + # TODO: We generate a new class every time. Is that really wanted? + inline_class = type(str(name), (inline,), attrs) + inlines.append(inline_class) return inlines - def _frontend_editing_view(self, request, cms_id, content_type, content_id): - """ - This view is used strictly for frontend editing -- it is not used inside the - standard administration interface. - - The code in feincms/templates/admin/feincms/fe_tools.html knows how to call - this view correctly. - """ - - try: - model_cls = loading.get_model(self.model._meta.app_label, content_type) - obj = model_cls.objects.get(parent=cms_id, id=content_id) - except: - raise Http404 - - form_class_base = getattr(model_cls, 'feincms_item_editor_form', ItemEditorForm) - - ModelForm = modelform_factory(model_cls, - exclude=('parent', 'region', 'ordering'), - form=form_class_base, - formfield_callback=curry(self.formfield_for_dbfield, request=request)) - - # we do not want to edit these two fields in the frontend editing mode; we are - # strictly editing single content blocks there. - # We have to remove them from the form because we explicitly redefined them in - # the ItemEditorForm definition above. Just using exclude is not enough. - del ModelForm.base_fields['region'] - del ModelForm.base_fields['ordering'] - - if request.method == 'POST': - # The prefix is used to replace the formset identifier from the ItemEditor - # interface. Customization of the form is easily possible through either matching - # the prefix (frontend editing) or the formset identifier (ItemEditor) as it is - # done in the richtext and mediafile init.html item editor includes. - form = ModelForm(request.POST, instance=obj, prefix=content_type) - - if form.is_valid(): - obj = form.save() - - return render_to_response('admin/feincms/fe_editor_done.html', { - 'content': obj.render(request=request), - 'identifier': obj.fe_identifier(), - 'FEINCMS_ADMIN_MEDIA': settings.FEINCMS_ADMIN_MEDIA, - 'FEINCMS_ADMIN_MEDIA_HOTLINKING': \ - settings.FEINCMS_ADMIN_MEDIA_HOTLINKING, - 'FEINCMS_JQUERY_NO_CONFLICT': \ - settings.FEINCMS_JQUERY_NO_CONFLICT, - }) - else: - form = ModelForm(instance=obj, prefix=content_type) - - context = self.get_extra_context(request) - context.update({ - 'frontend_editing': True, - 'title': _('Change %s') % force_unicode(model_cls._meta.verbose_name), - 'object': obj, - 'form': form, - 'is_popup': True, - 'media': self.media, - }) - - return render_to_response('admin/feincms/fe_editor.html', context, - context_instance=template.RequestContext(request)) - - def get_content_type_map(self): - """ Prepare mapping of content types to their prettified names. """ + def get_content_type_map(self, request): + """Prepare mapping of content types to their prettified names.""" content_types = [] for content_type in self.model._feincms_content_types: - content_name = content_type._meta.verbose_name - content_types.append((content_name, content_type.__name__.lower())) + if self.model == content_type._feincms_content_class: + content_name = content_type._meta.verbose_name + content_types.append((content_name, content_type.__name__.lower())) return content_types def get_extra_context(self, request): - """ Return extra context parameters for add/change views. """ + """Return extra context parameters for add/change views.""" extra_context = { - 'model': self.model, - 'available_templates': - getattr(self.model, '_feincms_templates', ()), - 'has_parent_attribute': hasattr(self.model, 'parent'), - 'content_types': self.get_content_type_map(), - 'FEINCMS_ADMIN_MEDIA': settings.FEINCMS_ADMIN_MEDIA, - 'FEINCMS_ADMIN_MEDIA_HOTLINKING': - settings.FEINCMS_ADMIN_MEDIA_HOTLINKING, - 'FEINCMS_JQUERY_NO_CONFLICT': settings.FEINCMS_JQUERY_NO_CONFLICT, - 'FEINCMS_CONTENT_FIELDSET_NAME': FEINCMS_CONTENT_FIELDSET_NAME, - - 'FEINCMS_FRONTEND_EDITING': settings.FEINCMS_FRONTEND_EDITING, - } + "request": request, + "model": self.model, + "available_templates": getattr(self.model, "_feincms_templates", ()), + "has_parent_attribute": hasattr(self.model, "parent"), + "content_types": self.get_content_type_map(request), + "FEINCMS_CONTENT_FIELDSET_NAME": FEINCMS_CONTENT_FIELDSET_NAME, + } for processor in self.model.feincms_item_editor_context_processors: extra_context.update(processor(request)) return extra_context - def add_view(self, request, form_url='', extra_context=None): + def add_view(self, request, **kwargs): + if not self.has_add_permission(request): + logger.warning( + 'Denied adding %s to "%s" (no add permission)', self.model, request.user + ) + raise Http404 + context = {} # insert dummy object as 'original' so template code can grab defaults # for template, etc. - context['original'] = self.model() + context["original"] = self.model() # If there are errors in the form, we need to preserve the object's # template as it was set when the user attempted to save it, so that # the same regions appear on screen. - if request.method == 'POST' and \ - hasattr(self.model, '_feincms_templates'): - context['original'].template_key = request.POST['template_key'] + if request.method == "POST" and hasattr(self.model, "_feincms_templates"): + context["original"].template_key = request.POST["template_key"] context.update(self.get_extra_context(request)) - context.update(extra_context or {}) - return super(ItemEditor, self).add_view(request, form_url, context) - - def change_view(self, request, object_id, extra_context=None): - self.model._needs_content_types() - - # Recognize frontend editing requests - # This is done here so that the developer does not need to add - # additional entries to # urls.py or something... - res = FRONTEND_EDITING_MATCHER.search(object_id) - if res: - return self._frontend_editing_view( - request, res.group(1), res.group(2), res.group(3)) + context.update(kwargs.get("extra_context", {})) + kwargs["extra_context"] = context + return super().add_view(request, **kwargs) + + def render_change_form(self, request, context, **kwargs): + if kwargs.get("add"): + if request.method == "GET" and "adminform" in context: + if "template_key" in context["adminform"].form.initial: + context["original"].template_key = context[ + "adminform" + ].form.initial["template_key"] + # ensure that initially-selected template in form is also + # used to render the initial regions in the item editor + return super().render_change_form(request, context, **kwargs) + + def change_view(self, request, object_id, **kwargs): + obj = self.get_object(request, unquote(object_id)) + if not self.has_change_permission(request, obj): + logger.warning( + 'Denied editing %s to "%s" (no edit permission)', + self.model, + request.user, + ) + raise Http404 context = {} context.update(self.get_extra_context(request)) - context.update(extra_context or {}) - return super(ItemEditor, self).change_view(request, object_id, context) - - # The next two add support for sending a "saving done" signal as soon - # as all relevant data have been saved (especially all foreign key relations) - # This can be used to keep functionality dependend on item content happy. - # NOTE: These two can (and probably should) be replaced by overriding - # `save_related` as soon as we don't depend on Django<1.4 any more. - def response_add(self, request, obj, *args, **kwargs): - r = super(ItemEditor, self).response_add(request, obj, *args, **kwargs) - itemeditor_post_save_related.send(sender=obj.__class__, instance=obj, created=True) - return r - - def response_change(self, request, obj, *args, **kwargs): - r = super(ItemEditor, self).response_change(request, obj, *args, **kwargs) - itemeditor_post_save_related.send(sender=obj.__class__, instance=obj, created=False) - return r + context.update(kwargs.get("extra_context", {})) + kwargs["extra_context"] = context + return super().change_view(request, object_id, **kwargs) + + def save_related(self, request, form, formsets, change): + super().save_related(request, form, formsets, change) + itemeditor_post_save_related.send( + sender=form.instance.__class__, instance=form.instance, created=not change + ) @property def change_form_template(self): - return self.get_template_list() - - def get_template_list(self): - # retained for backwards-compatibility, change_form_template wraps it opts = self.model._meta return [ - 'admin/feincms/%s/%s/item_editor.html' % ( - opts.app_label, opts.object_name.lower()), - 'admin/feincms/%s/item_editor.html' % opts.app_label, - 'admin/feincms/item_editor.html', - ] + "admin/feincms/%s/%s/item_editor.html" + % (opts.app_label, opts.object_name.lower()), + "admin/feincms/%s/item_editor.html" % opts.app_label, + "admin/feincms/item_editor.html", + ] def get_fieldsets(self, request, obj=None): """ @@ -259,10 +212,10 @@ def get_fieldsets(self, request, obj=None): Is it reasonable to assume this should always be included? """ - fieldsets = copy.deepcopy( - super(ItemEditor, self).get_fieldsets(request, obj)) + fieldsets = copy.deepcopy(super().get_fieldsets(request, obj)) + names = [f[0] for f in fieldsets] - if not FEINCMS_CONTENT_FIELDSET_NAME in dict(fieldsets).keys(): + if FEINCMS_CONTENT_FIELDSET_NAME not in names: fieldsets.append(FEINCMS_CONTENT_FIELDSET) return fieldsets @@ -273,6 +226,21 @@ def get_fieldsets(self, request, obj=None): recover_form_template = "admin/feincms/recover_form.html" - def render_revision_form(self, request, obj, version, context, revert=False, recover=False): + # For Reversion < v2.0.0 + def render_revision_form( + self, request, obj, version, context, revert=False, recover=False + ): + context.update(self.get_extra_context(request)) + return super().render_revision_form( + request, obj, version, context, revert, recover + ) + + # For Reversion >= v2.0.0 + def _reversion_revisionform_view( + self, request, version, template_name, extra_context=None + ): + context = extra_context or {} context.update(self.get_extra_context(request)) - return super(ItemEditor, self).render_revision_form(request, obj, version, context, revert, recover) + return super()._reversion_revisionform_view( + request, version, template_name, context + ) diff --git a/feincms/admin/tree_editor.py b/feincms/admin/tree_editor.py index b0700447d..07aa1d0ab 100644 --- a/feincms/admin/tree_editor.py +++ b/feincms/admin/tree_editor.py @@ -1,18 +1,35 @@ -from django.conf import settings as django_settings -from django.contrib import admin +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + +import json +import logging +from functools import reduce + +from django.contrib.admin.actions import delete_selected from django.contrib.admin.views import main +from django.contrib.auth import get_permission_codename from django.db.models import Q -from django.db.models.query import QuerySet -from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotFound, HttpResponseServerError -from django.utils import simplejson +from django.http import ( + HttpResponse, + HttpResponseBadRequest, + HttpResponseForbidden, + HttpResponseNotFound, + HttpResponseServerError, +) +from django.templatetags.static import static +from django.utils.encoding import force_str +from django.utils.html import escape from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _, ugettext - +from django.utils.translation import gettext, gettext_lazy as _ from mptt.exceptions import InvalidMove +from mptt.forms import MPTTAdminForm from feincms import settings +from feincms.extensions import ExtensionModelAdmin -import logging + +logger = logging.getLogger(__name__) # ------------------------------------------------------------------------ @@ -22,17 +39,17 @@ def django_boolean_icon(field_val, alt_text=None, title=None): """ # Origin: contrib/admin/templatetags/admin_list.py - BOOLEAN_MAPPING = { True: 'yes', False: 'no', None: 'unknown' } + BOOLEAN_MAPPING = {True: "yes", False: "no", None: "unknown"} alt_text = alt_text or BOOLEAN_MAPPING[field_val] if title is not None: title = 'title="%s" ' % title else: - title = '' - return mark_safe(u'%s' % - (settings._HACK_ADMIN_MEDIA_IMAGES, BOOLEAN_MAPPING[field_val], alt_text, title)) + title = "" + icon_url = static("feincms/img/icon-%s.gif" % BOOLEAN_MAPPING[field_val]) + return mark_safe(f'{alt_text}') -def _build_tree_structure(cls): +def _build_tree_structure(queryset): """ Build an in-memory representation of the item tree, trying to keep database accesses down to a minimum. The returned dictionary looks like @@ -44,28 +61,19 @@ def _build_tree_structure(cls): ... } """ - all_nodes = { } - - if hasattr(cls, '_mptt_meta'): # New-style MPTT - mptt_opts = cls._mptt_meta - else: - mptt_opts = cls._meta - - for p_id, parent_id in cls.objects.order_by(mptt_opts.tree_id_attr, mptt_opts.left_attr).values_list("pk", "%s_id" % mptt_opts.parent_attr): - all_nodes[p_id] = [] - - if parent_id: - if not all_nodes.has_key(parent_id): - # This happens very rarely, but protect against parents that - # we have yet to iteratove over. - all_nodes[parent_id] = [] - all_nodes[parent_id].append(p_id) - + all_nodes = {} + + mptt_opts = queryset.model._mptt_meta + items = queryset.order_by(mptt_opts.tree_id_attr, mptt_opts.left_attr).values_list( + "pk", "%s_id" % mptt_opts.parent_attr + ) + for p_id, parent_id in items: + all_nodes.setdefault(str(parent_id) if parent_id else 0, []).append(p_id) return all_nodes # ------------------------------------------------------------------------ -def ajax_editable_boolean_cell(item, attr, text='', override=None): +def ajax_editable_boolean_cell(item, attr, text="", override=None): """ Generate a html snippet for showing a boolean value on the admin page. Item is an object, attr is the attribute name we should display. Text @@ -80,23 +88,22 @@ def ajax_editable_boolean_cell(item, attr, text='', override=None): (useful for "disabled and you can't change it" situations). """ if text: - text = ' (%s)' % unicode(text) + text = " (%s)" % text if override is not None: - a = [ django_boolean_icon(override, text), text ] + a = [django_boolean_icon(override, text), text] else: value = getattr(item, attr) a = [ - '', - text, - ] + '' + % (item.pk, attr, 'checked="checked"' if value else "") + ] + + a.insert(0, '
' % (attr, item.pk)) + a.append("
") + return mark_safe("".join(a)) - a.insert(0, '
' % ( attr, item.id )) - a.append('
') - return unicode(''.join(a)) # ------------------------------------------------------------------------ def ajax_editable_boolean(attr, short_description): @@ -107,13 +114,14 @@ def ajax_editable_boolean(attr, short_description): Example:: class MyTreeEditor(TreeEditor): - list_display = ('__unicode__', 'active_toggle') + list_display = ('__str__', 'active_toggle') active_toggle = ajax_editable_boolean('active', _('is active')) """ + def _fn(self, item): return ajax_editable_boolean_cell(item, attr) - _fn.allow_tags = True + _fn.short_description = short_description _fn.editable_boolean_field = attr return _fn @@ -128,95 +136,157 @@ class ChangeList(main.ChangeList): def __init__(self, request, *args, **kwargs): self.user = request.user - super(ChangeList, self).__init__(request, *args, **kwargs) - - def get_query_set(self, *args, **kwargs): - return super(ChangeList, self).get_query_set(*args, **kwargs).order_by('tree_id', 'lft') + super().__init__(request, *args, **kwargs) + + def get_queryset(self, *args, **kwargs): + mptt_opts = self.model._mptt_meta + qs = ( + super() + .get_queryset(*args, **kwargs) + .order_by(mptt_opts.tree_id_attr, mptt_opts.left_attr) + ) + # Force has_filters, so that the expand/collapse in sidebar is visible + self.has_filters = True + return qs def get_results(self, request): + mptt_opts = self.model._mptt_meta if settings.FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS: - clauses = [Q( - tree_id=tree_id, - lft__lte=lft, - rght__gte=rght, - ) for lft, rght, tree_id in \ - self.query_set.values_list('lft', 'rght', 'tree_id')] + clauses = [ + Q( + **{ + mptt_opts.tree_id_attr: tree_id, + mptt_opts.left_attr + "__lte": lft, + mptt_opts.right_attr + "__gte": rght, + } + ) + for lft, rght, tree_id in self.queryset.values_list( + mptt_opts.left_attr, mptt_opts.right_attr, mptt_opts.tree_id_attr + ) + ] + # We could optimise a bit here by explicitely filtering out + # any clauses that are for parents of nodes included in the + # queryset anyway. (ie: drop all clauses that refer to a node + # that is a parent to another node) + if clauses: - self.query_set = self.model._default_manager.filter(reduce(lambda p, q: p|q, clauses)) + # Note: Django ORM is smart enough to drop additional + # clauses if the initial query set is unfiltered. This + # is good. + self.queryset = self.queryset.union( + self.model._default_manager.filter( + reduce(lambda p, q: p | q, clauses) + ) + ).order_by(mptt_opts.tree_id_attr, mptt_opts.left_attr) + + super().get_results(request) + + # Pre-process permissions because we still have the request here, + # which is not passed in later stages in the tree editor + for item in self.result_list: + item.feincms_changeable = self.model_admin.has_change_permission( + request, item + ) - super(ChangeList, self).get_results(request) + item.feincms_addable = ( + item.feincms_changeable + and self.model_admin.has_add_permission(request, item) + ) - opts = self.model_admin.opts - label = opts.app_label + '.' + opts.get_change_permission() - for item in self.result_list: - if settings.FEINCMS_TREE_EDITOR_OBJECT_PERMISSIONS: - item.feincms_editable = self.model_admin.has_change_permission(request, item) - else: - item.feincms_editable = True # ------------------------------------------------------------------------ -# MARK: - -# ------------------------------------------------------------------------ - -class TreeEditor(admin.ModelAdmin): +class TreeEditor(ExtensionModelAdmin): """ The ``TreeEditor`` modifies the standard Django administration change list to a drag-drop enabled interface for django-mptt_-managed Django models. - .. _django-mptt: http://github.com/mptt/django-mptt/ + .. _django-mptt: https://github.com/django-mptt/django-mptt/ """ + form = MPTTAdminForm + if settings.FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS: - # Make sure that no pagination is displayed. Slicing is disabled anyway, - # therefore this value does not have an influence on the queryset + # Make sure that no pagination is displayed. Slicing is disabled + # anyway, therefore this value does not have an influence on the + # queryset list_per_page = 999999999 def __init__(self, *args, **kwargs): - super(TreeEditor, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.list_display = list(self.list_display) - if 'indented_short_title' not in self.list_display: - if self.list_display[0] == 'action_checkbox': - self.list_display[1] = 'indented_short_title' + if "indented_short_title" not in self.list_display: + if self.list_display[0] == "action_checkbox": + self.list_display[1] = "indented_short_title" else: - self.list_display[0] = 'indented_short_title' - self.list_display_links = ('indented_short_title',) + self.list_display[0] = "indented_short_title" + self.list_display_links = ("indented_short_title",) opts = self.model._meta self.change_list_template = [ - 'admin/feincms/%s/%s/tree_editor.html' % (opts.app_label, opts.object_name.lower()), - 'admin/feincms/%s/tree_editor.html' % opts.app_label, - 'admin/feincms/tree_editor.html', - ] - - def editable(self, item): - return getattr(item, 'feincms_editable', True) + "admin/feincms/%s/%s/tree_editor.html" + % (opts.app_label, opts.object_name.lower()), + "admin/feincms/%s/tree_editor.html" % opts.app_label, + "admin/feincms/tree_editor.html", + ] + self.object_change_permission = ( + opts.app_label + "." + get_permission_codename("change", opts) + ) + self.object_add_permission = ( + opts.app_label + "." + get_permission_codename("add", opts) + ) + self.object_delete_permission = ( + opts.app_label + "." + get_permission_codename("delete", opts) + ) + + def changeable(self, item): + return getattr(item, "feincms_changeable", True) def indented_short_title(self, item): """ Generate a short title for an object, indent it depending on the object's depth in the hierarchy. """ - r = '' - if hasattr(item, 'get_absolute_url'): - r = '' % item.get_absolute_url() - - editable_class = '' - if not getattr(item, 'feincms_editable', True): - editable_class = ' tree-item-not-editable' - - r += '  ' % ( - item.id, editable_class, 14+item.level*18) -# r += '' - if hasattr(item, 'short_title'): - r += item.short_title() + mptt_opts = item._mptt_meta + r = "" + try: + url = item.get_absolute_url() + except (AttributeError,): + url = None + + if url: + r = ( + '' + ) % (url, item.pk) + + changeable_class = "" + if not self.changeable(item): + changeable_class = " tree-item-not-editable" + tree_root_class = "" + if not item.parent_id: + tree_root_class = " tree-root" + + r += ( + '  ' + ) % ( + item.pk, + changeable_class, + tree_root_class, + 14 + getattr(item, mptt_opts.level_attr) * 18, + ) + + # r += '' + if hasattr(item, "short_title") and callable(item.short_title): + r += escape(item.short_title()) else: - r += unicode(item) -# r += '' + r += escape("%s" % item) + # r += '' return mark_safe(r) - indented_short_title.short_description = _('title') - indented_short_title.allow_tags = True + + indented_short_title.short_description = _("title") def _collect_editable_booleans(self): """ @@ -224,7 +294,7 @@ def _collect_editable_booleans(self): want the user to be able to edit arbitrary fields by crafting an AJAX request by hand. """ - if hasattr(self, '_ajax_editable_booleans'): + if hasattr(self, "_ajax_editable_booleans"): return self._ajax_editable_booleans = {} @@ -234,44 +304,48 @@ def _collect_editable_booleans(self): # to the ModelAdmin class try: item = getattr(self.__class__, field) - except (AttributeError, TypeError), e: + except (AttributeError, TypeError): continue - attr = getattr(item, 'editable_boolean_field', None) + attr = getattr(item, "editable_boolean_field", None) if attr: - def _fn(self, instance): - return [ ajax_editable_boolean_cell(instance, _fn.attr) ] - _fn.attr = attr - result_func = getattr(item, 'editable_boolean_result', _fn) - self._ajax_editable_booleans[attr] = result_func + if hasattr(item, "editable_boolean_result"): + result_func = item.editable_boolean_result + else: - def _refresh_changelist_caches(self): - """ - Refresh information used to show the changelist tree structure such as - inherited active/inactive states etc. + def _fn(attr): + return lambda self, instance: [ + ajax_editable_boolean_cell(instance, attr) + ] - XXX: This is somewhat hacky, but since it's an internal method, so be it. - """ - - pass + result_func = _fn(attr) + self._ajax_editable_booleans[attr] = result_func def _toggle_boolean(self, request): """ Handle an AJAX toggle_boolean request """ try: - item_id = int(request.POST.get('item_id', None)) - attr = str(request.POST.get('attr', None)) - except: + item_id = int(request.POST.get("item_id", None)) + attr = str(request.POST.get("attr", None)) + except Exception: return HttpResponseBadRequest("Malformed request") if not request.user.is_staff: - logging.warning("Denied AJAX request by non-staff %s to toggle boolean %s for object #%s", request.user, attr, item_id) - return HttpResponseForbidden("You do not have permission to access this object") + logger.warning( + 'Denied AJAX request by non-staff "%s" to toggle boolean' + " %s for object #%s", + request.user, + attr, + item_id, + ) + return HttpResponseForbidden( + _("You do not have permission to modify this object") + ) self._collect_editable_booleans() - if not self._ajax_editable_booleans.has_key(attr): + if attr not in self._ajax_editable_booleans: return HttpResponseBadRequest("not a valid attribute %s" % attr) try: @@ -279,45 +353,47 @@ def _toggle_boolean(self, request): except self.model.DoesNotExist: return HttpResponseNotFound("Object does not exist") - can_change = False - - if hasattr(obj, "user_can") and obj.user_can(request.user, change_page=True): - # Was added in c7f04dfb5d, but I've no idea what user_can is about. - can_change = True - else: - can_change = self.has_change_permission(request, obj=obj) - - if not can_change: - logging.warning("Denied AJAX request by %s to toggle boolean %s for object %s", request.user, attr, item_id) - return HttpResponseForbidden("You do not have permission to access this object") - - logging.info("Processing request by %s to toggle %s on %s", request.user, attr, obj) + if not self.has_change_permission(request, obj=obj): + logger.warning( + 'Denied AJAX request by "%s" to toggle boolean %s for object %s', + request.user, + attr, + item_id, + ) + return HttpResponseForbidden( + _("You do not have permission to modify this object") + ) + + new_state = not getattr(obj, attr) + logger.info( + 'Toggle %s on #%d %s to %s by "%s"', + attr, + obj.pk, + obj, + "on" if new_state else "off", + request.user, + ) try: before_data = self._ajax_editable_booleans[attr](self, obj) - setattr(obj, attr, not getattr(obj, attr)) + setattr(obj, attr, new_state) obj.save() - self._refresh_changelist_caches() # ???: Perhaps better a post_save signal? - # Construct html snippets to send back to client for status update data = self._ajax_editable_booleans[attr](self, obj) - except Exception, e: - logging.exception("Unhandled exception while toggling %s on %s", attr, obj) - return HttpResponseServerError("Unable to toggle %s on %s" % (attr, obj)) + except Exception: + logger.exception("Unhandled exception while toggling %s on %s", attr, obj) + return HttpResponseServerError(f"Unable to toggle {attr} on {obj}") # Weed out unchanged cells to keep the updates small. This assumes # that the order a possible get_descendents() returns does not change # before and after toggling this attribute. Unlikely, but still... - d = [] - for a, b in zip(before_data, data): - if a != b: - d.append(b) - - # TODO: Shorter: [ y for x,y in zip(a,b) if x!=y ] - return HttpResponse(simplejson.dumps(d), mimetype="application/json") + return HttpResponse( + json.dumps([b for a, b in zip(before_data, data) if a != b]), + content_type="application/json", + ) def get_changelist(self, request, **kwargs): return ChangeList @@ -328,82 +404,171 @@ def changelist_view(self, request, extra_context=None, *args, **kwargs): change list/actions page. """ - if 'actions_column' not in self.list_display: - self.list_display.append('actions_column') + if "actions_column" not in self.list_display: + self.list_display.append("actions_column") # handle common AJAX requests - if request.is_ajax(): - cmd = request.POST.get('__cmd') - if cmd == 'toggle_boolean': + if "__cmd" in request.POST: + cmd = request.POST.get("__cmd") + if cmd == "toggle_boolean": return self._toggle_boolean(request) - elif cmd == 'move_node': + elif cmd == "move_node": return self._move_node(request) - else: - return HttpResponseBadRequest('Oops. AJAX request not understood.') - self._refresh_changelist_caches() + return HttpResponseBadRequest("Oops. AJAX request not understood.") extra_context = extra_context or {} - extra_context['FEINCMS_ADMIN_MEDIA'] = settings.FEINCMS_ADMIN_MEDIA - extra_context['FEINCMS_ADMIN_MEDIA_HOTLINKING'] = settings.FEINCMS_ADMIN_MEDIA_HOTLINKING - extra_context['tree_structure'] = mark_safe(simplejson.dumps( - _build_tree_structure(self.model))) - return super(TreeEditor, self).changelist_view(request, extra_context, *args, **kwargs) + extra_context.update( + { + "tree_structure": mark_safe( + json.dumps( + obj=_build_tree_structure(self.get_queryset(request)), + separators=(",", ":"), + ) + ), + "node_levels": mark_safe( + json.dumps( + dict( + self.get_queryset(request) + .order_by() + .values_list("pk", self.model._mptt_meta.level_attr) + ), + separators=(",", ":"), + ) + ), + } + ) + + return super().changelist_view(request, extra_context, *args, **kwargs) + + def has_add_permission(self, request, obj=None): + """ + Implement a lookup for object level permissions. Basically the same as + ModelAdmin.has_add_permission, but also passes the obj parameter in. + """ + perm = self.object_add_permission + if settings.FEINCMS_TREE_EDITOR_OBJECT_PERMISSIONS: + r = request.user.has_perm(perm, obj) + else: + r = request.user.has_perm(perm) + + return r and super().has_add_permission(request) def has_change_permission(self, request, obj=None): """ Implement a lookup for object level permissions. Basically the same as ModelAdmin.has_change_permission, but also passes the obj parameter in. """ + perm = self.object_change_permission if settings.FEINCMS_TREE_EDITOR_OBJECT_PERMISSIONS: - opts = self.opts - r = request.user.has_perm(opts.app_label + '.' + opts.get_change_permission(), obj) + r = request.user.has_perm(perm, obj) else: - r = True + r = request.user.has_perm(perm) - return r and super(TreeEditor, self).has_change_permission(request, obj) + return r and super().has_change_permission(request, obj) def has_delete_permission(self, request, obj=None): """ Implement a lookup for object level permissions. Basically the same as ModelAdmin.has_delete_permission, but also passes the obj parameter in. """ + perm = self.object_delete_permission if settings.FEINCMS_TREE_EDITOR_OBJECT_PERMISSIONS: - opts = self.opts - r = request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission(), obj) + r = request.user.has_perm(perm, obj) else: - r = True + r = request.user.has_perm(perm) - return r and super(TreeEditor, self).has_delete_permission(request, obj) + return r and super().has_delete_permission(request, obj) def _move_node(self, request): - cut_item = self.model._tree_manager.get(pk=request.POST.get('cut_item')) - pasted_on = self.model._tree_manager.get(pk=request.POST.get('pasted_on')) - position = request.POST.get('position') + if hasattr(self.model.objects, "move_node"): + tree_manager = self.model.objects + else: + tree_manager = self.model._tree_manager - if position in ('last-child', 'left'): + queryset = self.get_queryset(request) + cut_item = queryset.get(pk=request.POST.get("cut_item")) + pasted_on = queryset.get(pk=request.POST.get("pasted_on")) + position = request.POST.get("position") + + if not self.has_change_permission(request, cut_item): + self.message_user(request, _("No permission")) + return HttpResponse("FAIL") + + if position in ("last-child", "left", "right"): try: - self.model._tree_manager.move_node(cut_item, pasted_on, position) - except InvalidMove, e: - self.message_user(request, unicode(e)) - return HttpResponse('FAIL') + tree_manager.move_node(cut_item, pasted_on, position) + except InvalidMove as e: + self.message_user(request, "%s" % e) + return HttpResponse("FAIL") - # Ensure that model save has been run - cut_item = self.model._tree_manager.get(pk=cut_item.pk) - cut_item.save() + # Ensure that model save methods have been run (required to + # update Page._cached_url values, might also be helpful for other + # models inheriting MPTTModel) + for item in queryset.filter(id__in=(cut_item.pk, pasted_on.pk)): + item.save() - self.message_user(request, ugettext('%s has been moved to a new position.') % - cut_item) - return HttpResponse('OK') + self.message_user( + request, gettext("%s has been moved to a new position.") % cut_item + ) + return HttpResponse("OK") - self.message_user(request, ugettext('Did not understand moving instruction.')) - return HttpResponse('FAIL') + self.message_user(request, _("Did not understand moving instruction.")) + return HttpResponse("FAIL") def _actions_column(self, instance): - return ['
',] + if self.changeable(instance): + return ['
'] + return [] def actions_column(self, instance): - return u' '.join(self._actions_column(instance)) - actions_column.allow_tags = True - actions_column.short_description = _('actions') + return mark_safe(" ".join(self._actions_column(instance))) + + actions_column.short_description = _("actions") + + def delete_selected_tree(self, modeladmin, request, queryset): + """ + Deletes multiple instances and makes sure the MPTT fields get + recalculated properly. (Because merely doing a bulk delete doesn't + trigger the post_delete hooks.) + """ + # If this is True, the confirmation page has been displayed + if request.POST.get("post"): + n = 0 + # TODO: The disable_mptt_updates / rebuild is a work around + # for what seems to be a mptt problem when deleting items + # in a loop. Revisit this, there should be a better solution. + with queryset.model.objects.disable_mptt_updates(): + for obj in queryset: + if self.has_delete_permission(request, obj): + obj.delete() + n += 1 + obj_display = force_str(obj) + self.log_deletion(request, obj, obj_display) + else: + logger.warning( + 'Denied delete request by "%s" for object #%s', + request.user, + obj.id, + ) + if n > 0: + queryset.model.objects.rebuild() + self.message_user( + request, _("Successfully deleted %(count)d items.") % {"count": n} + ) + # Return None to display the change list page again + return None + else: + # (ab)using the built-in action to display the confirmation page + return delete_selected(self, request, queryset) + + def get_actions(self, request): + actions = super().get_actions(request) + if "delete_selected" in actions: + actions["delete_selected"] = ( + self.delete_selected_tree, + "delete_selected", + _("Delete selected %(verbose_name_plural)s"), + ) + return actions diff --git a/feincms/apps.py b/feincms/apps.py new file mode 100644 index 000000000..8e5394646 --- /dev/null +++ b/feincms/apps.py @@ -0,0 +1,17 @@ +def __getattr__(key): + # Work around Django 3.2's autoloading of *.apps modules (AppConfig + # autodiscovery) + if key in { + "ApplicationContent", + "app_reverse", + "app_reverse_lazy", + "permalink", + "UnpackTemplateResponse", + "standalone", + "unpack", + }: + from feincms.content.application import models + + return getattr(models, key) + + raise AttributeError("Unknown attribute '%s'" % key) diff --git a/feincms/compat.py b/feincms/compat.py deleted file mode 100644 index e45fa0526..000000000 --- a/feincms/compat.py +++ /dev/null @@ -1,19 +0,0 @@ -def c_any(iterable): - """ - Implements python 2.5's any() - """ - - for element in iterable: - if element: - return True - return False - -def c_all(iterable): - """ - Implements python 2.5's all() - """ - - for element in iterable: - if not element: - return False - return True diff --git a/feincms/content/application/models.py b/feincms/content/application/models.py index 75941147b..46d5b44c9 100644 --- a/feincms/content/application/models.py +++ b/feincms/content/application/models.py @@ -1,180 +1,177 @@ -""" -Third-party application inclusion support. -""" - +import warnings +from collections import OrderedDict +from email.utils import parsedate +from functools import partial, wraps from time import mktime -import re -from django.core import urlresolvers -from django.core.urlresolvers import Resolver404, resolve, reverse as _reverse, NoReverseMatch +from django.conf import settings +from django.core.cache import cache from django.db import models from django.http import HttpResponse +from django.template.response import TemplateResponse +from django.urls import ( + NoReverseMatch, + Resolver404, + get_script_prefix, + resolve, + reverse, + set_script_prefix, +) +from django.utils.functional import lazy +from django.utils.http import http_date from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ - -try: - from functools import partial -except ImportError: - from django.utils.functional import curry as partial +from django.utils.translation import get_language, gettext_lazy as _ -from feincms.admin.editor import ItemEditorForm +from feincms.admin.item_editor import ItemEditorForm from feincms.contrib.fields import JSONField +from feincms.translations import short_language_code from feincms.utils import get_object -try: - from email.utils import parsedate -except ImportError: # py 2.4 compat - from email.Utils import parsedate -try: - from threading import local -except ImportError: - from django.utils._threading_local import local +APP_REVERSE_CACHE_TIMEOUT = 3 + -_local = local() +__all__ = ( + "ApplicationContent", + "app_reverse", + "app_reverse_lazy", + "permalink", + "UnpackTemplateResponse", + "standalone", + "unpack", +) -def retrieve_page_information(page, request=None): - _local.proximity_info = (page.tree_id, page.lft, page.rght, page.level) +class UnpackTemplateResponse(TemplateResponse): + """ + Completely the same as marking applicationcontent-contained views with + the ``feincms.views.decorators.unpack`` decorator. + """ + + _feincms_unpack = True -def _empty_reverse_cache(): - _local.reverse_cache = {} +def standalone(view_func): + """ + Marks the view method as standalone view; this means that + ``HttpResponse`` objects returned from ``ApplicationContent`` + are returned directly, without further processing. + """ + def inner(request, *args, **kwargs): + response = view_func(request, *args, **kwargs) + if isinstance(response, HttpResponse): + response.standalone = True + return response -APPLICATIONCONTENT_RE = re.compile(r'^([^/]+)/([^/]+)$') + return wraps(view_func)(inner) -def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, *vargs, **vkwargs): +def unpack(view_func): """ - This reverse replacement adds two new capabilities to the Django reverse method: - - - If reverse is called from inside ``ApplicationContent.process``, it - automatically prepends the URL of the page the ``ApplicationContent`` - is attached to, thereby allowing ``reverse`` and ``{% url %}`` to - return correct URLs without hard-coding the application integration - point into the templates or URLconf files. - - If the viewname contains a slash, the part before the slash is - interpreted as the path to an URLconf file. This allows the template - author to resolve URLs only reachable via an ``ApplicationContent``, - even inside another application contents' ``process`` method:: - - {% url registration.urls/auth_logout %} + Marks the returned response as to-be-unpacked if it is a + ``TemplateResponse``. """ - if isinstance(viewname, basestring) and APPLICATIONCONTENT_RE.match(viewname): - # try to reverse an URL inside another applicationcontent - other_urlconf, other_viewname = viewname.split('/') + def inner(request, *args, **kwargs): + response = view_func(request, *args, **kwargs) + if isinstance(response, TemplateResponse): + response._feincms_unpack = True + return response - # TODO do not use internal feincms data structures as much - model_class = ApplicationContent._feincms_content_models[0] + return wraps(view_func)(inner) - if hasattr(_local, 'urlconf') and other_urlconf == _local.urlconf[0]: - # We are reversing an URL from our own ApplicationContent - return _reverse(other_viewname, other_urlconf, args, kwargs, _local.urlconf[1], *vargs, **vkwargs) - if not hasattr(_local, 'reverse_cache'): - _local.reverse_cache = {} +def cycle_app_reverse_cache(*args, **kwargs): + warnings.warn( + "cycle_app_reverse_cache does nothing and will be removed in" + " a future version of FeinCMS.", + DeprecationWarning, + stacklevel=2, + ) - # try different cache keys of descending specificity, this one always works - urlconf_cache_keys = { - 'none': '%s_noprox' % other_urlconf, - } +def app_reverse(viewname, urlconf=None, args=None, kwargs=None, *vargs, **vkwargs): + """ + Reverse URLs from application contents - # when we have more proximity info, we can use more specific cache keys - proximity_info = getattr(_local, 'proximity_info', None) - if proximity_info: - urlconf_cache_keys.update({ - 'all': '%s_%s_%s_%s_%s' % ((other_urlconf,) + proximity_info), - 'tree': '%s_%s' % (other_urlconf, proximity_info[0]), - }) + Works almost like Django's own reverse() method except that it resolves + URLs from application contents. The second argument, ``urlconf``, has to + correspond to the URLconf parameter passed in the ``APPLICATIONS`` list + to ``Page.create_content_type``:: - for key in ('all', 'tree', 'none'): - if key in urlconf_cache_keys and urlconf_cache_keys[key] in _local.reverse_cache: - content = _local.reverse_cache[urlconf_cache_keys[key]] - break - else: - contents = model_class.objects.filter( - urlconf_path=other_urlconf).select_related('parent') - - if proximity_info: - # find the closest match within the same subtree - tree_contents = contents.filter(parent__tree_id=proximity_info[0]) - if not len(tree_contents): - # no application contents within the same tree - cache_key = 'tree' - try: - content = contents[0] - except IndexError: - content = None - elif len(tree_contents) == 1: - cache_key = 'tree' - # just one match within the tree, use it - content = tree_contents[0] - else: # len(tree_contents) > 1 - cache_key = 'all' - try: - # select all ancestors and descendants and get the one with - # the smallest difference in levels - content = (tree_contents.filter( - parent__rght__gt=proximity_info[2], - parent__lft__lt=proximity_info[1] - ) | tree_contents.filter( - parent__lft__lte=proximity_info[2], - parent__lft__gte=proximity_info[1], - )).extra({'level_diff':"abs(level-%d)" % proximity_info[3]} - ).order_by('level_diff')[0] - except IndexError: - content = tree_contents[0] - else: - cache_key = 'none' - try: - content = contents[0] - except IndexError: - content = None - _local.reverse_cache[urlconf_cache_keys[cache_key]] = content - - if content: - # Save information from _urlconfs in case we are inside another - # application contents' ``process`` method currently - saved_cfg = getattr(_local, 'urlconf', None) - - if other_urlconf in model_class.ALL_APPS_CONFIG: - # We have an overridden URLconf - other_urlconf = model_class.ALL_APPS_CONFIG[other_urlconf]['config'].get( - 'urls', other_urlconf) + app_reverse('mymodel-detail', 'myapp.urls', args=...) - # Initialize application content reverse hackery for the other application - _local.urlconf = (other_urlconf, content.parent.get_absolute_url()) + or - try: - url = reverse(other_viewname, other_urlconf, args, kwargs, prefix, *vargs, **vkwargs) - except: - url = None + app_reverse('mymodel-detail', 'myapp.urls', kwargs=...) - if saved_cfg: - _local.urlconf = saved_cfg - else: - del _local.urlconf + The second argument may also be a request object if you want to reverse + an URL belonging to the current application content. + """ + + # First parameter might be a request instead of an urlconf path, so + # we'll try to be helpful and extract the current urlconf from it + extra_context = getattr(urlconf, "_feincms_extra_context", {}) + appconfig = extra_context.get("app_config", {}) + urlconf = appconfig.get("urlconf_path", urlconf) - # We found an URL somewhere in here... return it. Otherwise, we continue - # below - if url: - return url + appcontent_class = ApplicationContent._feincms_content_models[0] + cache_key = appcontent_class.app_reverse_cache_key(urlconf) + url_prefix = cache.get(cache_key) - if hasattr(_local, 'urlconf'): - # Special handling inside ApplicationContent.render; override urlconf - # and prefix variables so that reverse works as expected. - urlconf1, prefix1 = _local.urlconf + if url_prefix is None: + content = appcontent_class.closest_match(urlconf) + + if content is not None: + if urlconf in appcontent_class.ALL_APPS_CONFIG: + # We have an overridden URLconf + app_config = appcontent_class.ALL_APPS_CONFIG[urlconf] + urlconf = app_config["config"].get("urls", urlconf) + + prefix = content.parent.get_absolute_url() + prefix += "/" if prefix[-1] != "/" else "" + + url_prefix = (urlconf, prefix) + cache.set(cache_key, url_prefix, timeout=APP_REVERSE_CACHE_TIMEOUT) + + if url_prefix: + # vargs and vkwargs are used to send through additional parameters + # which are uninteresting to us (such as current_app) + prefix = get_script_prefix() try: - return _reverse(viewname, urlconf1, args, kwargs, prefix1, *vargs, **vkwargs) - except NoReverseMatch: - # fall through to calling reverse with default arguments - pass + set_script_prefix(url_prefix[1]) + return reverse( + viewname, url_prefix[0], args=args, kwargs=kwargs, *vargs, **vkwargs + ) + finally: + set_script_prefix(prefix) + + raise NoReverseMatch("Unable to find ApplicationContent for %r" % urlconf) + - return _reverse(viewname, urlconf, args, kwargs, prefix, *vargs, **vkwargs) -urlresolvers.reverse = reverse +#: Lazy version of ``app_reverse`` +app_reverse_lazy = lazy(app_reverse, str) + + +def permalink(func): + """ + Decorator that calls app_reverse() + + Use this instead of standard django.db.models.permalink if you want to + integrate the model through ApplicationContent. The wrapped function + must return 4 instead of 3 arguments:: + + class MyModel(models.Model): + @appmodels.permalink + def get_absolute_url(self): + return ('myapp.urls', 'model_detail', (), {'slug': self.slug}) + """ + + def inner(*args, **kwargs): + return app_reverse(*func(*args, **kwargs)) + + return wraps(func)(inner) class ApplicationContent(models.Model): @@ -183,24 +180,22 @@ class ApplicationContent(models.Model): # MyBlogApp for blog ") parameters = JSONField(null=True, editable=False) - ALL_APPS_CONFIG = {} + ALL_APPS_CONFIG = OrderedDict() class Meta: abstract = True - verbose_name = _('application content') - verbose_name_plural = _('application contents') + verbose_name = _("application content") + verbose_name_plural = _("application contents") @classmethod def initialize_type(cls, APPLICATIONS): - # Generate a more flexible application configuration structure from - # the legacy pattern: - - # TODO: Consider changing the input signature to something cleaner, at - # the cost of a one-time backwards incompatible change - for i in APPLICATIONS: if not 2 <= len(i) <= 3: - raise ValueError("APPLICATIONS must be provided with tuples containing at least two parameters (urls, name) and an optional extra config dict") + raise ValueError( + "APPLICATIONS must be provided with tuples containing at" + " least two parameters (urls, name) and an optional extra" + " config dict" + ) urls, name = i[0:2] @@ -208,37 +203,46 @@ def initialize_type(cls, APPLICATIONS): app_conf = i[2] if not isinstance(app_conf, dict): - raise ValueError("The third parameter of an APPLICATIONS entry must be a dict or the name of one!") + raise ValueError( + "The third parameter of an APPLICATIONS entry must be" + " a dict or the name of one!" + ) else: app_conf = {} - cls.ALL_APPS_CONFIG[urls] = { - "urls": urls, - "name": name, - "config": app_conf - } + cls.ALL_APPS_CONFIG[urls] = {"urls": urls, "name": name, "config": app_conf} - cls.add_to_class('urlconf_path', - models.CharField(_('application'), max_length=100, choices=[(c['urls'], c['name']) for c in cls.ALL_APPS_CONFIG.values()]) + cls.add_to_class( + "urlconf_path", + models.CharField( + _("application"), + max_length=100, + choices=[(c["urls"], c["name"]) for c in cls.ALL_APPS_CONFIG.values()], + ), ) class ApplicationContentItemEditorForm(ItemEditorForm): - app_config = {} + app_config = {} custom_fields = {} def __init__(self, *args, **kwargs): - super(ApplicationContentItemEditorForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) instance = kwargs.get("instance", None) if instance: try: - self.app_config = cls.ALL_APPS_CONFIG[instance.urlconf_path]['config'] + # TODO use urlconf_path from POST if set + # urlconf_path = request.POST.get('...urlconf_path', + # instance.urlconf_path) + self.app_config = cls.ALL_APPS_CONFIG[instance.urlconf_path][ + "config" + ] except KeyError: self.app_config = {} self.custom_fields = {} - admin_fields = self.app_config.get('admin_fields', {}) + admin_fields = self.app_config.get("admin_fields", {}) if isinstance(admin_fields, dict): self.custom_fields.update(admin_fields) @@ -246,16 +250,12 @@ def __init__(self, *args, **kwargs): get_fields = get_object(admin_fields) self.custom_fields.update(get_fields(self, *args, **kwargs)) + params = self.instance.parameters for k, v in self.custom_fields.items(): + v.initial = params.get(k) self.fields[k] = v - - - def clean(self, *args, **kwargs): - cleaned_data = super(ApplicationContentItemEditorForm, self).clean(*args, **kwargs) - - # TODO: Check for newly added instances so we can force a re-validation of their custom fields - - return cleaned_data + if k in params: + self.fields[k].initial = params[k] def save(self, commit=True, *args, **kwargs): # Django ModelForms return the model instance from save. We'll @@ -263,143 +263,221 @@ def save(self, commit=True, *args, **kwargs): # get the model so we can set .parameters to the values of our # custom fields before calling save(commit=True) - m = super(ApplicationContentItemEditorForm, self).save(commit=False, *args, **kwargs) + m = super().save(commit=False, *args, **kwargs) - m.parameters = dict((k, self.cleaned_data[k]) for k in self.custom_fields if k in self.cleaned_data) + m.parameters = { + k: self.cleaned_data[k] + for k in self.custom_fields + if k in self.cleaned_data + } if commit: m.save(**kwargs) return m - #: This provides hooks for us to customize the admin interface for embedded instances: + # This provides hooks for us to customize the admin interface for + # embedded instances: cls.feincms_item_editor_form = ApplicationContentItemEditorForm - # Make sure the patched reverse() method has all information it needs - cls.parent.field.rel.to.register_request_processors(retrieve_page_information) - def __init__(self, *args, **kwargs): - super(ApplicationContent, self).__init__(*args, **kwargs) - self.app_config = self.ALL_APPS_CONFIG.get(self.urlconf_path, {}).get('config', {}) + super().__init__(*args, **kwargs) + self.app_config = self.ALL_APPS_CONFIG.get(self.urlconf_path, {}).get( + "config", {} + ) - def process(self, request, **kwargs): + def process(self, request, **kw): page_url = self.parent.get_absolute_url() - # Get the rest of the URL - # Provide a way for appcontent items to customize URL processing by # altering the perceived path of the page: if "path_mapper" in self.app_config: path_mapper = get_object(self.app_config["path_mapper"]) path, page_url = path_mapper( - request.path, - page_url, - appcontent_parameters=self.parameters + request.path, page_url, appcontent_parameters=self.parameters ) else: - path = request._feincms_extra_context['extra_path'] + path = request._feincms_extra_context["extra_path"] # Resolve the module holding the application urls. - urlconf_path = self.app_config.get('urls', self.urlconf_path) - - # Change the prefix and urlconf for the monkey-patched reverse function ... - _local.urlconf = (urlconf_path, page_url) + urlconf_path = self.app_config.get("urls", self.urlconf_path) try: fn, args, kwargs = resolve(path, urlconf_path) except (ValueError, Resolver404): - del _local.urlconf - raise Resolver404 + raise Resolver404( + f"Not found (resolving {path!r} in {urlconf_path!r} failed)" + ) - #: Variables from the ApplicationContent parameters are added to request - # so we can expose them to our templates via the appcontent_parameters - # context_processor + # Variables from the ApplicationContent parameters are added to request + # so we can expose them to our templates via the appcontent_parameters + # context_processor request._feincms_extra_context.update(self.parameters) + # Save the application configuration for reuse elsewhere + request._feincms_extra_context.update( + {"app_config": dict(self.app_config, urlconf_path=self.urlconf_path)} + ) + view_wrapper = self.app_config.get("view_wrapper", None) if view_wrapper: fn = partial( - get_object(view_wrapper), - view=fn, - appcontent_parameters=self.parameters + get_object(view_wrapper), view=fn, appcontent_parameters=self.parameters ) - try: - output = fn(request, *args, **kwargs) + output = fn(request, *args, **kwargs) - if isinstance(output, HttpResponse): - if self.send_directly(request, output): - return output - elif output.status_code == 200: + if isinstance(output, HttpResponse): + if self.send_directly(request, output): + return output + elif output.status_code == 200: + if self.unpack(request, output) and "view" in kw: + # Handling of @unpack and UnpackTemplateResponse + kw["view"].template_name = output.template_name + kw["view"].request._feincms_extra_context.update( + output.context_data + ) + else: # If the response supports deferred rendering, render the # response right now. We do not handle template response # middleware. - if hasattr(output, 'render') and callable(output.render): + if hasattr(output, "render") and callable(output.render): output.render() - self.rendered_result = mark_safe(output.content.decode('utf-8')) - self.rendered_headers = {} - # Copy relevant headers for later perusal - for h in ('Cache-Control', 'Last-Modified', 'Expires'): - if h in output: - self.rendered_headers.setdefault(h, []).append(output[h]) - else: - self.rendered_result = mark_safe(output) + self.rendered_result = mark_safe(output.content.decode("utf-8")) - finally: - # We want exceptions to propagate, but we cannot allow the - # modifications to reverse() to stay here. - del _local.urlconf + self.rendered_headers = {} + + # Copy relevant headers for later perusal + for h in ("Cache-Control", "Last-Modified", "Expires"): + if h in output: + self.rendered_headers.setdefault(h, []).append(output[h]) + + application_cookies = output.cookies + if application_cookies: + self.rendered_headers["X-Feincms-Cookie"] = application_cookies - return True # successful + elif isinstance(output, tuple) and "view" in kw: + kw["view"].template_name = output[0] + kw["view"].request._feincms_extra_context.update(output[1]) + + else: + self.rendered_result = mark_safe(output) + + return True # successful def send_directly(self, request, response): - return response.status_code != 200 or request.is_ajax() or getattr(response, 'standalone', False) + mimetype = response.get("Content-Type", "text/plain") + if ";" in mimetype: + mimetype = mimetype.split(";")[0] + mimetype = mimetype.strip() + + is_ajax = ( + request.is_ajax() + if hasattr(request, "is_ajax") + else request.headers.get("x-requested-with") == "XMLHttpRequest" + ) + return ( + response.status_code != 200 + or is_ajax + or getattr(response, "standalone", False) + or mimetype not in ("text/html", "text/plain") + ) + + def unpack(self, request, response): + return getattr(response, "_feincms_unpack", False) def render(self, **kwargs): - return getattr(self, 'rendered_result', u'') + return getattr(self, "rendered_result", "") def finalize(self, request, response): - headers = getattr(self, 'rendered_headers', None) + headers = getattr(self, "rendered_headers", None) if headers: self._update_response_headers(request, response, headers) - def save(self, *args, **kwargs): - super(ApplicationContent, self).save(*args, **kwargs) - # Clear reverse() cache - _empty_reverse_cache() - - def delete(self, *args, **kwargs): - super(ApplicationContent, self).delete(*args, **kwargs) - # Clear reverse() cache - _empty_reverse_cache() - def _update_response_headers(self, request, response, headers): """ Combine all headers that were set by the different content types We are interested in Cache-Control, Last-Modified, Expires """ - from django.utils.http import http_date - # Ideally, for the Cache-Control header, we'd want to do some intelligent - # combining, but that's hard. Let's just collect and unique them and let - # the client worry about that. - cc_headers = set(('must-revalidate',)) - for x in (cc.split(",") for cc in headers.get('Cache-Control', ())): - cc_headers |= set((s.strip() for s in x)) + # Ideally, for the Cache-Control header, we'd want to do some + # intelligent combining, but that's hard. Let's just collect and unique + # them and let the client worry about that. + cc_headers = {"must-revalidate"} + for x in (cc.split(",") for cc in headers.get("Cache-Control", ())): + cc_headers |= {s.strip() for s in x} if len(cc_headers): - response['Cache-Control'] = ", ".join(cc_headers) - else: # Default value - response['Cache-Control'] = 'no-cache, must-revalidate' + response["Cache-Control"] = ", ".join(cc_headers) + else: # Default value + response["Cache-Control"] = "no-cache, must-revalidate" # Check all Last-Modified headers, choose the latest one - lm_list = [parsedate(x) for x in headers.get('Last-Modified', ())] + lm_list = [parsedate(x) for x in headers.get("Last-Modified", ())] if len(lm_list) > 0: - response['Last-Modified'] = http_date(mktime(max(lm_list))) + response["Last-Modified"] = http_date(mktime(max(lm_list))) # Check all Expires headers, choose the earliest one - lm_list = [parsedate(x) for x in headers.get('Expires', ())] + lm_list = [parsedate(x) for x in headers.get("Expires", ())] if len(lm_list) > 0: - response['Expires'] = http_date(mktime(min(lm_list))) + response["Expires"] = http_date(mktime(min(lm_list))) + + # Add all cookies + cookies = headers.get("X-Feincms-Cookie", None) + if cookies: + for kookie, val in cookies.items(): + response.set_cookie( + kookie, + value=val.value, + max_age=val["max-age"], + expires=val["expires"], + path=val["path"], + domain=val["domain"], + secure=val["secure"], + httponly=val["httponly"], + samesite=val["samesite"], + ) + + @classmethod + def app_reverse_cache_key(cls, urlconf_path, **kwargs): + return "FEINCMS:{}:APPCONTENT:{}:{}".format( + getattr(settings, "SITE_ID", 0), + get_language(), + urlconf_path, + ) + + @classmethod + def closest_match(cls, urlconf_path): + try: + page_class = cls.parent.field.remote_field.model + except AttributeError: + page_class = cls.parent.field.rel.to + + contents = ( + cls.objects.filter( + parent__in=page_class.objects.active(), urlconf_path=urlconf_path + ) + .order_by("pk") + .select_related("parent") + ) + + if len(contents) > 1: + try: + current = short_language_code(get_language()) + return [ + content + for content in contents + if short_language_code(content.parent.language) == current + ][0] + + except (AttributeError, IndexError): + pass + + try: + return contents[0] + except IndexError: + pass + + return None diff --git a/feincms/content/comments/models.py b/feincms/content/comments/models.py deleted file mode 100644 index e5fd7c653..000000000 --- a/feincms/content/comments/models.py +++ /dev/null @@ -1,93 +0,0 @@ -# ------------------------------------------------------------------------ -# coding=utf-8 -# ------------------------------------------------------------------------ -# -# Created by Martin J. Laubach on 08.01.10. -# skyl wuz here (11.05.10) -# -# ------------------------------------------------------------------------ - -""" -Embed a comment list and comment form anywhere. Uses the standard -``django.contrib.comments`` application. -""" - -from django import forms -from django.contrib import comments -from django.contrib.comments.models import Comment -from django.db import models -from django.http import HttpResponseRedirect -from django.template import RequestContext -from django.template.loader import render_to_string -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ - - -# ------------------------------------------------------------------------ -class CommentsContent(models.Model): - comments_enabled = models.BooleanField(_('enabled'), default=True, help_text=_('New comments may be added')) - - class Meta: - abstract = True - verbose_name = _('comments') - verbose_name_plural = _('comments') - - @classmethod - def initialize_type(cls): - from feincms.admin.editor import ItemEditorForm - class CommentContentAdminForm(ItemEditorForm): - def __init__(self, *args, **kwargs): - super(CommentContentAdminForm, self).__init__(*args, **kwargs) - parent = kwargs.get('instance', None) - if parent is not None: - f = self.fields['comments_enabled'] - r = f.help_text - r += u'
' - for c in Comment.objects.for_model(parent.parent).order_by('-submit_date'): - r += '
# %d %s - %s
' % \ - ( c.id, c.id, c.comment[:80], c.is_public and _('public') or _('not public') ) - f.help_text = r - - cls.feincms_item_editor_form = CommentContentAdminForm - - def process(self, request, **kwargs): - parent_type = self.parent.__class__.__name__.lower() - - comment_page = self.parent - if hasattr(comment_page, 'original_translation') and comment_page.original_translation: - comment_page = comment_page.original_translation - - f = None - if self.comments_enabled and request.POST: - - # I guess the drawback is that this page can't handle any other types of posts - # just the comments for right now, but if we just post to the current path - # and handle it this way .. at least it works for now. - - #extra = request._feincms_extra_context.get('page_extra_path', ()) - #if len(extra) > 0 and extra[0] == u"post-comment": - - from django.contrib.comments.views.comments import post_comment - r = post_comment(request, next=comment_page.get_absolute_url()) - - if isinstance(r, HttpResponseRedirect): - return r - - f = comments.get_form()(comment_page, data=request.POST) - - if f is None: - f = comments.get_form()(comment_page) - - self.rendered_output = render_to_string([ - 'content/comments/%s.html' % parent_type, - 'content/comments/default-site.html', - 'content/comments/default.html', - ], RequestContext(request, { - 'content': self, - 'feincms_page': self.parent, - 'parent': comment_page, - 'form': f, - })) - - def render(self, **kwargs): - return getattr(self, 'rendered_output', u'') diff --git a/feincms/content/contactform/__init__.py b/feincms/content/contactform/__init__.py index e69de29bb..2b97afb5a 100644 --- a/feincms/content/contactform/__init__.py +++ b/feincms/content/contactform/__init__.py @@ -0,0 +1,10 @@ +# flake8: noqa + +import warnings + + +warnings.warn( + "The contactform content has been deprecated. Use form-designer instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/content/contactform/models.py b/feincms/content/contactform/models.py index 156b4a829..6699703cc 100644 --- a/feincms/content/contactform/models.py +++ b/feincms/content/contactform/models.py @@ -1,26 +1,24 @@ """ -Simple contact form for FeinCMS. The default form class has name, email, subject -and content fields, content being the only one which is not required. You can -provide your own comment form by passing an additional ``form=YourClass`` -argument to the ``create_content_type`` call. +Simple contact form for FeinCMS. The default form class has name, email, +subject and content fields, content being the only one which is not required. +You can provide your own comment form by passing an additional +``form=YourClass`` argument to the ``create_content_type`` call. """ from django import forms from django.core.mail import send_mail from django.db import models from django.http import HttpResponseRedirect -from django.template import RequestContext from django.template.loader import render_to_string -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class ContactForm(forms.Form): - name = forms.CharField(label=_('name')) - email = forms.EmailField(label=_('email')) - subject = forms.CharField(label=_('subject')) + name = forms.CharField(label=_("name")) + email = forms.EmailField(label=_("email")) + subject = forms.CharField(label=_("subject")) - content = forms.CharField(widget=forms.Textarea, required=False, - label=_('content')) + content = forms.CharField(widget=forms.Textarea, required=False, label=_("content")) class ContactFormContent(models.Model): @@ -31,8 +29,8 @@ class ContactFormContent(models.Model): class Meta: abstract = True - verbose_name = _('contact form') - verbose_name_plural = _('contact forms') + verbose_name = _("contact form") + verbose_name_plural = _("contact forms") @classmethod def initialize_type(cls, form=None): @@ -40,37 +38,40 @@ def initialize_type(cls, form=None): cls.form = form def process(self, request, **kwargs): - if request.GET.get('_cf_thanks'): - self.rendered_output = render_to_string('content/contactform/thanks.html', - context_instance=RequestContext(request)) + if request.GET.get("_cf_thanks"): + self.rendered_output = render_to_string( + "content/contactform/thanks.html", {"content": self}, request=request + ) return - if request.method == 'POST': + if request.method == "POST": form = self.form(request.POST) if form.is_valid(): send_mail( - form.cleaned_data['subject'], - render_to_string('content/contactform/email.txt', { - 'data': form.cleaned_data, - }), - form.cleaned_data['email'], + form.cleaned_data["subject"], + render_to_string( + "content/contactform/email.txt", {"data": form.cleaned_data} + ), + form.cleaned_data["email"], [self.email], - fail_silently=True) + fail_silently=True, + ) - return HttpResponseRedirect('?_cf_thanks=1') + return HttpResponseRedirect("?_cf_thanks=1") else: - initial = {'subject': self.subject} - if request.user.is_authenticated(): - initial['email'] = request.user.email - initial['name'] = request.user.get_full_name() + initial = {"subject": self.subject} + if request.user.is_authenticated: + initial["email"] = request.user.email + initial["name"] = request.user.get_full_name() form = self.form(initial=initial) - self.rendered_output = render_to_string('content/contactform/form.html', { - 'content': self, - 'form': form, - }, context_instance=RequestContext(request)) + self.rendered_output = render_to_string( + "content/contactform/form.html", + {"content": self, "form": form}, + request=request, + ) def render(self, **kwargs): - return getattr(self, 'rendered_output', u'') + return getattr(self, "rendered_output", "") diff --git a/feincms/content/file/models.py b/feincms/content/file/models.py index 1bb867ebe..7fc8bec58 100644 --- a/feincms/content/file/models.py +++ b/feincms/content/file/models.py @@ -3,26 +3,35 @@ instead. """ +import os + from django.db import models -from django.template.loader import render_to_string -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ + +from feincms import settings +from feincms.utils.tuple import AutoRenderTuple class FileContent(models.Model): - # You should probably use `feincms.content.medialibrary.models.MediaFileContent` - # instead. + # You should probably use + # `feincms.content.medialibrary.models.MediaFileContent` instead. title = models.CharField(max_length=200) - file = models.FileField(_('file'), upload_to='filecontent') + file = models.FileField( + _("file"), + max_length=255, + upload_to=os.path.join(settings.FEINCMS_UPLOAD_PREFIX, "filecontent"), + ) class Meta: abstract = True - verbose_name = _('file') - verbose_name_plural = _('files') + verbose_name = _("file") + verbose_name_plural = _("files") def render(self, **kwargs): - return render_to_string([ - 'content/file/%s.html' % self.region, - 'content/file/default.html', - ], {'content': self}) - + return AutoRenderTuple( + ( + ["content/file/%s.html" % self.region, "content/file/default.html"], + {"content": self}, + ) + ) diff --git a/example/__init__.py b/feincms/content/filer/__init__.py similarity index 100% rename from example/__init__.py rename to feincms/content/filer/__init__.py diff --git a/feincms/content/filer/models.py b/feincms/content/filer/models.py new file mode 100644 index 000000000..df48d7147 --- /dev/null +++ b/feincms/content/filer/models.py @@ -0,0 +1,117 @@ +from django.contrib import admin +from django.core.exceptions import ImproperlyConfigured +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from feincms.admin.item_editor import FeinCMSInline +from feincms.utils.tuple import AutoRenderTuple + + +try: + from filer.fields.file import FilerFileField + from filer.fields.image import FilerImageField +except ImportError: + __all__ = () + +else: + __all__ = ( + "MediaFileContentInline", + "ContentWithFilerFile", + "FilerFileContent", + "FilerImageContent", + ) + + class MediaFileContentInline(FeinCMSInline): + radio_fields = {"type": admin.VERTICAL} + + class ContentWithFilerFile(models.Model): + """ + File content + """ + + feincms_item_editor_inline = MediaFileContentInline + + class Meta: + abstract = True + + def render(self, **kwargs): + return AutoRenderTuple( + ( + [ + f"content/filer/{self.file_type}_{self.type}.html", + "content/filer/%s.html" % self.type, + "content/filer/%s.html" % self.file_type, + "content/filer/default.html", + ], + {"content": self}, + ) + ) + + class FilerFileContent(ContentWithFilerFile): + mediafile = FilerFileField( + verbose_name=_("file"), related_name="+", on_delete=models.CASCADE + ) + file_type = "file" + type = "download" + + class Meta: + abstract = True + verbose_name = _("file") + verbose_name_plural = _("files") + + class FilerImageContent(ContentWithFilerFile): + """ + Create a media file content as follows:: + + from feincms.contents import FilerImageContent + Page.create_content_type(FilerImageContent, TYPE_CHOICES=( + ('inline', _('Default')), + ('lightbox', _('Lightbox')), + ('whatever', _('Whatever')), + )) + + For a media file of type 'image' and type 'lightbox', the following + templates are tried in order: + + * content/mediafile/image_lightbox.html + * content/mediafile/lightbox.html + * content/mediafile/image.html + * content/mediafile/default.html + + The context contains ``content`` and ``request`` (if available). + + The content.mediafile attribute are as follows (selection): + label, description, default_caption, default_alt_text, + author, must_always_publish_author_credit, + must_always_publish_copyright, date_taken, file, id, is_public, url + """ + + mediafile = FilerImageField( + verbose_name=_("image"), related_name="+", on_delete=models.CASCADE + ) + caption = models.CharField(_("caption"), max_length=1000, blank=True) + url = models.CharField(_("URL"), max_length=1000, blank=True) + + file_type = "image" + + class Meta: + abstract = True + verbose_name = _("image") + verbose_name_plural = _("images") + + @classmethod + def initialize_type(cls, TYPE_CHOICES=None): + if TYPE_CHOICES is None: + raise ImproperlyConfigured( + "You have to set TYPE_CHOICES when creating a %s" % cls.__name__ + ) + + cls.add_to_class( + "type", + models.CharField( + _("type"), + max_length=20, + choices=TYPE_CHOICES, + default=TYPE_CHOICES[0][0], + ), + ) diff --git a/feincms/content/image/models.py b/feincms/content/image/models.py index 3d8954419..1593ba5ec 100644 --- a/feincms/content/image/models.py +++ b/feincms/content/image/models.py @@ -3,43 +3,92 @@ instead. """ -from django.core.exceptions import ImproperlyConfigured +import os + from django.db import models -from django.template.loader import render_to_string -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ + +from feincms import settings +from feincms.templatetags import feincms_thumbnail +from feincms.utils.tuple import AutoRenderTuple + class ImageContent(models.Model): - # You should probably use `feincms.content.medialibrary.models.MediaFileContent` - # instead. + # You should probably use + # `feincms.content.medialibrary.models.MediaFileContent` instead. """ Create an ImageContent like this:: - Cls.create_content_type(ImageContent, POSITION_CHOICES=( - ('left', 'Left'), - ('right', 'Right'), + Cls.create_content_type( + ImageContent, + POSITION_CHOICES=( + ('left', 'Float to left'), + ('right', 'Float to right'), + ('block', 'Block'), + ), + FORMAT_CHOICES=( + ('noop', 'Do not resize'), + ('cropscale:100x100', 'Square Thumbnail'), + ('cropscale:200x450', 'Medium Portait'), + ('thumbnail:1000x1000', 'Large'), )) + + Note that FORMAT_CHOICES is optional. The part before the colon + corresponds to the template filters in the ``feincms_thumbnail`` + template filter library. Known values are ``cropscale`` and + ``thumbnail``. Everything else (such as ``noop``) is ignored. """ - image = models.ImageField(_('image'), upload_to='imagecontent') + image = models.ImageField( + _("image"), + max_length=255, + upload_to=os.path.join(settings.FEINCMS_UPLOAD_PREFIX, "imagecontent"), + ) + alt_text = models.CharField( + _("alternate text"), + max_length=255, + blank=True, + help_text=_("Description of image"), + ) + caption = models.CharField(_("caption"), max_length=255, blank=True) class Meta: abstract = True - verbose_name = _('image') - verbose_name_plural = _('images') + verbose_name = _("image") + verbose_name_plural = _("images") def render(self, **kwargs): - return render_to_string([ - 'content/image/%s.html' % self.position, - 'content/image/default.html', - ], {'content': self}) + templates = ["content/image/default.html"] + if hasattr(self, "position"): + templates.insert(0, "content/image/%s.html" % self.position) - @classmethod - def initialize_type(cls, POSITION_CHOICES=None): - if POSITION_CHOICES is None: - raise ImproperlyConfigured, 'You need to set POSITION_CHOICES when creating a %s' % cls.__name__ + return AutoRenderTuple((templates, {"content": self})) + + def get_image(self): + img_type, _, size = getattr(self, "format", "").partition(":") + if not size: + return self.image - models.CharField(_('position'), max_length=10, choices=POSITION_CHOICES, - default=POSITION_CHOICES[0][0] - ).contribute_to_class(cls, 'position') + thumbnailer = {"cropscale": feincms_thumbnail.CropscaleThumbnailer}.get( + img_type, feincms_thumbnail.Thumbnailer + ) + return thumbnailer(self.image, size) + + @classmethod + def initialize_type(cls, POSITION_CHOICES=None, FORMAT_CHOICES=None): + if POSITION_CHOICES: + models.CharField( + _("position"), + max_length=10, + choices=POSITION_CHOICES, + default=POSITION_CHOICES[0][0], + ).contribute_to_class(cls, "position") + if FORMAT_CHOICES: + models.CharField( + _("format"), + max_length=64, + choices=FORMAT_CHOICES, + default=FORMAT_CHOICES[0][0], + ).contribute_to_class(cls, "format") diff --git a/feincms/content/medialibrary/models.py b/feincms/content/medialibrary/models.py index 4105d7fb3..3c575e56c 100644 --- a/feincms/content/medialibrary/models.py +++ b/feincms/content/medialibrary/models.py @@ -1,134 +1,10 @@ -""" -Media library-based file inclusion tool. Can handle any type of media file, -not only images. -""" +# flake8: noqa import warnings -from django import forms -from django.conf import settings -from django.contrib.admin.widgets import AdminRadioSelect -from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist -from django.db import models -from django.template.loader import render_to_string -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ -from feincms.admin.editor import ItemEditorForm -from feincms.module.medialibrary.models import MediaFile -from feincms.templatetags import feincms_thumbnail - - -class MediaFileWidget(forms.TextInput): - """ - TextInput widget, shows a link to the current value if there is one. - """ - - def render(self, name, value, attrs=None): - inputfield = super(MediaFileWidget, self).render(name, value, attrs) - if value: - try: - mf = MediaFile.objects.get(pk=value) - except MediaFile.DoesNotExist: - return inputfield - - try: - caption = mf.translation.caption - except ObjectDoesNotExist: - caption = _('(no caption)') - - if mf.type == 'image': - image = feincms_thumbnail.thumbnail(mf.file.name, '240x120') - image = u'
' % {'url': image} - else: - image = u'' - - return mark_safe(u""" -
%(image)s - %(caption)s - %(url)s
- %(inputfield)s -
""" % { - 'image': image, - 'url': mf.file.url, - 'caption': caption, - 'inputfield': inputfield}) - - return inputfield - - -# FeinCMS connector -class MediaFileContent(models.Model): - """ - Create a media file content as follows:: - - Page.create_content_type(MediaFileContent, POSITION_CHOICES=( - ('default', _('Default')), - ('lightbox', _('Lightbox')), - ('whatever', _('Whatever')), - )) - - For a media file of type 'image' and position 'lightbox', the following - templates are tried in order: - - * content/mediafile/image_lightbox.html - * content/mediafile/image.html - * content/mediafile/lightbox.html - * content/mediafile/default.html - - The context contains ``content`` and ``request`` (if available). - """ - - feincms_item_editor_includes = { - 'head': ['admin/content/mediafile/init.html'], - } - - class Meta: - abstract = True - verbose_name = _('media file') - verbose_name_plural = _('media files') - - @classmethod - def initialize_type(cls, POSITION_CHOICES=None, MEDIAFILE_CLASS=MediaFile): - warnings.warn('feincms.content.medialibrary.models.MediaFileContent is deprecated.' - ' Use feincms.content.medialibrary.v2.MediaFileContent instead.', - DeprecationWarning) - if 'feincms.module.medialibrary' not in settings.INSTALLED_APPS: - raise ImproperlyConfigured, 'You have to add \'feincms.module.medialibrary\' to your INSTALLED_APPS before creating a %s' % cls.__name__ - - if POSITION_CHOICES is None: - raise ImproperlyConfigured, 'You need to set POSITION_CHOICES when creating a %s' % cls.__name__ - - cls.add_to_class('mediafile', models.ForeignKey(MEDIAFILE_CLASS, verbose_name=_('media file'), - related_name='%s_%s_set' % (cls._meta.app_label, cls._meta.module_name) - )) - - cls.add_to_class('position', models.CharField(_('position'), - max_length=10, choices=POSITION_CHOICES, - default=POSITION_CHOICES[0][0])) - - class MediaFileContentAdminForm(ItemEditorForm): - mediafile = forms.ModelChoiceField(queryset=MEDIAFILE_CLASS.objects.all(), - widget=MediaFileWidget, label=_('media file')) - position = forms.ChoiceField(choices=POSITION_CHOICES, - initial=POSITION_CHOICES[0][0], label=_('position'), - widget=AdminRadioSelect(attrs={'class': 'radiolist'})) - - cls.feincms_item_editor_form = MediaFileContentAdminForm - - def render(self, **kwargs): - request = kwargs.get('request') - return render_to_string([ - 'content/mediafile/%s_%s.html' % (self.mediafile.type, self.position), - 'content/mediafile/%s.html' % self.mediafile.type, - 'content/mediafile/%s.html' % self.position, - 'content/mediafile/default.html', - ], { 'content': self, 'request': request }) - - - @classmethod - def default_create_content_type(cls, cms_model): - return cms_model.create_content_type(cls, POSITION_CHOICES=( - ('block', _('block')), - ('left', _('left')), - ('right', _('right')), - )) +warnings.warn( + "Import MediaFileContent from feincms.module.medialibrary.contents.", + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/content/medialibrary/v2.py b/feincms/content/medialibrary/v2.py deleted file mode 100644 index 901e3a75f..000000000 --- a/feincms/content/medialibrary/v2.py +++ /dev/null @@ -1,63 +0,0 @@ -from django.contrib import admin -from django.db import models -from django.template.loader import render_to_string -from django.utils.translation import ugettext_lazy as _ - -from feincms.admin.item_editor import FeinCMSInline -from feincms.module.medialibrary.fields import ContentWithMediaFile - - -class MediaFileContentInline(FeinCMSInline): - raw_id_fields = ('mediafile',) - radio_fields = {'type': admin.VERTICAL} - - -class MediaFileContent(ContentWithMediaFile): - """ - Rehashed, backwards-incompatible media file content which does not contain - the problems from v1 anymore. - - Create a media file content as follows:: - - from feincms.content.medialibrary.v2 import MediaFileContent - Page.create_content_type(MediaFileContent, TYPE_CHOICES=( - ('default', _('Default')), - ('lightbox', _('Lightbox')), - ('whatever', _('Whatever')), - )) - - For a media file of type 'image' and type 'lightbox', the following - templates are tried in order: - - * content/mediafile/image_lightbox.html - * content/mediafile/image.html - * content/mediafile/lightbox.html - * content/mediafile/default.html - - The context contains ``content`` and ``request`` (if available). - """ - - feincms_item_editor_inline = MediaFileContentInline - - class Meta: - abstract = True - verbose_name = _('media file') - verbose_name_plural = _('media files') - - @classmethod - def initialize_type(cls, TYPE_CHOICES=None): - cls.add_to_class('type', models.CharField(_('type'), - max_length=20, choices=TYPE_CHOICES, default=TYPE_CHOICES[0][0])) - - def render(self, **kwargs): - ctx = {'content': self} - ctx.update(kwargs) - - return render_to_string([ - 'content/mediafile/%s_%s.html' % (self.mediafile.type, self.type), - 'content/mediafile/%s.html' % self.mediafile.type, - 'content/mediafile/%s.html' % self.type, - 'content/mediafile/default.html', - ], ctx) - - diff --git a/feincms/content/raw/models.py b/feincms/content/raw/models.py index 213e2bb6d..ca8068642 100644 --- a/feincms/content/raw/models.py +++ b/feincms/content/raw/models.py @@ -1,6 +1,6 @@ from django.db import models from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class RawContent(models.Model): @@ -11,13 +11,12 @@ class RawContent(models.Model): snippets too. """ - text = models.TextField(_('content'), blank=True) + text = models.TextField(_("content"), blank=True) class Meta: abstract = True - verbose_name = _('raw content') - verbose_name_plural = _('raw contents') + verbose_name = _("raw content") + verbose_name_plural = _("raw contents") def render(self, **kwargs): return mark_safe(self.text) - diff --git a/feincms/content/richtext/models.py b/feincms/content/richtext/models.py index bf528ab17..cd698ee69 100644 --- a/feincms/content/richtext/models.py +++ b/feincms/content/richtext/models.py @@ -1,66 +1,10 @@ -from django import forms -from django.core.exceptions import ImproperlyConfigured from django.db import models -from django.forms.util import ErrorList -from django.template.loader import render_to_string -from django.utils.html import escape -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from feincms import settings -from feincms.admin.editor import ItemEditorForm -from feincms.utils import get_object +from feincms.contrib.richtext import RichTextField +from feincms.utils.tuple import AutoRenderTuple -class RichTextContentAdminForm(ItemEditorForm): - text = forms.CharField(widget=forms.Textarea, required=False, label=_('text')) - - def __init__(self, *args, **kwargs): - super(RichTextContentAdminForm, self).__init__(*args, **kwargs) - self.fields['text'].widget.attrs.update({'class': 'item-richtext'}) - - #: If FEINCMS_TIDY_ALLOW_WARNINGS_OVERRIDE allows, we'll convert this into - # a checkbox so the user can choose whether to ignore HTML validation - # warnings instead of fixing them: - seen_tidy_warnings = forms.BooleanField( - required=False, - label=_("HTML Tidy"), - help_text=_("Ignore the HTML validation warnings"), - widget=forms.HiddenInput - ) - - def clean(self): - cleaned_data = super(RichTextContentAdminForm, self).clean() - - if settings.FEINCMS_TIDY_HTML: - text, errors, warnings = get_object(settings.FEINCMS_TIDY_FUNCTION)(cleaned_data['text']) - - # Ick, but we need to be able to update text and seen_tidy_warnings: - self.data = self.data.copy() - - # We always replace the HTML with the tidied version: - cleaned_data['text'] = text - self.data['%s-text' % self.prefix] = text - - if settings.FEINCMS_TIDY_SHOW_WARNINGS and (errors or warnings): - if settings.FEINCMS_TIDY_ALLOW_WARNINGS_OVERRIDE: - # Convert the ignore input from hidden to Checkbox so the user can change it: - self.fields['seen_tidy_warnings'].widget = forms.CheckboxInput() - - if errors or not (settings.FEINCMS_TIDY_ALLOW_WARNINGS_OVERRIDE and cleaned_data['seen_tidy_warnings']): - self._errors["text"] = ErrorList([mark_safe( - _("HTML validation produced %(count)d warnings. Please review the updated content below before continuing: %(messages)s") % { - "count": len(warnings) + len(errors), - "messages": '
  • %s
' % "
  • ".join(map(escape, errors + warnings)) - } - )]) - - # If we're allowed to ignore warnings and we don't have any - # errors we'll set our hidden form field to allow the user to - # ignore warnings on the next submit: - if not errors and settings.FEINCMS_TIDY_ALLOW_WARNINGS_OVERRIDE: - self.data["%s-seen_tidy_warnings" % self.prefix] = True - - return cleaned_data class RichTextContent(models.Model): """ @@ -68,48 +12,26 @@ class RichTextContent(models.Model): anything you want using ``FEINCMS_RICHTEXT_INIT_CONTEXT`` and ``FEINCMS_RICHTEXT_INIT_TEMPLATE``. + If you are using TinyMCE 4.x then ``FEINCMS_RICHTEXT_INIT_TEMPLATE`` + needs to be set to ``admin/content/richtext/init_tinymce4.html``. + Optionally runs the HTML code through HTML cleaners if you specify ``cleanse=True`` when calling ``create_content_type``. """ - form = RichTextContentAdminForm - feincms_item_editor_form = RichTextContentAdminForm - feincms_item_editor_context_processors = ( lambda x: settings.FEINCMS_RICHTEXT_INIT_CONTEXT, ) - feincms_item_editor_includes = { - 'head': [ settings.FEINCMS_RICHTEXT_INIT_TEMPLATE ], - } - - text = models.TextField(_('text'), blank=True) + feincms_item_editor_includes = {"head": [settings.FEINCMS_RICHTEXT_INIT_TEMPLATE]} class Meta: abstract = True - verbose_name = _('rich text') - verbose_name_plural = _('rich texts') + verbose_name = _("rich text") + verbose_name_plural = _("rich texts") def render(self, **kwargs): - request = kwargs.get('request') - return render_to_string('content/richtext/default.html', - { 'content': self, 'request': request }) - - def save(self, *args, **kwargs): - # TODO: Move this to the form? - if getattr(self, 'cleanse', False): - from feincms.utils.html.cleanse import cleanse_html - self.text = cleanse_html(self.text) - super(RichTextContent, self).save(*args, **kwargs) + return AutoRenderTuple(("content/richtext/default.html", {"content": self})) @classmethod - def initialize_type(cls, cleanse=False): - cls.cleanse = cleanse - - # TODO: Move this into somewhere more generic: - if settings.FEINCMS_TIDY_HTML: - # Make sure we can load the tidy function without dependency failures: - try: - get_object(settings.FEINCMS_TIDY_FUNCTION) - except ImportError, e: - raise ImproperlyConfigured("FEINCMS_TIDY_HTML is enabled but the HTML tidy function %s could not be imported: %s" % (settings.FEINCMS_TIDY_FUNCTION, e)) - + def initialize_type(cls, cleanse=None): + cls.add_to_class("text", RichTextField(_("text"), blank=True, cleanse=cleanse)) diff --git a/feincms/content/rss/models.py b/feincms/content/rss/models.py deleted file mode 100644 index 52366acb4..000000000 --- a/feincms/content/rss/models.py +++ /dev/null @@ -1,52 +0,0 @@ -import time -from datetime import datetime - -from django.db import models -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ -from django.template.loader import render_to_string - -import feedparser - - -class RSSContent(models.Model): - """ - RSS feed inclusion content. - - This content requires a cronjob on the server, which runs - ``./manage.py update_rsscontent`` every couple of hours. - """ - - title = models.CharField(_('title'), max_length=50, - help_text=_('The rss field is updated several times a day. A change in the title will only be visible on the home page after the next feed update.')) - link = models.URLField(_('link')) - rendered_content = models.TextField(_('pre-rendered content'), blank=True, editable=False) - last_updated = models.DateTimeField(_('last updated'), blank=True, null=True) - max_items = models.IntegerField(_('max. items'), default=5) - - class Meta: - abstract = True - verbose_name = _('RSS feed') - verbose_name_plural = _('RSS feeds') - - def render(self, **kwargs): - return mark_safe(self.rendered_content) - - def cache_content(self, date_format=None, save=True): - feed = feedparser.parse(self.link) - entries = feed['entries'][:self.max_items] - if date_format: - for entry in entries: - entry.updated = time.strftime(date_format, - entry.updated_parsed) - - self.rendered_content = render_to_string('content/rss/content.html', { - 'feed_title': self.title, - 'feed_link': feed['feed']['link'], - 'entries': entries, - }) - self.last_updated = datetime.now() - - if save: - self.save() - diff --git a/feincms/content/section/models.py b/feincms/content/section/models.py index 52d96067c..48f344cc7 100644 --- a/feincms/content/section/models.py +++ b/feincms/content/section/models.py @@ -1,17 +1,20 @@ -from django import forms from django.conf import settings as django_settings -from django.contrib.admin.widgets import AdminRadioSelect -from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist +from django.contrib import admin +from django.core.exceptions import ImproperlyConfigured from django.db import models -from django.template.loader import render_to_string -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from feincms import settings -from feincms.admin.editor import ItemEditorForm +from feincms.admin.item_editor import FeinCMSInline +from feincms.contrib.richtext import RichTextField +from feincms.module.medialibrary.fields import MediaFileForeignKey from feincms.module.medialibrary.models import MediaFile +from feincms.utils.tuple import AutoRenderTuple -from feincms.content.medialibrary.models import MediaFileWidget + +class SectionContentInline(FeinCMSInline): + raw_id_fields = ("mediafile",) + radio_fields = {"type": admin.VERTICAL} class SectionContent(models.Model): @@ -19,76 +22,88 @@ class SectionContent(models.Model): Title, media file and rich text fields in one content block. """ + feincms_item_editor_inline = SectionContentInline feincms_item_editor_context_processors = ( lambda x: settings.FEINCMS_RICHTEXT_INIT_CONTEXT, ) - feincms_item_editor_includes = { - 'head': [ - settings.FEINCMS_RICHTEXT_INIT_TEMPLATE, - 'admin/content/mediafile/init.html', - ], - } - - title = models.CharField(_('title'), max_length=200, blank=True) - richtext = models.TextField(_('text'), blank=True) + feincms_item_editor_includes = {"head": [settings.FEINCMS_RICHTEXT_INIT_TEMPLATE]} + + title = models.CharField(_("title"), max_length=200, blank=True) + richtext = RichTextField(_("text"), blank=True) + mediafile = MediaFileForeignKey( + MediaFile, + on_delete=models.CASCADE, + verbose_name=_("media file"), + related_name="+", + blank=True, + null=True, + ) class Meta: abstract = True - verbose_name = _('section') - verbose_name_plural = _('sections') + verbose_name = _("section") + verbose_name_plural = _("sections") @classmethod - def initialize_type(cls, TYPE_CHOICES=None, cleanse=False): - if 'feincms.module.medialibrary' not in django_settings.INSTALLED_APPS: - raise ImproperlyConfigured, 'You have to add \'feincms.module.medialibrary\' to your INSTALLED_APPS before creating a %s' % cls.__name__ + def initialize_type(cls, TYPE_CHOICES=None, cleanse=None): + if "feincms.module.medialibrary" not in django_settings.INSTALLED_APPS: + raise ImproperlyConfigured( + "You have to add 'feincms.module.medialibrary' to your" + " INSTALLED_APPS before creating a %s" % cls.__name__ + ) if TYPE_CHOICES is None: - raise ImproperlyConfigured, 'You need to set TYPE_CHOICES when creating a %s' % cls.__name__ - - cls.add_to_class('mediafile', models.ForeignKey(MediaFile, verbose_name=_('media file'), - related_name='%s_%s_set' % (cls._meta.app_label, cls._meta.module_name), - blank=True, null=True, - )) - - cls.add_to_class('type', models.CharField(_('type'), - max_length=10, choices=TYPE_CHOICES, - default=TYPE_CHOICES[0][0])) - - class MediaFileContentAdminForm(ItemEditorForm): - mediafile = forms.ModelChoiceField(queryset=MediaFile.objects.all(), - widget=MediaFileWidget, required=False) - type = forms.ChoiceField(choices=TYPE_CHOICES, - initial=TYPE_CHOICES[0][0], label=_('type'), - widget=AdminRadioSelect(attrs={'class': 'radiolist'})) - - def __init__(self, *args, **kwargs): - super(MediaFileContentAdminForm, self).__init__(*args, **kwargs) - self.fields['richtext'].widget.attrs.update({'class': 'item-richtext'}) - - cls.feincms_item_editor_form = MediaFileContentAdminForm - cls.form = MediaFileContentAdminForm - cls.cleanse = cleanse + raise ImproperlyConfigured( + "You need to set TYPE_CHOICES when creating a %s" % cls.__name__ + ) + + cls.add_to_class( + "type", + models.CharField( + _("type"), + max_length=10, + choices=TYPE_CHOICES, + default=TYPE_CHOICES[0][0], + ), + ) + + if cleanse: + cls.cleanse = cleanse @classmethod def get_queryset(cls, filter_args): # Explicitly add nullable FK mediafile to minimize the DB query count - return cls.objects.select_related('parent', 'mediafile').filter(filter_args) + return cls.objects.select_related("parent", "mediafile").filter(filter_args) def render(self, **kwargs): if self.mediafile: mediafile_type = self.mediafile.type else: - mediafile_type = 'nomedia' - - return render_to_string([ - 'content/section/%s_%s.html' % (self.type, mediafile_type), - 'content/section/%s.html' % self.type, - 'content/section/%s.html' % mediafile_type, - 'content/section/default.html', - ], {'content': self}) + mediafile_type = "nomedia" + + return AutoRenderTuple( + ( + [ + f"content/section/{mediafile_type}_{self.type}.html", + "content/section/%s.html" % mediafile_type, + "content/section/%s.html" % self.type, + "content/section/default.html", + ], + {"content": self}, + ) + ) def save(self, *args, **kwargs): - if getattr(self, 'cleanse', False): - from feincms.utils.html.cleanse import cleanse_html - self.richtext = cleanse_html(self.richtext) - super(SectionContent, self).save(*args, **kwargs) + if getattr(self, "cleanse", None): + try: + # Passes the rich text content as first argument because + # the passed callable has been converted into a bound method + self.richtext = self.cleanse(self.richtext) + except TypeError: + # Call the original callable, does not pass the rich richtext + # content instance along + self.richtext = self.cleanse.im_func(self.richtext) + + super().save(*args, **kwargs) + + save.alters_data = True diff --git a/feincms/content/table/models.py b/feincms/content/table/models.py deleted file mode 100644 index 1cf9f2a87..000000000 --- a/feincms/content/table/models.py +++ /dev/null @@ -1,96 +0,0 @@ -from django.db import models -from django.utils import simplejson -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ - - -class TableFormatter(object): - """ - Table formatter which should convert a structure of nested lists into - a suitable HTML table representation. - """ - - def __init__(self, **kwargs): - for k, v in kwargs.items(): - setattr(self, k, v) - - def __call__(self, data): - return self.format_table(data) - - def format_table(self, data): - return u'%s
    ' % u''.join( - self.format_row(index, row) for index, row in enumerate(data)) - - def format_row(self, index, row): - self.row_index = index - return u'%s' % u''.join( - self.format_cell(index, cell) for index, cell in enumerate(row)) - - def format_cell(self, index, cell): - return u'%s' % cell - - -class TitleTableFormatter(TableFormatter): - """ - TitleTableFormatter(first_row_title=True, first_column_title=True) - """ - - def format_cell(self, index, cell): - if (not self.row_index and getattr(self, 'first_row_title', True)) or \ - (not index and getattr(self, 'first_column_title', True)): - return u'%s' % cell - return u'%s' % cell - - -class TableContent(models.Model): - """ - Content to edit and display HTML tables in the CMS. - - The standard rich text editor configuration in FeinCMS does not activate - the table plugin. This content type can be used to edit and display - nicely formatted HTML tables. It is easy to specify your own table - renderers. - """ - - feincms_item_editor_includes = { - 'head': ['admin/content/table/init.html'], - } - - html = models.TextField('HTML', blank=True, editable=False) - - DEFAULT_TYPES = [ - ('plain', _('plain'), TableFormatter()), - ('titlerow', _('title row'), TitleTableFormatter( - first_row_title=True, first_column_title=False)), - ('titlerowcol', _('title row and column'), TitleTableFormatter( - first_row_title=True, first_column_title=True)), - ] - - class Meta: - abstract = True - verbose_name = _('table') - verbose_name_plural = _('tables') - - @classmethod - def initialize_type(cls, TYPES=None): - TYPES = TYPES or cls.DEFAULT_TYPES - - cls.FORMATTERS = dict((t[0], t[2]) for t in TYPES) - cls.TYPE_CHOICES = [(t[0], t[1]) for t in TYPES] - - cls.add_to_class('type', models.CharField(_('type'), max_length=20, - choices=cls.TYPE_CHOICES, - default=cls.TYPE_CHOICES[0][0])) - - # Add after type, so that type comes before data in admin interface - cls.add_to_class('data', models.TextField(_('data'), blank=True)) - - def render(self, **kwargs): - return mark_safe(self.html) - - def save(self, *args, **kwargs): - # XXX ugly, but otherwise the decoder raises exceptions - self.data = self.data.replace('\r', '\\r').replace('\n', '\\n').replace('\t', '\\t') - self.html = self.data and self.FORMATTERS[self.type](simplejson.loads(self.data)) or u'' - - super(TableContent, self).save(*args, **kwargs) diff --git a/feincms/content/template/models.py b/feincms/content/template/models.py index efff1d426..57e99ab70 100644 --- a/feincms/content/template/models.py +++ b/feincms/content/template/models.py @@ -1,70 +1,34 @@ -import os - -from django import forms -from django.conf import settings from django.db import models -from django.template.loader import find_template_loader, render_to_string -from django.utils.translation import ugettext_lazy as _ - -from feincms.admin.editor import ItemEditorForm - - -def get_templates(): - seen = set() - - yield ('', '----------') - - for loader in settings.TEMPLATE_LOADERS: - loader_instance = find_template_loader(loader) - if not loader_instance or not hasattr(loader_instance, 'get_template_sources'): - continue - - for basepath in loader_instance.get_template_sources('.'): - path = os.path.join(basepath, 'content', 'template') - try: - templates = os.listdir(path) - except (OSError, IOError): - continue - - for template in templates: - if template in seen: - continue - if template[:4] == '.tmp': - continue - seen.add(template) - yield (template, template) +from django.utils.translation import gettext_lazy as _ - -class TemplateContentAdminForm(ItemEditorForm): - filename = forms.ChoiceField(label=_('template')) - - def __init__(self, *args, **kwargs): - super(TemplateContentAdminForm, self).__init__(*args, **kwargs) - self.fields['filename'].choices = sorted(get_templates(), key=lambda p: p[1]) +from feincms.content.raw.models import RawContent # noqa +from feincms.content.richtext.models import RichTextContent # noqa +from feincms.utils.tuple import AutoRenderTuple class TemplateContent(models.Model): """ - This content type scans all template folders for files in the - ``content/template/`` folder and lets the website administrator select - any template from a set of provided choices. - - The templates aren't restricted in any way. + Pass a list of templates when creating this content type. It uses the + default template system:: + + Page.create_content_type(TemplateContent, TEMPLATES=[ + ('content/template/something1.html', 'something'), + ('content/template/something2.html', 'something else'), + ('base.html', 'makes no sense'), + ]) """ - feincms_item_editor_form = TemplateContentAdminForm - - filename = models.CharField(_('template'), max_length=100, - choices=()) - class Meta: abstract = True - verbose_name = _('template content') - verbose_name_plural = _('template contents') + verbose_name = _("template content") + verbose_name_plural = _("template contents") - def render(self, **kwargs): - context = kwargs.pop('context', None) + @classmethod + def initialize_type(cls, TEMPLATES): + cls.add_to_class( + "template", + models.CharField(_("template"), max_length=100, choices=TEMPLATES), + ) - return render_to_string('content/template/%s' % self.filename, dict({ - 'content': self, - }, **kwargs), context_instance=context) + def render(self, **kwargs): + return AutoRenderTuple((self.template, {"content": self})) diff --git a/feincms/content/video/models.py b/feincms/content/video/models.py index f8bf0f93d..e6f67da70 100644 --- a/feincms/content/video/models.py +++ b/feincms/content/video/models.py @@ -1,8 +1,9 @@ import re from django.db import models -from django.template.loader import render_to_string -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ + +from feincms.utils.tuple import AutoRenderTuple class VideoContent(models.Model): @@ -10,31 +11,64 @@ class VideoContent(models.Model): Copy-paste a URL to youtube or vimeo into the text box, this content type will automatically generate the necessary embed code. - Other portals aren't supported currently, but would be easy to add if anyone - would take up the baton. + Other portals aren't supported currently, but would be easy to add if + anyone would take up the baton. + + You should probably use feincms-oembed. """ PORTALS = ( - ('youtube', re.compile(r'youtube'), lambda url: {'v': re.search(r'[?&]v=([^#&]+)', url).group(1)}), - ('vimeo', re.compile(r'vimeo'), lambda url: {'id': re.search(r'/(\d+)', url).group(1)}), - ('sf', re.compile(r'sf\.tv'), lambda url: {'id': re.search(r'/([a-z0-9\-]+)', url).group(1)}), - ) + ( + "youtube", + re.compile(r"youtube"), + lambda url: {"v": re.search(r"([?&]v=|./././)([^#&]+)", url).group(2)}, + ), + ( + "vimeo", + re.compile(r"vimeo"), + lambda url: {"id": re.search(r"/(\d+)", url).group(1)}, + ), + ( + "sf", + re.compile(r"sf\.tv"), + lambda url: {"id": re.search(r"/([a-z0-9\-]+)", url).group(1)}, + ), + ) - video = models.URLField(_('video link'), - help_text=_('This should be a link to a youtube or vimeo video, i.e.: http://www.youtube.com/watch?v=zmj1rpzDRZ0')) + video = models.URLField( + _("video link"), + help_text=_( + "This should be a link to a youtube or vimeo video," + " i.e.: http://www.youtube.com/watch?v=zmj1rpzDRZ0" + ), + ) class Meta: abstract = True - verbose_name = _('video') - verbose_name_plural = _('videos') + verbose_name = _("video") + verbose_name_plural = _("videos") - def render(self, **kwargs): + def get_context_dict(self): + "Extend this if you need more variables passed to template" + return {"content": self, "portal": "unknown"} + + def get_templates(self, portal="unknown"): + "Extend/override this if you want to modify the templates used" + return ["content/video/%s.html" % portal, "content/video/unknown.html"] + + def ctx_for_video(self, vurl): + "Get a context dict for a given video URL" + ctx = self.get_context_dict() for portal, match, context_fn in self.PORTALS: - if match.search(self.video): - return render_to_string([ - 'content/video/%s.html' % portal, - 'content/video/unknown.html', - ], dict(context_fn(self.video), content=self)) - - return render_to_string('content/video/unknown.html', { - 'content': self}) + if match.search(vurl): + try: + ctx.update(context_fn(vurl)) + ctx["portal"] = portal + break + except AttributeError: + continue + return ctx + + def render(self, **kwargs): + ctx = self.ctx_for_video(self.video) + return AutoRenderTuple((self.get_templates(ctx["portal"]), ctx)) diff --git a/feincms/contents.py b/feincms/contents.py new file mode 100644 index 000000000..dd31f2f18 --- /dev/null +++ b/feincms/contents.py @@ -0,0 +1,4 @@ +from feincms.content.filer.models import * # noqa +from feincms.content.raw.models import RawContent # noqa +from feincms.content.richtext.models import RichTextContent # noqa +from feincms.content.template.models import TemplateContent # noqa diff --git a/feincms/context_processors.py b/feincms/context_processors.py index a74385c3a..30bf7de70 100644 --- a/feincms/context_processors.py +++ b/feincms/context_processors.py @@ -3,14 +3,10 @@ def add_page_if_missing(request): """ - If this attribute exists, then a page object has been registered already - by some other part of the code. We let it decide which page object it - wants to pass into the template + Returns ``feincms_page`` for request. """ try: - return { - 'feincms_page': Page.objects.for_request(request, best_match=True), - } + return {"feincms_page": Page.objects.for_request(request, best_match=True)} except Page.DoesNotExist: return {} diff --git a/feincms/contrib/fields.py b/feincms/contrib/fields.py index 847653dc6..38f13a714 100644 --- a/feincms/contrib/fields.py +++ b/feincms/contrib/fields.py @@ -1,20 +1,31 @@ +import json import logging from django import forms -from django.db import models from django.core.serializers.json import DjangoJSONEncoder -from django.utils import simplejson as json +from django.db import models + class JSONFormField(forms.fields.CharField): def clean(self, value, *args, **kwargs): + # It seems that sometimes we receive dict objects here, not only + # strings. Partial form validation maybe? if value: + if isinstance(value, str): + try: + value = json.loads(value) + except ValueError: + raise forms.ValidationError("Invalid JSON data!") + try: - # Run the value through JSON so we can normalize formatting and at least learn about malformed data: - value = json.dumps(json.loads(value), cls=DjangoJSONEncoder) + # Run the value through JSON so we can normalize formatting + # and at least learn about malformed data: + value = json.dumps(value, cls=DjangoJSONEncoder) except ValueError: raise forms.ValidationError("Invalid JSON data!") - return super(JSONFormField, self).clean(value, *args, **kwargs) + return super().clean(value, *args, **kwargs) + class JSONField(models.TextField): """ @@ -24,9 +35,6 @@ class JSONField(models.TextField): http://www.djangosnippets.org/snippets/1478/ """ - # Used so to_python() is called - __metaclass__ = models.SubfieldBase - formfield = JSONFormField def to_python(self, value): @@ -34,7 +42,7 @@ def to_python(self, value): if isinstance(value, dict): return value - elif isinstance(value, basestring): + elif isinstance(value, str) or isinstance(value, bytes): # Avoid asking the JSON decoder to handle empty values: if not value: return {} @@ -42,18 +50,24 @@ def to_python(self, value): try: return json.loads(value) except ValueError: - logging.getLogger("feincms.contrib.fields").exception("Unable to deserialize store JSONField data: %s", value) + logging.getLogger("feincms.contrib.fields").exception( + "Unable to deserialize store JSONField data: %s", value + ) return {} else: assert value is None return {} + def from_db_value(self, value, expression, connection, context=None): + return self.to_python(value) + def get_prep_value(self, value): """Convert our JSON object to a string before we save""" return self._flatten_value(value) def value_to_string(self, obj): - """Extract our value from the passed object and return it in string form""" + """Extract our value from the passed object and return it in string + form""" if hasattr(obj, self.attname): value = getattr(obj, self.attname) @@ -71,15 +85,6 @@ def _flatten_value(self, value): if isinstance(value, dict): value = json.dumps(value, cls=DjangoJSONEncoder) - assert isinstance(value, basestring) + assert isinstance(value, str) return value - -try: - from south.modelsinspector import add_introspection_rules - - JSONField_introspection_rule = ( (JSONField,), [], {}, ) - - add_introspection_rules(rules=[JSONField_introspection_rule], patterns=["^feincms\.contrib\.fields"]) -except ImportError: - pass \ No newline at end of file diff --git a/feincms/contrib/preview/urls.py b/feincms/contrib/preview/urls.py index d4cb66b47..bf9f21a7d 100644 --- a/feincms/contrib/preview/urls.py +++ b/feincms/contrib/preview/urls.py @@ -1,7 +1,8 @@ -from django.conf.urls.defaults import * +from django.urls import re_path from feincms.contrib.preview.views import PreviewHandler -urlpatterns = patterns('', - url(r'^(.*)/_preview/(\d+)/$', PreviewHandler(), name='feincms_preview'), -) + +urlpatterns = [ + re_path(r"^(.*)/_preview/(\d+)/$", PreviewHandler.as_view(), name="feincms_preview") +] diff --git a/feincms/contrib/preview/views.py b/feincms/contrib/preview/views.py index 5abf2b2b0..08b3e5aaa 100644 --- a/feincms/contrib/preview/views.py +++ b/feincms/contrib/preview/views.py @@ -1,9 +1,7 @@ from django.http import Http404 from django.shortcuts import get_object_or_404 -from feincms.module.page import processors -from feincms.module.page.models import Page -from feincms.views.base import Handler +from feincms.views import Handler class PreviewHandler(Handler): @@ -15,22 +13,26 @@ class PreviewHandler(Handler): *** Everything here is subject to change. *** """ - def __call__(self, request, path, page_id): - if not request.user.is_staff: - raise Http404 - - page = get_object_or_404(Page, pk=page_id) + def get_object(self): + """Get the page by the id in the url here instead.""" - # Throw out request processor which will cause the page to-be-previewed - # to be seen as inactive (which is the case, of course) - page.request_processors = [rp for rp in Page.request_processors if rp not in ( - processors.require_path_active_request_processor,)] + # This might happen when there is a preview request for + # a page that doesn't exist, and now FeinCMS wants to + # show a 404 page via FEINCMS_CMS_404_PAGE. + if len(self.args) < 2: + return super().get_object() + page = get_object_or_404(self.page_model, pk=self.args[1]) # Remove _preview/42/ from URL, the rest of the handler code should not # know that anything about previewing. Handler.prepare will still raise # a 404 if the extra_path isn't consumed by any content type - request.path = page.get_absolute_url() + self.request.path = page.get_absolute_url() - response = self.build_response(request, page) - response['Cache-Control'] = 'no-cache, must-revalidate, no-store, private' + return page + + def handler(self, request, *args, **kwargs): + if not request.user.is_staff: + raise Http404("Not found (not allowed)") + response = super().handler(request, *args, **kwargs) + response["Cache-Control"] = "no-cache, must-revalidate, no-store, private" return response diff --git a/feincms/contrib/richtext.py b/feincms/contrib/richtext.py new file mode 100644 index 000000000..5a6a7da19 --- /dev/null +++ b/feincms/contrib/richtext.py @@ -0,0 +1,31 @@ +from django import forms +from django.db import models + + +class RichTextFormField(forms.fields.CharField): + def __init__(self, *args, **kwargs): + self.cleanse = kwargs.pop("cleanse", None) + super().__init__(*args, **kwargs) + css_class = self.widget.attrs.get("class", "") + css_class += " item-richtext" + self.widget.attrs["class"] = css_class + + def clean(self, value): + value = super().clean(value) + if self.cleanse: + value = self.cleanse(value) + return value + + +class RichTextField(models.TextField): + """ + Drop-in replacement for Django's ``models.TextField`` which allows editing + rich text instead of plain text in the item editor. + """ + + def __init__(self, *args, **kwargs): + self.cleanse = kwargs.pop("cleanse", None) + super().__init__(*args, **kwargs) + + def formfield(self, form_class=RichTextFormField, **kwargs): + return super().formfield(form_class=form_class, cleanse=self.cleanse, **kwargs) diff --git a/feincms/contrib/tagging.py b/feincms/contrib/tagging.py index 0ddcc2028..6c29eb727 100644 --- a/feincms/contrib/tagging.py +++ b/feincms/contrib/tagging.py @@ -1,5 +1,4 @@ # ------------------------------------------------------------------------ -# coding=utf-8 # ------------------------------------------------------------------------ # FeinCMS django-tagging support. To add tagging to your (page) model, # simply do a @@ -8,26 +7,31 @@ # tagging.tag_model(Page) # ------------------------------------------------------------------------ -from __future__ import absolute_import -from django.db.models.signals import pre_save from django import forms from django.contrib.admin.widgets import FilteredSelectMultiple - +from django.db.models.signals import pre_save +from django.utils.translation import gettext_lazy as _ from tagging.fields import TagField +from tagging.models import Tag +from tagging.registry import AlreadyRegistered, register as tagging_register +from tagging.utils import parse_tag_input + # ------------------------------------------------------------------------ def taglist_to_string(taglist): - retval = '' + retval = "" if len(taglist) >= 1: taglist.sort() - retval = ','.join(taglist) + retval = ",".join(taglist) return retval + # ------------------------------------------------------------------------ # The following is lifted from: # http://code.google.com/p/django-tagging/issues/detail?id=189 + """ TagSelectField @@ -40,45 +44,62 @@ class MyModel(models.Model): """ + class TagSelectFormField(forms.MultipleChoiceField): def clean(self, value): - return taglist_to_string(list(value)); + return taglist_to_string(list(value)) + + +class Tag_formatvalue_mixin: + def format_value(self, value): + value = parse_tag_input(value or "") + return super().format_value(value) + + +class fv_FilteredSelectMultiple(Tag_formatvalue_mixin, FilteredSelectMultiple): + pass + + +class fv_SelectMultiple(Tag_formatvalue_mixin, forms.SelectMultiple): + pass + class TagSelectField(TagField): def __init__(self, filter_horizontal=False, *args, **kwargs): - super(TagSelectField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.filter_horizontal = filter_horizontal def formfield(self, **defaults): - from tagging.models import Tag, TaggedItem - from tagging.utils import parse_tag_input - if self.filter_horizontal: - widget = FilteredSelectMultiple(self.verbose_name, is_stacked=False) + widget = fv_FilteredSelectMultiple(self.verbose_name, is_stacked=False) else: - widget = forms.SelectMultiple() - def _render(name, value, attrs=None, *args, **kwargs): - value = parse_tag_input(value) - return type(widget).render(widget, name, value, attrs, *args, **kwargs) - widget.render = _render - defaults['widget'] = widget - choices = [ (str(t), str(t)) for t in Tag.objects.all() ] + widget = fv_SelectMultiple() + + defaults["widget"] = widget + choices = [(str(t), str(t)) for t in Tag.objects.all()] return TagSelectFormField(choices=choices, required=not self.blank, **defaults) -# ------------------------------------------------------------------------ + # ------------------------------------------------------------------------ def pre_save_handler(sender, instance, **kwargs): """ Intercept attempts to save and sort the tag field alphabetically, so we won't have different permutations in the filter list. """ - from tagging.utils import parse_tag_input - taglist = parse_tag_input(instance.tags) instance.tags = taglist_to_string(taglist) + # ------------------------------------------------------------------------ -def tag_model(cls, admin_cls=None, field_name='tags', sort_tags=False, select_field=False): +def tag_model( + cls, + admin_cls=None, + field_name="tags", + sort_tags=False, + select_field=False, + auto_add_admin_field=True, + admin_list_display=True, +): """ tag_model accepts a number of named parameters: @@ -89,23 +110,38 @@ def tag_model(cls, admin_cls=None, field_name='tags', sort_tags=False, select_fi sort_tags Boolean, defaults to False. If set to True, a pre_save handler will be inserted to sort the tag field alphabetically. This is useful in case you want a canonical representation - for a tag collection, as when you're presenting a list of + for a tag collection, as when yo're presenting a list of tag combinations (e.g. in an admin filter list). select_field If True, show a multi select instead of the standard CharField for tag entry. + auto_add_admin_field If True, attempts to add the tag field to the admin + class. """ - from tagging import register as tagging_register - cls.add_to_class(field_name, (select_field and TagSelectField or TagField)(field_name.capitalize(), blank=True)) + cls.add_to_class( + field_name, + (TagSelectField if select_field else TagField)( + field_name.capitalize(), blank=True + ), + ) # use another name for the tag descriptor - # See http://code.google.com/p/django-tagging/issues/detail?id=95 for the reason why - tagging_register(cls, tag_descriptor_attr='tagging_' + field_name) + # See http://code.google.com/p/django-tagging/issues/detail?id=95 for the + # reason why + try: + tagging_register(cls, tag_descriptor_attr="tagging_" + field_name) + except AlreadyRegistered: + return if admin_cls: - admin_cls.list_display.append(field_name) + if admin_list_display: + admin_cls.list_display.append(field_name) admin_cls.list_filter.append(field_name) + if auto_add_admin_field and hasattr(admin_cls, "add_extension_options"): + admin_cls.add_extension_options(_("Tagging"), {"fields": (field_name,)}) + if sort_tags: pre_save.connect(pre_save_handler, sender=cls) + # ------------------------------------------------------------------------ diff --git a/feincms/default_settings.py b/feincms/default_settings.py index e43b84494..488bf673f 100644 --- a/feincms/default_settings.py +++ b/feincms/default_settings.py @@ -1,6 +1,4 @@ # ------------------------------------------------------------------------ -# coding=utf-8 -# $Id$ # ------------------------------------------------------------------------ """ Default settings for FeinCMS @@ -9,92 +7,139 @@ ``settings.py`` file. """ -from os.path import join - -import django from django.conf import settings + +# e.g. 'uploads' if you would prefer /uploads/imagecontent/test.jpg +# to /imagecontent/test.jpg. +FEINCMS_UPLOAD_PREFIX = getattr(settings, "FEINCMS_UPLOAD_PREFIX", "") + # ------------------------------------------------------------------------ # Settings for MediaLibrary #: Local path to newly uploaded media files -FEINCMS_MEDIALIBRARY_UPLOAD_TO = getattr(settings, 'FEINCMS_MEDIALIBRARY_UPLOAD_TO', 'medialibrary/%Y/%m/') +FEINCMS_MEDIALIBRARY_UPLOAD_TO = getattr( + settings, "FEINCMS_MEDIALIBRARY_UPLOAD_TO", "medialibrary/%Y/%m/" +) + +#: Thumbnail function for suitable mediafiles. Only receives the media file +#: and should return a thumbnail URL (or nothing). +FEINCMS_MEDIALIBRARY_THUMBNAIL = getattr( + settings, + "FEINCMS_MEDIALIBRARY_THUMBNAIL", + "feincms.module.medialibrary.thumbnail.default_admin_thumbnail", +) # ------------------------------------------------------------------------ # Settings for RichText -FEINCMS_RICHTEXT_INIT_TEMPLATE = getattr(settings, 'FEINCMS_RICHTEXT_INIT_TEMPLATE', - 'admin/content/richtext/init_tinymce.html') -FEINCMS_RICHTEXT_INIT_CONTEXT = getattr(settings, 'FEINCMS_RICHTEXT_INIT_CONTEXT', { - 'TINYMCE_JS_URL': join(settings.MEDIA_URL, 'js/tiny_mce/tiny_mce.js'), - 'TINYMCE_CONTENT_CSS_URL': None, - 'TINYMCE_LINK_LIST_URL': None - }) +FEINCMS_RICHTEXT_INIT_TEMPLATE = getattr( + settings, + "FEINCMS_RICHTEXT_INIT_TEMPLATE", + "admin/content/richtext/init_tinymce4.html", +) +FEINCMS_RICHTEXT_INIT_CONTEXT = getattr( + settings, + "FEINCMS_RICHTEXT_INIT_CONTEXT", + { + "TINYMCE_JS_URL": "https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.9.11/tinymce.min.js", # noqa + "TINYMCE_DOMAIN": None, + "TINYMCE_CONTENT_CSS_URL": None, + "TINYMCE_LINK_LIST_URL": None, + }, +) # ------------------------------------------------------------------------ -# Admin media settings - -#: Path to FeinCMS' admin media -FEINCMS_ADMIN_MEDIA = getattr(settings, 'FEINCMS_ADMIN_MEDIA', '/static/feincms/') -#: Link to google APIs instead of using local copy of JS libraries -FEINCMS_ADMIN_MEDIA_HOTLINKING = getattr(settings, 'FEINCMS_ADMIN_MEDIA_HOTLINKING', False) -#: avoid jQuery conflicts -- scripts should use feincms.jQuery instead of $ -FEINCMS_JQUERY_NO_CONFLICT = \ - getattr(settings, 'FEINCMS_JQUERY_NO_CONFLICT', False) +# Settings for the page module -# Django's admin media files have been transitioned over to standard staticfiles. -# ADMIN_MEDIA_PREFIX isn't available anymore with newer versions (Django >= 1.4). -if hasattr(settings, 'ADMIN_MEDIA_PREFIX'): - _HACK_ADMIN_MEDIA_IMAGES = settings.ADMIN_MEDIA_PREFIX + 'img/admin/' -else: - _HACK_ADMIN_MEDIA_IMAGES = settings.STATIC_URL + 'admin/img/' +#: Include ancestors in filtered tree editor lists +FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS = getattr( + settings, "FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS", False +) + +#: Enable checking of object level permissions. Note that if this option is +#: enabled, you must plug in an authentication backend that actually does +#: implement object level permissions or no page will be editable. +FEINCMS_TREE_EDITOR_OBJECT_PERMISSIONS = getattr( + settings, "FEINCMS_TREE_EDITOR_OBJECT_PERMISSIONS", False +) + +#: When enabled, the page module is automatically registered with Django's +#: default admin site (this is activated by default). +FEINCMS_USE_PAGE_ADMIN = getattr(settings, "FEINCMS_USE_PAGE_ADMIN", True) + +#: app_label.model_name as per apps.get_model. +#: defaults to page.Page +FEINCMS_DEFAULT_PAGE_MODEL = getattr( + settings, "FEINCMS_DEFAULT_PAGE_MODEL", "page.Page" +) # ------------------------------------------------------------------------ -# Settings for the page module +#: Allow random gunk after a valid page? +FEINCMS_ALLOW_EXTRA_PATH = getattr(settings, "FEINCMS_ALLOW_EXTRA_PATH", False) -#: Include ancestors in filtered tree editor lists -FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS = getattr(settings, 'FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS', False) +# ------------------------------------------------------------------------ +#: How to switch languages. +#: * ``'STANDARD'``: The page a user navigates to sets the site's language +#: and overwrites whatever was set before. +#: * ``'EXPLICIT'``: The language set has priority, may only be overridden +#: by explicitely a language with ``?set_language=xx``. +FEINCMS_TRANSLATION_POLICY = getattr(settings, "FEINCMS_TRANSLATION_POLICY", "STANDARD") -#: Show frontend-editing button? -FEINCMS_FRONTEND_EDITING = getattr(settings, 'FEINCMS_FRONTEND_EDITING', True) +# ------------------------------------------------------------------------ +#: Makes the page handling mechanism try to find a cms page with that +#: path if it encounters a page not found situation. This allows for nice +#: customised cms-styled error pages. Do not go overboard, this should +#: be as simple and as error resistant as possible, so refrain from +#: deeply nested error pages or advanced content types. +FEINCMS_CMS_404_PAGE = getattr(settings, "FEINCMS_CMS_404_PAGE", None) -#: Enable checking of object level permissions. Note that if this option is enabled, -#: you must plug in an authentication backend that actually does implement object -#: level permissions or no page will be editable. -FEINCMS_TREE_EDITOR_OBJECT_PERMISSIONS = getattr(settings, 'FEINCMS_TREE_EDITOR_OBJECT_PERMISSIONS', False) +# ------------------------------------------------------------------------ +#: When uploading files to the media library, replacing an existing entry, +#: try to save the new file under the old file name in order to keep the +#: media file path (and thus the media url) constant. +#: Experimental, this might not work with all storage backends. +FEINCMS_MEDIAFILE_OVERWRITE = getattr(settings, "FEINCMS_MEDIAFILE_OVERWRITE", False) # ------------------------------------------------------------------------ -# Various settings +#: Prefix for thumbnails. Set this to something non-empty to separate thumbs +#: from uploads. The value should end with a slash, but this is not enforced. +FEINCMS_THUMBNAIL_DIR = getattr(settings, "FEINCMS_THUMBNAIL_DIR", "_thumbs/") # ------------------------------------------------------------------------ -#: Enable caching intermediate results in feincms. Be aware that this might deliver -#: slightly out of date pages if you are not using the 'changedate' page extension. -FEINCMS_USE_CACHE = getattr(settings, 'FEINCMS_USE_CACHE', False) +#: feincms_thumbnail template filter library cache timeout. The default is to +#: not cache anything for backwards compatibility. +FEINCMS_THUMBNAIL_CACHE_TIMEOUT = getattr( + settings, "FEINCMS_THUMBNAIL_CACHE_TIMEOUT", 0 +) # ------------------------------------------------------------------------ -#: Allow random gunk after a valid page? -FEINCMS_ALLOW_EXTRA_PATH = getattr(settings, 'FEINCMS_ALLOW_EXTRA_PATH', False) +#: Prevent changing template within admin for pages which have been +#: allocated a Template with singleton=True -- template field will become +#: read-only for singleton pages. +FEINCMS_SINGLETON_TEMPLATE_CHANGE_ALLOWED = getattr( + settings, "FEINCMS_SINGLETON_TEMPLATE_CHANGE_ALLOWED", False +) + +#: Prevent admin page deletion for pages which have been allocated a +#: Template with singleton=True +FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED = getattr( + settings, "FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED", False +) # ------------------------------------------------------------------------ -#: How to switch languages. -#: STANDARD = the page a user navigates to sets the site's language and overwrites -#: whatever was set before -#: EXPLICIT = the language set has priority, may only be overridden by explicitely -#: setting a language with ?set_language=xx -FEINCMS_TRANSLATION_POLICY = getattr(settings, 'FEINCMS_TRANSLATION_POLICY', 'STANDARD') +#: Filter languages available for front end users to this set. This allows +#: to have languages not yet ready for prime time while being able to access +#: those pages in the admin backend. +FEINCMS_FRONTEND_LANGUAGES = getattr(settings, "FEINCMS_FRONTEND_LANGUAGES", None) # ------------------------------------------------------------------------ -#: Set to True if you want to run the FeinCMS test suite unconditionally: -FEINCMS_RUN_TESTS = getattr(settings, 'FEINCMS_RUN_TESTS', False) # ------------------------------------------------------------------------ -# Settings for HTML validation +#: Attempt to get translations of MediaFile objects. If `False`, FeinCMS will +#: instead just output the file name. +FEINCMS_MEDIAFILE_TRANSLATIONS = getattr( + settings, "FEINCMS_MEDIAFILE_TRANSLATIONS", True +) -#: If True, HTML will be run through a tidy function before saving: -FEINCMS_TIDY_HTML = getattr(settings, 'FEINCMS_TIDY_HTML', False) -#: If True, displays form validation errors so the user can see how their HTML has been changed: -FEINCMS_TIDY_SHOW_WARNINGS = getattr(settings, 'FEINCMS_TIDY_SHOW_WARNINGS', True) -#: If True, users will be allowed to ignore HTML warnings (errors are always blocked): -FEINCMS_TIDY_ALLOW_WARNINGS_OVERRIDE = getattr(settings, 'FEINCMS_TIDY_ALLOW_WARNINGS_OVERRIDE', True) -#: Name of the tidy function - anything which takes (html) and returns (html, errors, warnings) can be used: -FEINCMS_TIDY_FUNCTION = getattr(settings, 'FEINCMS_TIDY_FUNCTION', 'feincms.utils.html.tidy.tidy_html') +# ------------------------------------------------------------------------ diff --git a/feincms/extensions/__init__.py b/feincms/extensions/__init__.py new file mode 100644 index 000000000..f35d3ed49 --- /dev/null +++ b/feincms/extensions/__init__.py @@ -0,0 +1,14 @@ +from .base import ( + Extension, + ExtensionModelAdmin, + ExtensionsMixin, + prefetch_modeladmin_get_queryset, +) + + +__all__ = ( + "ExtensionsMixin", + "Extension", + "ExtensionModelAdmin", + "prefetch_modeladmin_get_queryset", +) diff --git a/feincms/extensions/base.py b/feincms/extensions/base.py new file mode 100644 index 000000000..c1b84b382 --- /dev/null +++ b/feincms/extensions/base.py @@ -0,0 +1,140 @@ +""" +Base types for extensions refactor +""" + +import inspect +from functools import wraps + +from django.contrib import admin +from django.core.exceptions import ImproperlyConfigured + +from feincms.utils import get_object + + +class ExtensionsMixin: + @classmethod + def register_extensions(cls, *extensions): + """ + Register all extensions passed as arguments. + + Extensions should be specified as a string to the python module + containing the extension. If it is a bundled extension of FeinCMS, + you do not need to specify the full python module path -- only + specifying the last part (f.e. ``'seo'`` or ``'translations'``) is + sufficient. + """ + + if not hasattr(cls, "_extensions"): + cls._extensions = [] + cls._extensions_seen = [] + + for ext in extensions: + if ext in cls._extensions: + continue + + extension = None + + if inspect.isclass(ext) and issubclass(ext, Extension): + extension = ext + + elif isinstance(ext, str): + try: + extension = get_object(ext) + except (AttributeError, ImportError, ValueError): + if not extension: + raise ImproperlyConfigured( + f"{ext} is not a valid extension for {cls.__name__}" + ) + + if hasattr(extension, "Extension"): + extension = extension.Extension + + elif hasattr(extension, "register"): + extension = extension.register + + elif hasattr(extension, "__call__"): + pass + + else: + raise ImproperlyConfigured( + f"{ext} is not a valid extension for {cls.__name__}" + ) + + if extension in cls._extensions_seen: + continue + cls._extensions_seen.append(extension) + + if hasattr(extension, "handle_model"): + cls._extensions.append(extension(cls)) + else: + raise ImproperlyConfigured("%r is an invalid extension." % extension) + + +class Extension: + def __init__(self, model, **kwargs): + self.model = model + for key, value in kwargs.items(): + if not hasattr(self, key): + raise TypeError( + "%s() received an invalid keyword %r" + % (self.__class__.__name__, key) + ) + setattr(self, key, value) + + self.handle_model() + + def handle_model(self): + raise NotImplementedError + + def handle_modeladmin(self, modeladmin): + pass + + +class ExtensionModelAdmin(admin.ModelAdmin): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.initialize_extensions() + + def initialize_extensions(self): + if not hasattr(self, "_extensions_initialized"): + self._extensions_initialized = True + for extension in getattr(self.model, "_extensions", []): + extension.handle_modeladmin(self) + + def add_extension_options(self, *f): + if self.fieldsets is None: + return + + if isinstance(f[-1], dict): # called with a fieldset + self.fieldsets.insert(self.fieldset_insertion_index, f) + f[1]["classes"] = list(f[1].get("classes", [])) + f[1]["classes"].append("collapse") + elif f: # assume called with "other" fields + try: + self.fieldsets[1][1]["fields"].extend(f) + except IndexError: + # Fall back to first fieldset if second does not exist + # XXX This is really messy. + self.fieldsets[0][1]["fields"].extend(f) + + def extend_list(self, attribute, iterable): + extended = list(getattr(self, attribute, ())) + extended.extend(iterable) + setattr(self, attribute, extended) + + +def prefetch_modeladmin_get_queryset(modeladmin, *lookups): + """ + Wraps default modeladmin ``get_queryset`` to prefetch related lookups. + """ + + def do_wrap(f): + @wraps(f) + def wrapper(request, *args, **kwargs): + qs = f(request, *args, **kwargs) + qs = qs.prefetch_related(*lookups) + return qs + + return wrapper + + modeladmin.get_queryset = do_wrap(modeladmin.get_queryset) diff --git a/feincms/extensions/changedate.py b/feincms/extensions/changedate.py new file mode 100644 index 000000000..06370bcd9 --- /dev/null +++ b/feincms/extensions/changedate.py @@ -0,0 +1,68 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +""" +Track the modification date for objects. +""" + +from email.utils import mktime_tz, parsedate_tz +from time import mktime + +from django.db import models +from django.db.models.signals import pre_save +from django.utils import timezone +from django.utils.http import http_date +from django.utils.translation import gettext_lazy as _ + +from feincms import extensions + + +# ------------------------------------------------------------------------ +def pre_save_handler(sender, instance, **kwargs): + """ + Intercept attempts to save and insert the current date and time into + creation and modification date fields. + """ + now = timezone.now() + if instance.id is None: + instance.creation_date = now + instance.modification_date = now + + +# ------------------------------------------------------------------------ +def dt_to_utc_timestamp(dt): + return int(mktime(dt.timetuple())) + + +class Extension(extensions.Extension): + def handle_model(self): + self.model.add_to_class( + "creation_date", + models.DateTimeField(_("creation date"), null=True, editable=False), + ) + self.model.add_to_class( + "modification_date", + models.DateTimeField(_("modification date"), null=True, editable=False), + ) + + self.model.last_modified = lambda p: p.modification_date + + pre_save.connect(pre_save_handler, sender=self.model) + + +# ------------------------------------------------------------------------ +def last_modified_response_processor(page, request, response): + # Don't include Last-Modified if we don't want to be cached + if "no-cache" in response.get("Cache-Control", ""): + return + + # If we already have a Last-Modified, take the later one + last_modified = dt_to_utc_timestamp(page.last_modified()) + if response.has_header("Last-Modified"): + last_modified = max( + last_modified, mktime_tz(parsedate_tz(response["Last-Modified"])) + ) + + response["Last-Modified"] = http_date(last_modified) + + +# ------------------------------------------------------------------------ diff --git a/feincms/extensions/ct_tracker.py b/feincms/extensions/ct_tracker.py new file mode 100644 index 000000000..8dd10ee88 --- /dev/null +++ b/feincms/extensions/ct_tracker.py @@ -0,0 +1,166 @@ +# ------------------------------------------------------------------------ +# +# ct_tracker.py +# FeinCMS +# +# Created by Martin J. Laubach on 02.10.09. +# Copyright (c) 2009 Martin J. Laubach. All rights reserved. +# Updated in 2011 by Matthias Kestenholz for the 1.3 release. +# +# ------------------------------------------------------------------------ + +""" +Track the content types for pages. Instead of gathering the content +types present in each page at run time, save the current state at +saving time, thus saving at least one DB query on page delivery. +""" + +from django.contrib.contenttypes.models import ContentType +from django.db.models.signals import class_prepared, post_save, pre_save +from django.utils.translation import gettext_lazy as _ + +from feincms import extensions +from feincms.contrib.fields import JSONField +from feincms.models import ContentProxy + + +INVENTORY_VERSION = 1 +_translation_map_cache = {} + + +# ------------------------------------------------------------------------ +class TrackerContentProxy(ContentProxy): + def _fetch_content_type_counts(self): + """ + If an object with an empty _ct_inventory is encountered, compute all + the content types currently used on that object and save the list in + the object itself. Further requests for that object can then access + that information and find out which content types are used without + resorting to multiple selects on different ct tables. + + It is therefore important that even an "empty" object does not have an + empty _ct_inventory. + """ + + if "counts" not in self._cache: + if ( + self.item._ct_inventory + and self.item._ct_inventory.get("_version_", -1) == INVENTORY_VERSION + ): + try: + self._cache["counts"] = self._from_inventory( + self.item._ct_inventory + ) + except KeyError: + # It's possible that the inventory does not fit together + # with the current models anymore, f.e. because a content + # type has been removed. + pass + + if "counts" not in self._cache: + super()._fetch_content_type_counts() + + self.item._ct_inventory = self._to_inventory(self._cache["counts"]) + + self.item.__class__.objects.filter(id=self.item.id).update( + _ct_inventory=self.item._ct_inventory + ) + + # Run post save handler by hand + if hasattr(self.item, "get_descendants"): + self.item.get_descendants(include_self=False).update( + _ct_inventory=None + ) + return self._cache["counts"] + + def _translation_map(self): + cls = self.item.__class__ + if cls not in _translation_map_cache: + # Prime translation map and cache it in the class. This needs to be + # done late as opposed to at class definition time as not all + # information is ready, especially when we are doing a "syncdb" the + # ContentType table does not yet exist + tmap = {} + model_to_contenttype = ContentType.objects.get_for_models( + *self.item._feincms_content_types + ) + for idx, fct in enumerate(self.item._feincms_content_types): + dct = model_to_contenttype[fct] + + # Rely on non-negative primary keys + tmap[-dct.id] = idx # From-inventory map + tmap[idx] = dct.id # To-inventory map + + _translation_map_cache[cls] = tmap + return _translation_map_cache[cls] + + def _from_inventory(self, inventory): + """ + Transforms the inventory from Django's content types to FeinCMS's + ContentProxy counts format. + """ + + tmap = self._translation_map() + + return { + region: [(pk, tmap[-ct]) for pk, ct in items] + for region, items in inventory.items() + if region != "_version_" + } + + def _to_inventory(self, counts): + map = self._translation_map() + + inventory = { + region: [(pk, map[ct]) for pk, ct in items] + for region, items in counts.items() + } + inventory["_version_"] = INVENTORY_VERSION + return inventory + + +# ------------------------------------------------------------------------ +def class_prepared_handler(sender, **kwargs): + # It might happen under rare circumstances that not all model classes + # are fully loaded and initialized when the translation map is accessed. + # This leads to (lots of) crashes on the server. Better be safe and + # kill the translation map when any class_prepared signal is received. + _translation_map_cache.clear() + + +class_prepared.connect(class_prepared_handler) + + +# ------------------------------------------------------------------------ +def tree_post_save_handler(sender, instance, **kwargs): + """ + Clobber the _ct_inventory attribute of this object and all sub-objects + on save. + """ + # TODO: Does not find everything it should when ContentProxy content + # inheritance has been customized. + instance.get_descendants(include_self=True).update(_ct_inventory=None) + + +# ------------------------------------------------------------------------ +def single_pre_save_handler(sender, instance, **kwargs): + """Clobber the _ct_inventory attribute of this object""" + + instance._ct_inventory = None + + +# ------------------------------------------------------------------------ +class Extension(extensions.Extension): + def handle_model(self): + self.model.add_to_class( + "_ct_inventory", + JSONField(_("content types"), editable=False, blank=True, null=True), + ) + self.model.content_proxy_class = TrackerContentProxy + + pre_save.connect(single_pre_save_handler, sender=self.model) + if hasattr(self.model, "get_descendants"): + post_save.connect(tree_post_save_handler, sender=self.model) + + +# ------------------------------------------------------------------------ diff --git a/feincms/extensions/datepublisher.py b/feincms/extensions/datepublisher.py new file mode 100644 index 000000000..cdf2786a4 --- /dev/null +++ b/feincms/extensions/datepublisher.py @@ -0,0 +1,153 @@ +""" +Allows setting a date range for when the page is active. Modifies the active() +manager method so that only pages inside the given range are used in the +default views and the template tags. + +Depends on the page class having a "active_filters" list that will be used by +the page's manager to determine which entries are to be considered active. +""" +# ------------------------------------------------------------------------ + +from datetime import datetime + +import django +from django.db import models +from django.db.models import Q +from django.utils import timezone +from django.utils.cache import patch_response_headers +from django.utils.html import mark_safe +from django.utils.translation import gettext_lazy as _ + +from feincms import extensions + + +# ------------------------------------------------------------------------ +def format_date(d, if_none=""): + """ + Format a date in a nice human readable way: Omit the year if it's the + current year. Also return a default value if no date is passed in. + """ + + if d is None: + return if_none + + now = timezone.now() + fmt = (d.year == now.year) and "%d.%m" or "%d.%m.%Y" + return d.strftime(fmt) + + +def latest_children(self): + return self.get_children().order_by("-publication_date") + + +# ------------------------------------------------------------------------ +def granular_now(n=None, default_tz=None): + """ + A datetime.now look-alike that returns times rounded to a five minute + boundary. This helps the backend database to optimize/reuse/cache its + queries by not creating a brand new query each time. + + Also useful if you are using johnny-cache or a similar queryset cache. + """ + if n is None: + n = timezone.now() + if default_tz is None: + default_tz = timezone.get_current_timezone() + + rounded_minute = (n.minute // 5) * 5 + d = datetime(n.year, n.month, n.day, n.hour, rounded_minute) + if django.VERSION < (5,): + return timezone.make_aware(d, default_tz, is_dst=True) + else: + return timezone.make_aware(d, default_tz) + + +# ------------------------------------------------------------------------ +def datepublisher_response_processor(page, request, response): + """ + This response processor is automatically added when the datepublisher + extension is registered. It sets the response headers to match with + the publication end date of the page so that upstream caches and + the django caching middleware know when to expunge the copy. + """ + expires = page.publication_end_date + if expires is not None: + delta = expires - timezone.now() + delta = int(delta.days * 86400 + delta.seconds) + patch_response_headers(response, delta) + + +# ------------------------------------------------------------------------ +class Extension(extensions.Extension): + def handle_model(self): + self.model.add_to_class( + "publication_date", + models.DateTimeField(_("publication date"), default=granular_now), + ) + self.model.add_to_class( + "publication_end_date", + models.DateTimeField( + _("publication end date"), + blank=True, + null=True, + help_text=_("Leave empty if the entry should stay active forever."), + ), + ) + self.model.add_to_class("latest_children", latest_children) + + # Patch in rounding the pub and pub_end dates on save + orig_save = self.model.save + + def granular_save(obj, *args, **kwargs): + if obj.publication_date: + obj.publication_date = granular_now(obj.publication_date) + if obj.publication_end_date: + obj.publication_end_date = granular_now(obj.publication_end_date) + orig_save(obj, *args, **kwargs) + + self.model.save = granular_save + + # Append publication date active check + if hasattr(self.model._default_manager, "add_to_active_filters"): + self.model._default_manager.add_to_active_filters( + lambda queryset: queryset.filter( + Q(publication_date__lte=granular_now()) + & ( + Q(publication_end_date__isnull=True) + | Q(publication_end_date__gt=granular_now()) + ) + ), + key="datepublisher", + ) + + # Processor to patch up response headers for expiry date + self.model.register_response_processor(datepublisher_response_processor) + + def handle_modeladmin(self, modeladmin): + def datepublisher_admin(self, obj): + return mark_safe( + "%s – %s" + % ( + format_date(obj.publication_date), + format_date(obj.publication_end_date, "∞"), + ) + ) + + datepublisher_admin.short_description = _("visible from - to") + + modeladmin.__class__.datepublisher_admin = datepublisher_admin + + try: + pos = modeladmin.list_display.index("is_visible_admin") + except ValueError: + pos = len(modeladmin.list_display) + + modeladmin.list_display.insert(pos + 1, "datepublisher_admin") + + modeladmin.add_extension_options( + _("Date-based publishing"), + {"fields": ["publication_date", "publication_end_date"]}, + ) + + +# ------------------------------------------------------------------------ diff --git a/feincms/extensions/featured.py b/feincms/extensions/featured.py new file mode 100644 index 000000000..a15535571 --- /dev/null +++ b/feincms/extensions/featured.py @@ -0,0 +1,20 @@ +""" +Add a "featured" field to objects so admins can better direct top content. +""" + +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from feincms import extensions + + +class Extension(extensions.Extension): + def handle_model(self): + self.model.add_to_class( + "featured", models.BooleanField(_("featured"), default=False) + ) + + def handle_modeladmin(self, modeladmin): + modeladmin.add_extension_options( + _("Featured"), {"fields": ("featured",), "classes": ("collapse",)} + ) diff --git a/feincms/extensions/seo.py b/feincms/extensions/seo.py new file mode 100644 index 000000000..3698c00d5 --- /dev/null +++ b/feincms/extensions/seo.py @@ -0,0 +1,40 @@ +""" +Add a keyword and a description field which are helpful for SEO optimization. +""" + +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from feincms import extensions + + +class Extension(extensions.Extension): + def handle_model(self): + self.model.add_to_class( + "meta_keywords", + models.TextField( + _("meta keywords"), + blank=True, + help_text=_("Keywords are ignored by most search engines."), + ), + ) + self.model.add_to_class( + "meta_description", + models.TextField( + _("meta description"), + blank=True, + help_text=_( + "This text is displayed on the search results page. " + "It is however not used for the SEO ranking. " + "Text longer than 140 characters is truncated." + ), + ), + ) + + def handle_modeladmin(self, modeladmin): + modeladmin.extend_list("search_fields", ["meta_keywords", "meta_description"]) + + modeladmin.add_extension_options( + _("Search engine optimization"), + {"fields": ("meta_keywords", "meta_description"), "classes": ("collapse",)}, + ) diff --git a/feincms/extensions/translations.py b/feincms/extensions/translations.py new file mode 100644 index 000000000..7465b1072 --- /dev/null +++ b/feincms/extensions/translations.py @@ -0,0 +1,321 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + +""" +This extension adds a language field to every page. When calling the request +processors the page's language is activated. +Pages in secondary languages can be said to be a translation of a page in the +primary language (the first language in settings.LANGUAGES), thereby enabling +deeplinks between translated pages. + +It is recommended to activate +:class:`django.middleware.locale.LocaleMiddleware` so that the correct language +will be activated per user or session even for non-FeinCMS managed views such +as Django's administration tool. +""" + +# ------------------------------------------------------------------------ +import logging +from typing import Optional + +from django.conf import settings as django_settings +from django.db import models +from django.http import HttpRequest, HttpResponseRedirect +from django.utils import translation +from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ + +from feincms import extensions, settings +from feincms._internal import monkeypatch_method, monkeypatch_property +from feincms.translations import is_primary_language + + +# ------------------------------------------------------------------------ +logger = logging.getLogger(__name__) + +LANGUAGE_COOKIE_NAME: str = django_settings.LANGUAGE_COOKIE_NAME +if hasattr(translation, "LANGUAGE_SESSION_KEY"): + LANGUAGE_SESSION_KEY = translation.LANGUAGE_SESSION_KEY +else: + # stopgap measure for django >= 4, need to revisit this later + LANGUAGE_SESSION_KEY = LANGUAGE_COOKIE_NAME + +PRIMARY_LANGUAGE: str = django_settings.LANGUAGES[0][0] + + +# ------------------------------------------------------------------------ +def user_has_language_set(request: HttpRequest) -> bool: + """ + Determine whether the user has explicitely set a language earlier on. + This is taken later on as an indication that we should not mess with the + site's language settings, after all, the user's decision is what counts. + """ + if ( + hasattr(request, "session") + and request.session.get(LANGUAGE_SESSION_KEY) is not None + ): + return True + if LANGUAGE_COOKIE_NAME in request.COOKIES: + return True + return False + + +# ------------------------------------------------------------------------ +def translation_allowed_language(select_language: str) -> str: + "Check for feincms specific set of allowed front end languages." + if settings.FEINCMS_FRONTEND_LANGUAGES: + language = select_language[:2] + if language not in settings.FEINCMS_FRONTEND_LANGUAGES: + select_language = PRIMARY_LANGUAGE + + return select_language + + +# ------------------------------------------------------------------------ +def translation_set_language( + request: HttpRequest, select_language: str +) -> Optional[HttpResponseRedirect]: + """ + Set and activate a language, if that language is available. + """ + + select_language = translation_allowed_language(select_language) + + if translation.check_for_language(select_language): + fallback = False + else: + # The page is in a language that Django has no messages for. + # We display anyhow, but fall back to primary language for + # other messages and other applications. It is *highly* recommended to + # create a new django.po for the language instead of + # using this behaviour. + select_language = PRIMARY_LANGUAGE + fallback = True + + translation.activate(select_language) + request.LANGUAGE_CODE = translation.get_language() # type: ignore + + if hasattr(request, "session"): + # User has a session, then set this language there + current_session_language = request.session.get( + LANGUAGE_SESSION_KEY, PRIMARY_LANGUAGE + ) + + if select_language != current_session_language: + request.session[LANGUAGE_SESSION_KEY] = select_language + elif request.method == "GET" and not fallback: + # No session is active. We need to set a cookie for the language + # so that it persists when users change their location to somewhere + # not under the control of the CMS. + # Only do this when request method is GET (mainly, do not abort + # POST requests) + response = HttpResponseRedirect(request.get_full_path()) + response.set_cookie(str(LANGUAGE_COOKIE_NAME), select_language, samesite="Lax") + return response + + +# ------------------------------------------------------------------------ +def translations_request_processor_explicit(page, request): + # If this page is just a redirect, don't do any language specific setup + if page.redirect_to: + return + + # Until further notice, the user might be wanting to switch to the + # page's language... + desired_language = page.language + + # ...except if the user explicitely wants to switch language + if "set_language" in request.GET: + desired_language = request.GET["set_language"] + # ...or the user already has explicitely set a language, bail out and + # don't change it for them behind their back + elif user_has_language_set(request): + return + + return translation_set_language(request, desired_language) + + +# ------------------------------------------------------------------------ +def translations_request_processor_standard(page, request): + # If this page is just a redirect, don't do any language specific setup + if getattr(page, "redirect_to", None): + return + + if page.language == translation.get_language(): + return + + return translation_set_language(request, page.language) + + +# ------------------------------------------------------------------------ +def get_current_language_code(request): + language_code = getattr(request, "LANGUAGE_CODE", None) + if language_code is None: + logger.warning( + "Could not access request.LANGUAGE_CODE. Is 'django.middleware." + "locale.LocaleMiddleware' in MIDDLEWARE_CLASSES?" + ) + return language_code + + +# ------------------------------------------------------------------------ +class Extension(extensions.Extension): + def handle_model(self): + cls = self.model + + cls.add_to_class( + "language", + models.CharField( + _("language"), + max_length=10, + choices=django_settings.LANGUAGES, + default=PRIMARY_LANGUAGE, + ), + ) + cls.add_to_class( + "translation_of", + models.ForeignKey( + "self", + on_delete=models.CASCADE, + blank=True, + null=True, + verbose_name=_("translation of"), + related_name="translations", + limit_choices_to={"language": PRIMARY_LANGUAGE}, + help_text=_("Leave this empty for entries in the primary language."), + ), + ) + + if hasattr(cls, "register_request_processor"): + if settings.FEINCMS_TRANSLATION_POLICY == "EXPLICIT": + cls.register_request_processor( + translations_request_processor_explicit, key="translations" + ) + else: # STANDARD + cls.register_request_processor( + translations_request_processor_standard, key="translations" + ) + + if hasattr(cls, "get_redirect_to_target"): + original_get_redirect_to_target = cls.get_redirect_to_target + + @monkeypatch_method(cls) + def get_redirect_to_target(self, request=None): + """ + Find an acceptable redirect target. If this is a local link, + then try to find the page this redirect references and + translate it according to the user's language. This way, one + can easily implement a localized "/"-url to welcome page + redirection. + """ + target = original_get_redirect_to_target(self, request) + if target and target.find("//") == -1: + # Not an offsite link http://bla/blubb + try: + page = cls.objects.page_for_path(target) + language = get_current_language_code(request) + language = translation_allowed_language(language) + page = page.get_translation(language) + # Note: Does not care about active status? + target = page.get_absolute_url() + except cls.DoesNotExist: + pass + return target + + @monkeypatch_method(cls) + def available_translations(self): + if not self.id: # New, unsaved pages have no translations + return [] + + if hasattr(cls.objects, "apply_active_filters"): + filter_active = cls.objects.apply_active_filters + else: + + def filter_active(queryset): + return queryset + + if is_primary_language(self.language): + return filter_active(self.translations.all()) + elif self.translation_of: + # reuse prefetched queryset, do not filter it + res = [ + t + for t in filter_active(self.translation_of.translations.all()) + if t.language != self.language + ] + res.insert(0, self.translation_of) + return res + else: + return [] + + @monkeypatch_method(cls) + def get_original_translation(self, *args, **kwargs): + if is_primary_language(self.language): + return self + if self.translation_of: + return self.translation_of + logger.debug( + "Page pk=%d (%s) has no primary language translation (%s)", + self.pk, + self.language, + PRIMARY_LANGUAGE, + ) + return self + + @monkeypatch_property(cls) + def original_translation(self): + return self.get_original_translation() + + @monkeypatch_method(cls) + def get_translation(self, language): + return self.original_translation.translations.get(language=language) + + def handle_modeladmin(self, modeladmin): + extensions.prefetch_modeladmin_get_queryset( + modeladmin, "translation_of__translations", "translations" + ) + + def available_translations_admin(self, page): + # Do not use available_translations() because we don't care + # whether pages are active or not here. + translations = [page] + translations.extend(page.translations.all()) + if page.translation_of: + translations.append(page.translation_of) + translations.extend(page.translation_of.translations.all()) + translations = {p.language: p.id for p in translations} + + links = [] + + for key, title in django_settings.LANGUAGES: + if key == page.language: + continue + + if key in translations: + links.append( + '%s' + % (translations[key], _("Edit translation"), key.upper()) + ) + else: + links.append( + '%s' + % (page.id, key, _("Create translation"), key.upper()) + ) + + return mark_safe(" | ".join(links)) + + available_translations_admin.short_description = _("translations") + modeladmin.__class__.available_translations_admin = available_translations_admin + + if hasattr(modeladmin, "add_extension_options"): + modeladmin.add_extension_options("language", "translation_of") + + modeladmin.extend_list( + "list_display", ["language", "available_translations_admin"] + ) + modeladmin.extend_list("list_filter", ["language"]) + modeladmin.extend_list("raw_id_fields", ["translation_of"]) + + +# ------------------------------------------------------------------------ diff --git a/feincms/locale/ca/LC_MESSAGES/django.mo b/feincms/locale/ca/LC_MESSAGES/django.mo index 457643cd3..f16f42f8e 100644 Binary files a/feincms/locale/ca/LC_MESSAGES/django.mo and b/feincms/locale/ca/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/ca/LC_MESSAGES/django.po b/feincms/locale/ca/LC_MESSAGES/django.po index 8868b8d02..d415b080b 100644 --- a/feincms/locale/ca/LC_MESSAGES/django.po +++ b/feincms/locale/ca/LC_MESSAGES/django.po @@ -1,207 +1,203 @@ -# Traducción española de FeinCMS -# Copyright (C) 2009 proycon -# This file is distributed under the same license as the FeinCMS package. -# Maarten van Gompel (proycon) , 2009. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. # +# Translators: +# proycon , 2009 msgid "" msgstr "" "Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:19-0500\n" -"PO-Revision-Date: 2010-09-20 20:39+0100\n" -"Last-Translator: antoni aloy \n" -"Language-Team: ca \n" -"Language: \n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Matthias Kestenholz \n" +"Language-Team: Catalan (http://www.transifex.com/projects/p/feincms/language/" +"ca/)\n" +"Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Poedit-Language: Catalan\n" -"X-Poedit-SourceCharset: utf-8\n" -"X-Poedit-Country: SPAIN\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "plantilla" - -#: models.py:553 -msgid "ordering" -msgstr "ordenació" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "idioma" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filterspecs.py:47 admin/filterspecs.py:86 msgid "All" msgstr "Tots" -#: admin/filterspecs.py:57 module/page/models.py:261 +#: admin/filterspecs.py:58 module/page/models.py:178 msgid "Parent" msgstr "Pare" -#: admin/filterspecs.py:95 +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 msgid "Category" msgstr "Categoria" -#: admin/item_editor.py:150 +#: admin/item_editor.py:190 #, python-format msgid "Change %s" msgstr "Modificar %s" -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:259 -#: module/page/models.py:338 +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 msgid "title" msgstr "títol" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +msgid "You do not have permission to modify this object" +msgstr "" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 #, python-format msgid "%s has been moved to a new position." msgstr "%s ha sigut mogut a una nova posició" -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:512 msgid "Did not understand moving instruction." msgstr "No entenc l'odre de moviment" -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:523 msgid "actions" msgstr "accions" -#: content/application/models.py:241 +#: admin/tree_editor.py:552 +#, python-format +msgid "Successfully deleted %(count)d items." +msgstr "" + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +#: content/application/models.py:147 msgid "application content" msgstr "contingut de l'aplicació" -#: content/application/models.py:242 +#: content/application/models.py:148 msgid "application contents" msgstr "continguts de l'aplicació" -#: content/application/models.py:273 +#: content/application/models.py:179 msgid "application" msgstr "aplicació" -#: content/comments/models.py:28 +#: content/comments/models.py:32 msgid "enabled" msgstr "actiu" -#: content/comments/models.py:28 +#: content/comments/models.py:33 msgid "New comments may be added" msgstr "Es podran afegir nous comentaris" -#: content/comments/models.py:32 content/comments/models.py:33 +#: content/comments/models.py:37 content/comments/models.py:38 msgid "comments" msgstr "comentaris" -#: content/comments/models.py:48 +#: content/comments/models.py:60 msgid "public" msgstr "públic" -#: content/comments/models.py:48 +#: content/comments/models.py:61 msgid "not public" msgstr "privat" -#: content/contactform/models.py:18 +#: content/contactform/models.py:20 msgid "name" msgstr "nom" -#: content/contactform/models.py:19 +#: content/contactform/models.py:21 msgid "email" msgstr "correu" -#: content/contactform/models.py:20 +#: content/contactform/models.py:22 msgid "subject" msgstr "assumpte" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:26 content/raw/models.py:16 msgid "content" msgstr "contingut" -#: content/contactform/models.py:34 +#: content/contactform/models.py:37 msgid "contact form" msgstr "formulari de contacte" -#: content/contactform/models.py:35 +#: content/contactform/models.py:38 msgid "contact forms" msgstr "formularis de contacte" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 msgid "file" msgstr "arxiu" -#: content/file/models.py:21 +#: content/file/models.py:29 msgid "files" msgstr "arxius" -#: content/image/models.py:24 content/image/models.py:28 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "imatge" -#: content/image/models.py:29 +#: content/image/models.py:49 +msgid "alternate text" +msgstr "" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "llegenda" + +#: content/image/models.py:56 msgid "images" msgstr "imatges" -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 +#: content/image/models.py:82 msgid "position" msgstr "posició" -#: content/medialibrary/models.py:38 -msgid "(no caption)" -msgstr "(sense llegenda)" +#: content/image/models.py:90 +msgid "format" +msgstr "" -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 msgid "media file" msgstr "arxiu de medis" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 msgid "media files" msgstr "arxius de medis" -#: content/medialibrary/models.py:131 -msgid "block" -msgstr "bloc" - -#: content/medialibrary/models.py:132 -msgid "left" -msgstr "esquerra" - -#: content/medialibrary/models.py:133 -msgid "right" -msgstr "dreta" - -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: content/medialibrary/models.py:64 content/section/models.py:59 msgid "type" msgstr "tipus" -#: content/raw/models.py:18 +#: content/raw/models.py:20 msgid "raw content" msgstr "contingut en cruu" -#: content/raw/models.py:19 +#: content/raw/models.py:21 msgid "raw contents" msgstr "continguts en cruu" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "text" - -#: content/richtext/models.py:26 +#: content/richtext/models.py:24 msgid "HTML Tidy" msgstr "HTML Tidy" -#: content/richtext/models.py:27 +#: content/richtext/models.py:25 msgid "Ignore the HTML validation warnings" msgstr "Ignora els avisos de validació de l'HTML" -#: content/richtext/models.py:51 +#: content/richtext/models.py:55 #, python-format msgid "" "HTML validation produced %(count)d warnings. Please review the updated " @@ -210,15 +206,19 @@ msgstr "" "La validació de l'HTML generà %(count)d avisos. Per favor, revisa el " "contingut actualitzat de la part inferior abans de continuar: %(messages)s" -#: content/richtext/models.py:89 +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "text" + +#: content/richtext/models.py:103 msgid "rich text" msgstr "text ric" -#: content/richtext/models.py:90 +#: content/richtext/models.py:104 msgid "rich texts" msgstr "texts rics" -#: content/rss/models.py:21 +#: content/rss/models.py:25 msgid "" "The rss field is updated several times a day. A change in the title will " "only be visible on the home page after the next feed update." @@ -226,75 +226,55 @@ msgstr "" "El feed RSS s'actualitza varies vegades al dia. Els canvis en el títol sols " "seran visibles a la plana inicial després de la propera actualització." -#: content/rss/models.py:22 +#: content/rss/models.py:28 msgid "link" msgstr "enlaç" -#: content/rss/models.py:23 +#: content/rss/models.py:30 msgid "pre-rendered content" msgstr "contingut pre-renderitzat" -#: content/rss/models.py:24 +#: content/rss/models.py:32 msgid "last updated" msgstr "darrera actualització" -#: content/rss/models.py:25 +#: content/rss/models.py:33 msgid "max. items" msgstr "nombre màxim" -#: content/rss/models.py:29 +#: content/rss/models.py:37 msgid "RSS feed" msgstr "feed RSS" -#: content/rss/models.py:30 +#: content/rss/models.py:38 msgid "RSS feeds" msgstr "feeds RSS" -#: content/section/models.py:37 +#: content/section/models.py:43 msgid "section" msgstr "secció" -#: content/section/models.py:38 +#: content/section/models.py:44 msgid "sections" msgstr "seccions" -#: content/table/models.py:62 -msgid "plain" -msgstr "plà" - -#: content/table/models.py:63 -msgid "title row" -msgstr "títol de la fila" - -#: content/table/models.py:65 -msgid "title row and column" -msgstr "títol de fila i columna" - -#: content/table/models.py:71 -msgid "table" -msgstr "taula" - -#: content/table/models.py:72 -msgid "tables" -msgstr "taules" - -#: content/table/models.py:86 -msgid "data" -msgstr "dades" - -#: content/template/models.py:62 +#: content/template/models.py:53 msgid "template content" msgstr "plantilla de contingut" -#: content/template/models.py:63 +#: content/template/models.py:54 msgid "template contents" msgstr "plantilles de continguts" -#: content/video/models.py:23 +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "plantilla" + +#: content/video/models.py:34 msgid "video link" msgstr "enllaç de vídeo" -#: content/video/models.py:24 +#: content/video/models.py:36 msgid "" "This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." "com/watch?v=zmj1rpzDRZ0" @@ -302,418 +282,493 @@ msgstr "" "Això ha de ser un enllaç a un vídeo de Youtube o Vimeo. Per exemple: http://" "www.youtube.com/watch?v=zmj1rpzDRZ0" -#: content/video/models.py:28 +#: content/video/models.py:41 msgid "video" msgstr "vídeo" -#: content/video/models.py:29 +#: content/video/models.py:42 msgid "videos" msgstr "vídeos" -#: module/blog/models.py:31 +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "" + +#: models.py:550 +msgid "ordering" +msgstr "ordenació" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "etiquetes" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "idioma" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "tradució de" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "Deixa aquest camp buid per les entrades en l'idioma base" + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "traduccions disponibles" + +#: module/blog/models.py:33 msgid "published" msgstr "publicat" -#: module/blog/models.py:33 +#: module/blog/models.py:36 msgid "This is used for the generated navigation too." msgstr "Això també es fa servir per la navegació generada automàticament." -#: module/blog/models.py:36 +#: module/blog/models.py:40 msgid "published on" msgstr "publicat el" -#: module/blog/models.py:37 +#: module/blog/models.py:42 msgid "Will be set automatically once you tick the `published` checkbox above." msgstr "" "S'establirà automàticament quan marquis la casella superior de 'publicat'" -#: module/blog/models.py:42 +#: module/blog/models.py:48 msgid "entry" msgstr "entrada" -#: module/blog/models.py:43 +#: module/blog/models.py:49 msgid "entries" msgstr "entrades" -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "etiquetes" - -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "tradució de" - -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -msgid "Leave this empty for entries in the primary language." -msgstr "Deixa aquest camp buid per les entrades en l'idioma base" - -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" -msgstr "traduccions disponibles" - -#: module/extensions/changedate.py:38 +#: module/extensions/changedate.py:41 msgid "creation date" msgstr "data de creació" -#: module/extensions/changedate.py:39 +#: module/extensions/changedate.py:43 msgid "modification date" msgstr "data de modificació" -#: module/extensions/ct_tracker.py:134 +#: module/extensions/ct_tracker.py:156 msgid "content types" msgstr "tipus de contingut" -#: module/extensions/featured.py:9 +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "data de publicació" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "publicar fins a" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "Si la deixen en blanc l'entrada estarà activa per sempre" + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "visible de-fins a" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "Publicació segons la data" + +#: module/extensions/featured.py:15 msgid "featured" msgstr "categoritzat" -#: module/extensions/featured.py:14 +#: module/extensions/featured.py:21 msgid "Featured" msgstr "Categoritzat" -#: module/extensions/seo.py:9 +#: module/extensions/seo.py:16 msgid "meta keywords" msgstr "meta paraules" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "Això serà afegit a la llista de paraules clau per defecte" +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" -#: module/extensions/seo.py:11 +#: module/extensions/seo.py:20 msgid "meta description" msgstr "meta descripció" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." -msgstr "Això serà afegit a la descripció per defecte" +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" -#: module/extensions/seo.py:18 +#: module/extensions/seo.py:32 msgid "Search engine optimization" msgstr "Optimització per als motors de cerca" -#: module/medialibrary/models.py:51 +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "Editar la traducció" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Crear traducció" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "traduccions" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "" +msgstr[1] "" + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "Previsualitzar" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "tamany d'arxiu" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "creat" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "tipus d'arxiu" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "informació de l'arxiu" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "" + +#: module/medialibrary/models.py:49 msgid "parent" msgstr "pare" -#: module/medialibrary/models.py:53 module/page/models.py:260 +#: module/medialibrary/models.py:51 module/page/models.py:175 msgid "slug" msgstr "slug" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:55 msgid "category" msgstr "categoria" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 msgid "categories" msgstr "categories" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" -msgstr "tipus d'arxiu" - -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" -msgstr "creat" - -#: module/medialibrary/models.py:93 +#: module/medialibrary/models.py:101 msgid "copyright" msgstr "drets de còpia" -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 -msgid "file size" -msgstr "tamany d'arxiu" - -#: module/medialibrary/models.py:203 -msgid "file info" -msgstr "informació de l'arxiu" - -#: module/medialibrary/models.py:280 +#: module/medialibrary/models.py:219 msgid "Image" msgstr "Imatge" -#: module/medialibrary/models.py:281 +#: module/medialibrary/models.py:221 msgid "Video" msgstr "Vídeo" -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:224 msgid "Audio" msgstr "Audio" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:226 msgid "PDF document" msgstr "document PDF" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:227 msgid "Flash" msgstr "Flash" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:228 msgid "Text" msgstr "Text" -#: module/medialibrary/models.py:286 +#: module/medialibrary/models.py:229 msgid "Rich Text" msgstr "Text ric" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:230 msgid "Zip archive" msgstr "" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:231 msgid "Microsoft Word" msgstr "Microsoft Word" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:233 msgid "Microsoft Excel" msgstr "Microsoft Excel" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:235 msgid "Microsoft PowerPoint" msgstr "Microsoft PowerPoint" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:237 msgid "Binary" msgstr "Binari" -#: module/medialibrary/models.py:307 -msgid "caption" -msgstr "llegenda" - -#: module/medialibrary/models.py:308 +#: module/medialibrary/models.py:261 msgid "description" msgstr "descripció" -#: module/medialibrary/models.py:311 +#: module/medialibrary/models.py:264 msgid "media file translation" msgstr "traducció de l'arxiu de medis" -#: module/medialibrary/models.py:312 +#: module/medialibrary/models.py:265 msgid "media file translations" msgstr "traduccions dels arxius de medis" -#: module/medialibrary/models.py:335 -msgid "Preview" -msgstr "Previsualitzar" +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" +msgstr "extracte" -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." +msgstr "Afegeix una breu descripció resumint el contingut de la plana" -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" -msgstr "" +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" +msgstr "Extracte" -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" -msgstr "" +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 +msgid "navigation extension" +msgstr "extensió de navegació" -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" +#: module/page/extensions/navigation.py:119 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." msgstr "" +"Selecciona el mòdul que proveeix les subplanes si necessites personalitzar " +"la navegació." -#: module/medialibrary/models.py:448 -msgid "No input file given" +#: module/page/extensions/navigation.py:134 +msgid "Navigation extension" +msgstr "Extensió de navegació" + +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" msgstr "" -#: module/page/models.py:256 -msgid "active" -msgstr "actiu" +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" -#: module/page/models.py:263 module/page/models.py:711 -msgid "in navigation" +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" msgstr "a la navegació" -#: module/page/models.py:264 -msgid "override URL" -msgstr "URL sobrescrit" +#: module/page/extensions/relatedpages.py:21 +msgid "Select pages that should be listed as related content." +msgstr "Selecciones les planes que es mostraran com a contingut relacionat." -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." +#: module/page/extensions/relatedpages.py:26 +msgid "Related pages" +msgstr "Planes relacionades" + +#: module/page/extensions/sites.py:21 +msgid "Site" msgstr "" -"Sobreescriu l'URL. Ha de contenir una '/' a l'inici i al final quan es " -"tracti d'una URL local. Afecta tant a la navegació com a les URLs de les " -"subplanes." -#: module/page/models.py:266 -msgid "redirect to" -msgstr "redirecciona a" +#: module/page/extensions/symlinks.py:22 +msgid "symlinked page" +msgstr "Plana enllaçada" -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." -msgstr "URL de destí per les redireccions automàtiques" +#: module/page/extensions/symlinks.py:23 +msgid "All content is inherited from this page if given." +msgstr "Tot el contingut es heretat d'aquesta plana." -#: module/page/models.py:268 -msgid "Cached URL" -msgstr "URL en caché" +#: module/page/extensions/titles.py:19 +msgid "content title" +msgstr "títol del contingut" -#: module/page/models.py:279 -msgid "page" -msgstr "plana" +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." +msgstr "La primera línia és el títol principal, les altres són subtítols." -#: module/page/models.py:280 -msgid "pages" -msgstr "planes" +#: module/page/extensions/titles.py:26 +msgid "page title" +msgstr "títol de la plana" -#: module/page/models.py:297 module/page/models.py:782 -msgid "is active" -msgstr "és activa" +#: module/page/extensions/titles.py:30 +#, fuzzy +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." +msgstr "Títol de la plana pel navegador. Per defecte el mateix que el títol." -#: module/page/models.py:631 +#: module/page/extensions/titles.py:60 +msgid "Titles" +msgstr "Títols" + +#: module/page/forms.py:187 msgid "This URL is already taken by an active page." msgstr "Aquesta URL ja està en ús en una plana activa." -#: module/page/models.py:649 +#: module/page/forms.py:206 msgid "This URL is already taken by another active page." msgstr "Aquesta URL ja età en ús per una altra plana activa." -#: module/page/models.py:674 +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" + +#: module/page/modeladmins.py:42 msgid "Other options" msgstr "Altres opcions" -#: module/page/models.py:721 +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "a la navegació" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 msgid "Add child page" msgstr "Afegeix una plana filla." -#: module/page/models.py:723 +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 msgid "View on site" msgstr "Veure a Lloc" -#: module/page/models.py:759 +#: module/page/modeladmins.py:142 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "" + +#: module/page/modeladmins.py:197 msgid "You don't have the necessary permissions to edit this object" msgstr "" -#: module/page/models.py:774 +#: module/page/modeladmins.py:223 msgid "inherited" msgstr "heretat" -#: module/page/models.py:778 +#: module/page/modeladmins.py:229 msgid "extensions" msgstr "extensions" -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "data de publicació" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "publicar fins a" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "Si la deixen en blanc l'entrada estarà activa per sempre" - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "visible de-fins a" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" -msgstr "Publicació segons la data" +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" +msgstr "és activa" -#: module/page/extensions/excerpt.py:9 -msgid "excerpt" -msgstr "extracte" +#: module/page/models.py:169 +msgid "active" +msgstr "actiu" -#: module/page/extensions/excerpt.py:10 -msgid "Add a brief excerpt summarizing the content of this page." -msgstr "Afegeix una breu descripció resumint el contingut de la plana" +#: module/page/models.py:173 +#, fuzzy +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "Això també es fa servir per la navegació generada automàticament." -#: module/page/extensions/excerpt.py:12 -msgid "Excerpt" -msgstr "Extracte" +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" +msgstr "" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 -msgid "navigation extension" -msgstr "extensió de navegació" +#: module/page/models.py:184 +msgid "override URL" +msgstr "URL sobrescrit" -#: module/page/extensions/navigation.py:99 +#: module/page/models.py:186 msgid "" -"Select the module providing subpages for this page if you need to customize " -"the navigation." +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." msgstr "" -"Selecciona el mòdul que proveeix les subplanes si necessites personalitzar " -"la navegació." - -#: module/page/extensions/navigation.py:112 -msgid "Navigation extension" -msgstr "Extensió de navegació" - -#: module/page/extensions/relatedpages.py:13 -msgid "Select pages that should be listed as related content." -msgstr "Selecciones les planes que es mostraran com a contingut relacionat." +"Sobreescriu l'URL. Ha de contenir una '/' a l'inici i al final quan es " +"tracti d'una URL local. Afecta tant a la navegació com a les URLs de les " +"subplanes." -#: module/page/extensions/relatedpages.py:20 -msgid "Related pages" -msgstr "Planes relacionades" +#: module/page/models.py:190 +msgid "redirect to" +msgstr "redirecciona a" -#: module/page/extensions/sites.py:16 -msgid "Site" +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." msgstr "" -#: module/page/extensions/symlinks.py:15 -msgid "symlinked page" -msgstr "Plana enllaçada" - -#: module/page/extensions/symlinks.py:16 -msgid "All content is inherited from this page if given." -msgstr "Tot el contingut es heretat d'aquesta plana." - -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" -msgstr "Plana enllaçada" - -#: module/page/extensions/titles.py:13 -msgid "content title" -msgstr "títol del contingut" - -#: module/page/extensions/titles.py:14 -msgid "The first line is the main title, the following lines are subtitles." -msgstr "La primera línia és el títol principal, les altres són subtítols." - -#: module/page/extensions/titles.py:15 -msgid "page title" -msgstr "títol de la plana" - -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." -msgstr "Títol de la plana pel navegador. Per defecte el mateix que el títol." - -#: module/page/extensions/titles.py:43 -msgid "Titles" -msgstr "Títols" - -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "Editar la traducció" - -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "Crear traducció" - -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "traduccions" +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "URL en caché" -#: templates/admin/filter.html:3 +#: module/page/models.py:298 #, python-format -msgid " By %(filter_title)s " -msgstr "Per %(filter_title)s" +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" +msgstr "" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "Cercar" +#: module/page/models.py:426 +msgid "page" +msgstr "plana" + +#: module/page/models.py:427 +msgid "pages" +msgstr "planes" #: templates/admin/feincms/_messages_js.html:4 msgid "Really delete item?" @@ -748,11 +803,8 @@ msgstr "Vols canviar la plantilla?
    Tots els canvis es guardaran." #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s is moved to %%(target_region)s." msgstr "" -"Vols canviar la plantilla?
    Tots els canvis es guardaran i el contingut " -"des de %(source_regions)s se mourà a " -"%(target_region)s" #: templates/admin/feincms/_messages_js.html:12 msgid "Hide" @@ -774,11 +826,15 @@ msgstr "Abans" msgid "Insert new:" msgstr "Inserir nou:" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "Regió buida" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." @@ -786,46 +842,63 @@ msgstr "" "El contingut de la plana pare s'inserirà automàticament. Per sobreescriure " "aquest comportament afegeix algun contingut." -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "Afegir un nou element" -#: templates/admin/feincms/fe_editor.html:46 +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "" + +#: templates/admin/feincms/fe_editor.html:30 msgid "Save" msgstr "Guardar" -#: templates/admin/feincms/fe_tools.html:27 +#: templates/admin/feincms/fe_tools.html:28 msgid "Stop Editing" msgstr "Acabar l'edició" -#: templates/admin/feincms/fe_tools.html:32 +#: templates/admin/feincms/fe_tools.html:33 msgid "edit" msgstr "editar" -#: templates/admin/feincms/fe_tools.html:34 +#: templates/admin/feincms/fe_tools.html:35 msgid "new" msgstr "nou" -#: templates/admin/feincms/fe_tools.html:35 +#: templates/admin/feincms/fe_tools.html:36 msgid "up" msgstr "amunt" -#: templates/admin/feincms/fe_tools.html:36 +#: templates/admin/feincms/fe_tools.html:37 msgid "down" msgstr "avall" -#: templates/admin/feincms/fe_tools.html:37 +#: templates/admin/feincms/fe_tools.html:38 msgid "remove" msgstr "esborrar" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:16 +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "Editar al Lloc" + +#: templates/admin/feincms/page/page/item_editor.html:23 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 msgid "Home" msgstr "Inici" -#: templates/admin/feincms/recover_form.html:10 +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "" + +#: templates/admin/feincms/recover_form.html:11 #, python-format msgid "Recover deleted %(verbose_name)s" msgstr "" @@ -834,48 +907,44 @@ msgstr "" msgid "Press the save button below to recover this version of the object." msgstr "" -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:12 msgid "History" msgstr "" -#: templates/admin/feincms/revision_form.html:15 +#: templates/admin/feincms/revision_form.html:13 #, python-format msgid "Revert %(verbose_name)s" msgstr "" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:24 msgid "Press the save button below to revert to this version of the object." msgstr "" -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "Dreceres" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" msgstr "Tancar l'arbre" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" msgstr "Expandir l'arbre" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "Filtrar" -#: templates/admin/feincms/page/page/item_editor.html:9 -msgid "Edit on site" -msgstr "Editar al Lloc" - -#: templates/admin/feincms/page/page/item_editor.html:20 -msgid "Add" -msgstr "" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr "Per %(filter_title)s" #: templates/admin/medialibrary/add_to_category.html:5 #: templates/admin/medialibrary/add_to_category.html:9 -#, fuzzy msgid "Add media files to category" -msgstr "traducció de l'arxiu de medis" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:11 msgid "Select category to apply:" @@ -886,19 +955,22 @@ msgid "The following media files will be added to the selected category:" msgstr "" #: templates/admin/medialibrary/add_to_category.html:22 -#, fuzzy msgid "Add to category" -msgstr "categoria" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:23 msgid "Cancel" msgstr "" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:11 msgid "Bulk upload a ZIP file:" msgstr "Puja un arxiu ZIP:" #: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 msgid "Send" msgstr "Enviar" @@ -935,116 +1007,26 @@ msgstr "Enviar" msgid "Thanks!" msgstr "Gràcies!" -#~ msgid "rich text (ckeditor)" -#~ msgstr "text rico (ckeditor)" - -#~ msgid "rich texts (ckeditor)" -#~ msgstr "texts rics (ckeditor)" - -#~ msgid "You may edit the copied page below." -#~ msgstr "Pots editar la plana copiada just aquí abaix." - -#~ msgid "" -#~ "You have replaced %s. You may continue editing the now-active page below." -#~ msgstr "" -#~ "Has reemplaçat %s. Pots continuar editant la ara plana activa aquí sota." - -#~ msgid "Move to" -#~ msgstr "Moure a" - -#~ msgid "Move" -#~ msgstr "Moure" - -#~ msgid "Replace page %(to_replace)s" -#~ msgstr "Reemplaçar la plana %(to_replace)s" - -#~ msgid "Create hidden copy of this page" -#~ msgstr "Crea una còpia oculta d'aquesta plana" - -#~ msgid "The %(name)s \"%(obj)s\" was changed successfully." -#~ msgstr "%(name)s \"%(obj)s\" fue modificado con éxito." - -#~ msgid "You may edit it again below." -#~ msgstr "Puedes volver a editar el elemento." - -#~ msgid "You may add another %s below." -#~ msgstr "Puedes añadir un nuevo %s aquí debajo." - -#~ msgid "Insert as child" -#~ msgstr "Incluir como descendente" - -#~ msgid "Insert before" -#~ msgstr "Incluir antes" - -#~ msgid "is visible" -#~ msgstr "es visible" - -#~ msgid "Video from unknown portal" -#~ msgstr "Vídeo de un portal desconocido" - -#~ msgid "Tree saved successfully." -#~ msgstr "Árbol guardado con éxito." - -#~ msgid "Cannot make a node a child of itself." -#~ msgstr "No se puede ser dependiente de si mismo." - -#~ msgid "Delete" -#~ msgstr "Borrar" - -#~ msgid "Save and add another" -#~ msgstr "Guardar y añadir otro" - -#~ msgid "Save and continue editing" -#~ msgstr "Guardar y continuar a editar" - -#~ msgid "Properties" -#~ msgstr "Propiedades" - -#~ msgid "Move selected item to" -#~ msgstr "Mover elemento seleccionado para" - -#~ msgid "OK" -#~ msgstr "Vale" - -#~ msgid "Add %(name)s" -#~ msgstr "Añadir %(name)s" - -#~ msgid "Edit" -#~ msgstr "editar" - -#~ msgid "Database error" -#~ msgstr "Error de base de dados" - -#~ msgid "view content" -#~ msgstr "mostrar contenido" - -#~ msgid "" -#~ "Could not parse the view content because the view is excluded from " -#~ "infanta handling." -#~ msgstr "" -#~ "No fue posible leer el contenido de la vista, porque esta está excluida " -#~ "del tratamiento de infanta." - -#~ msgid "Placeholder for the %(viewname)s calling %(viewfunc)s" -#~ msgstr "Substituto para %(viewname)s, que llama %(viewfunc)s" +#~ msgid "plain" +#~ msgstr "plà" -#~ msgid "Placeholder for calling %(viewfunc)s" -#~ msgstr "Substituto para llamar %(viewfunc)s" +#~ msgid "title row" +#~ msgstr "títol de la fila" -#~ msgid "not active" -#~ msgstr "inactivo" +#~ msgid "title row and column" +#~ msgstr "títol de fila i columna" -#~ msgid "Save tree" -#~ msgstr "Guardar árbol" +#~ msgid "table" +#~ msgstr "taula" -#~ msgid "%(icon)s (not active)" -#~ msgstr "%(icon)s (inactivo)" +#~ msgid "tables" +#~ msgstr "taules" -#~ msgid "%(icon)s (until %(from)s)" -#~ msgstr "%(icon)s (hasta %(from)s)" +#~ msgid "data" +#~ msgstr "dades" -#~ msgid "%(icon)s (since %(to)s)" -#~ msgstr "%(icon)s (a partir de %(to)s)" +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Això serà afegit a la llista de paraules clau per defecte" -#~ msgid "%(icon)s (%(from)s – %(to)s)" -#~ msgstr "%(icon)s (%(from)s – %(to)s)" +#~ msgid "This will be prepended to the default description." +#~ msgstr "Això serà afegit a la descripció per defecte" diff --git a/feincms/locale/cs/LC_MESSAGES/django.mo b/feincms/locale/cs/LC_MESSAGES/django.mo new file mode 100644 index 000000000..960bb25a7 Binary files /dev/null and b/feincms/locale/cs/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/cs/LC_MESSAGES/django.po b/feincms/locale/cs/LC_MESSAGES/django.po new file mode 100644 index 000000000..fea739112 --- /dev/null +++ b/feincms/locale/cs/LC_MESSAGES/django.po @@ -0,0 +1,1052 @@ +# 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 , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \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>=2 && n<=4) ? 1 : 2\n" + +#: admin/filterspecs.py:47 admin/filterspecs.py:86 +msgid "All" +msgstr "vše" + +#: admin/filterspecs.py:58 module/page/models.py:178 +msgid "Parent" +msgstr "rodič" + +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 +msgid "Category" +msgstr "kategorie" + +#: admin/item_editor.py:190 +#, python-format +msgid "Change %s" +msgstr "změň %s" + +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 +msgid "title" +msgstr "Název" + +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +#, fuzzy +#| msgid "You don't have the necessary permissions to edit this object" +msgid "You do not have permission to modify this object" +msgstr "Nemáte dostatečná oprávnění pro editaci tohoto objektu" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 +#, python-format +msgid "%s has been moved to a new position." +msgstr "%s přesunut na novou pozici" + +#: admin/tree_editor.py:512 +msgid "Did not understand moving instruction." +msgstr "Nesrozumitelné přesunutí" + +#: admin/tree_editor.py:523 +msgid "actions" +msgstr "akce" + +#: admin/tree_editor.py:552 +#, fuzzy, python-format +#| msgid "Successfully added %(count)d media file to %(category)s." +#| msgid_plural "Successfully added %(count)d media files to %(category)s." +msgid "Successfully deleted %(count)d items." +msgstr "Úspěšně přidáno %(count)d mediálních souborů do %(category)s." + +#: admin/tree_editor.py:565 +#, fuzzy, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "Obnov smazané %(verbose_name)s" + +#: content/application/models.py:147 +msgid "application content" +msgstr "aplikační obsah" + +#: content/application/models.py:148 +msgid "application contents" +msgstr "aplikační obsahy" + +#: content/application/models.py:179 +msgid "application" +msgstr "aplikace" + +#: content/comments/models.py:32 +msgid "enabled" +msgstr "zapnutý" + +#: content/comments/models.py:33 +msgid "New comments may be added" +msgstr "Komentáře povoleny" + +#: content/comments/models.py:37 content/comments/models.py:38 +msgid "comments" +msgstr "komentáře" + +#: content/comments/models.py:60 +msgid "public" +msgstr "veřejný" + +#: content/comments/models.py:61 +msgid "not public" +msgstr "neveřejný" + +#: content/contactform/models.py:20 +msgid "name" +msgstr "jméno" + +#: content/contactform/models.py:21 +msgid "email" +msgstr "mail" + +#: content/contactform/models.py:22 +msgid "subject" +msgstr "předmět" + +#: content/contactform/models.py:26 content/raw/models.py:16 +msgid "content" +msgstr "obsah" + +#: content/contactform/models.py:37 +msgid "contact form" +msgstr "kontaktní formulář" + +#: content/contactform/models.py:38 +msgid "contact forms" +msgstr "kontaktní formuláře" + +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 +msgid "file" +msgstr "soubor" + +#: content/file/models.py:29 +msgid "files" +msgstr "soubory" + +#: content/image/models.py:46 content/image/models.py:55 +msgid "image" +msgstr "obrázek" + +#: content/image/models.py:49 +#, fuzzy +msgid "alternate text" +msgstr "šablonový obsah" + +#: content/image/models.py:50 +#, fuzzy +msgid "Description of image" +msgstr "popis" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "název" + +#: content/image/models.py:56 +msgid "images" +msgstr "obrázky" + +#: content/image/models.py:82 +msgid "position" +msgstr "pozice" + +#: content/image/models.py:90 +msgid "format" +msgstr "" + +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 +msgid "media file" +msgstr "mediální soubor" + +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 +msgid "media files" +msgstr "mediální soubory" + +#: content/medialibrary/models.py:64 content/section/models.py:59 +msgid "type" +msgstr "typ" + +#: content/raw/models.py:20 +msgid "raw content" +msgstr "binární obsah" + +#: content/raw/models.py:21 +msgid "raw contents" +msgstr "binární obsahy" + +#: content/richtext/models.py:24 +msgid "HTML Tidy" +msgstr "čisté HTML " + +#: content/richtext/models.py:25 +msgid "Ignore the HTML validation warnings" +msgstr "ignorovat HTML validační varovnání" + +#: content/richtext/models.py:55 +#, python-format +msgid "" +"HTML validation produced %(count)d warnings. Please review the updated " +"content below before continuing: %(messages)s" +msgstr "" +"HTML validace vyplivla %(count)d varování. Prosím zkontroluj obsah níže před " +"pokračováním: %(messages)s" + +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "text" + +#: content/richtext/models.py:103 +msgid "rich text" +msgstr "" + +#: content/richtext/models.py:104 +msgid "rich texts" +msgstr "" + +#: content/rss/models.py:25 +msgid "" +"The rss field is updated several times a day. A change in the title will " +"only be visible on the home page after the next feed update." +msgstr "" +"RSS pole je měněno několikrát za den. Změna v titulku bude patrná při přístí " +"změně RSS kanálu." + +#: content/rss/models.py:28 +msgid "link" +msgstr "odkaz" + +#: content/rss/models.py:30 +msgid "pre-rendered content" +msgstr "predrenderovaný obsah" + +#: content/rss/models.py:32 +msgid "last updated" +msgstr "naposledy změněno" + +#: content/rss/models.py:33 +msgid "max. items" +msgstr "max. položek" + +#: content/rss/models.py:37 +msgid "RSS feed" +msgstr "RSS kanál" + +#: content/rss/models.py:38 +msgid "RSS feeds" +msgstr "RSS kanály" + +#: content/section/models.py:43 +msgid "section" +msgstr "sekce" + +#: content/section/models.py:44 +msgid "sections" +msgstr "sekce" + +#: content/template/models.py:53 +msgid "template content" +msgstr "šablonový obsah" + +#: content/template/models.py:54 +msgid "template contents" +msgstr "šablonové obsahy" + +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "šablona" + +#: content/video/models.py:34 +msgid "video link" +msgstr "odkaz na video" + +#: content/video/models.py:36 +msgid "" +"This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." +"com/watch?v=zmj1rpzDRZ0" +msgstr "" +"Toto má být odkaz na youtube nebo vimeo video, např. http://www.youtube.com/" +"watch?v=zmj1rpzDRZ0" + +#: content/video/models.py:41 +msgid "video" +msgstr "video" + +#: content/video/models.py:42 +msgid "videos" +msgstr "videoa" + +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "Tagy" + +#: models.py:550 +msgid "ordering" +msgstr "řazení" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "tagy" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "jazyk" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "překlad" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "Nech prázdné pro články v primárním jazyce" + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "překlady k dispozici" + +#: module/blog/models.py:33 +msgid "published" +msgstr "publikovaný" + +#: module/blog/models.py:36 +msgid "This is used for the generated navigation too." +msgstr "Toto je také použito pro generovanou navigaci" + +#: module/blog/models.py:40 +msgid "published on" +msgstr "publikovaný" + +#: module/blog/models.py:42 +msgid "Will be set automatically once you tick the `published` checkbox above." +msgstr "" +"Bude automaticky nastaven jakmile klikneš na zaškrtávátko `publikovaný` výše." + +#: module/blog/models.py:48 +msgid "entry" +msgstr "Článek" + +#: module/blog/models.py:49 +msgid "entries" +msgstr "Články" + +#: module/extensions/changedate.py:41 +msgid "creation date" +msgstr "datum vytvoření" + +#: module/extensions/changedate.py:43 +msgid "modification date" +msgstr "datum úpravy" + +#: module/extensions/ct_tracker.py:156 +msgid "content types" +msgstr "typy obsahu" + +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "datum publikace" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "konec publikace" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "Nech prázdné pro články, které mají být publikovány stále" + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "Publikováno od - do" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "Publikování na základě data" + +#: module/extensions/featured.py:15 +msgid "featured" +msgstr "zvýrazněný" + +#: module/extensions/featured.py:21 +msgid "Featured" +msgstr "zvýrazněný" + +#: module/extensions/seo.py:16 +msgid "meta keywords" +msgstr "meta klíčová slova" + +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" + +#: module/extensions/seo.py:20 +msgid "meta description" +msgstr "meta popis" + +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" + +#: module/extensions/seo.py:32 +msgid "Search engine optimization" +msgstr "Optimalizace pro vyhledávače" + +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Vytvoř překlad" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "překlady" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" +"Nemůže být přepsán jiným typem (pokoušíš se přepsat %(old_ext)s s " +"%(new_ext)s)" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "Úspěšně přidáno %(count)d mediálních souborů do %(category)s." +msgstr[1] "Úspěšně přidáno %(count)d mediálních souborů do %(category)s." + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "Přiděj vybrané mediální soubory do kategorie" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "ZIP archiv exporotován jako %s" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "export ZIP archivu selhal: %s" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "Vyexportuj vybrané mediální soubory jako zip archiv" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "náhled" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "velikost souboru" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "vytvoreno" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "typ souboru" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "info o souboru" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "%d souborů importováno" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "import ZIPu selhal: %s" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "Žádný vstupní soubor" + +#: module/medialibrary/models.py:49 +msgid "parent" +msgstr "rodič" + +#: module/medialibrary/models.py:51 module/page/models.py:175 +msgid "slug" +msgstr "" + +#: module/medialibrary/models.py:55 +msgid "category" +msgstr "kategorie" + +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 +msgid "categories" +msgstr "kategorie" + +#: module/medialibrary/models.py:101 +msgid "copyright" +msgstr "" + +#: module/medialibrary/models.py:219 +msgid "Image" +msgstr "obrázek" + +#: module/medialibrary/models.py:221 +msgid "Video" +msgstr "" + +#: module/medialibrary/models.py:224 +msgid "Audio" +msgstr "" + +#: module/medialibrary/models.py:226 +msgid "PDF document" +msgstr "PDF dokument" + +#: module/medialibrary/models.py:227 +msgid "Flash" +msgstr "" + +#: module/medialibrary/models.py:228 +msgid "Text" +msgstr "" + +#: module/medialibrary/models.py:229 +msgid "Rich Text" +msgstr "" + +#: module/medialibrary/models.py:230 +msgid "Zip archive" +msgstr "" + +#: module/medialibrary/models.py:231 +msgid "Microsoft Word" +msgstr "" + +#: module/medialibrary/models.py:233 +msgid "Microsoft Excel" +msgstr "" + +#: module/medialibrary/models.py:235 +msgid "Microsoft PowerPoint" +msgstr "" + +#: module/medialibrary/models.py:237 +msgid "Binary" +msgstr "Binární" + +#: module/medialibrary/models.py:261 +msgid "description" +msgstr "popis" + +#: module/medialibrary/models.py:264 +msgid "media file translation" +msgstr "překlad mediálního souboru" + +#: module/medialibrary/models.py:265 +msgid "media file translations" +msgstr "překlady mediálního souboru" + +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" +msgstr "" + +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." +msgstr "" + +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" +msgstr "výňatek" + +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 +msgid "navigation extension" +msgstr "rozšíření navigace" + +#: module/page/extensions/navigation.py:119 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." +msgstr "" +"Vyber modul nabízející podstránky pro tuto stránku jestli chceš upravit " +"navigaci." + +#: module/page/extensions/navigation.py:134 +msgid "Navigation extension" +msgstr "rozšíření navigace" + +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" +msgstr "" + +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" + +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" +msgstr "v navigaci" + +#: module/page/extensions/relatedpages.py:21 +msgid "Select pages that should be listed as related content." +msgstr "Vyber příbuzné stránky" + +#: module/page/extensions/relatedpages.py:26 +msgid "Related pages" +msgstr "Příbuzné stránky" + +#: module/page/extensions/sites.py:21 +msgid "Site" +msgstr "" + +#: module/page/extensions/symlinks.py:22 +msgid "symlinked page" +msgstr "propojená stránka" + +#: module/page/extensions/symlinks.py:23 +msgid "All content is inherited from this page if given." +msgstr "Všechen obsah je poděděn do této stránky, pakliže vybrána" + +#: module/page/extensions/titles.py:19 +msgid "content title" +msgstr "Název obsahu" + +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." +msgstr "První řádka je hlavní titulek, ostatní řádky podtitulky" + +#: module/page/extensions/titles.py:26 +msgid "page title" +msgstr "titulek stránky" + +#: module/page/extensions/titles.py:30 +#, fuzzy +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." +msgstr "Titulek pro okno prohlížeče. Výchozí: stejné jako titulek stránky." + +#: module/page/extensions/titles.py:60 +msgid "Titles" +msgstr "Titulky" + +#: module/page/forms.py:187 +msgid "This URL is already taken by an active page." +msgstr "Toto URL je už obsazeno aktivní stránkou." + +#: module/page/forms.py:206 +msgid "This URL is already taken by another active page." +msgstr "Toto URL je už obsazeno jinou stránkou." + +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" + +#: module/page/modeladmins.py:42 +msgid "Other options" +msgstr "Další nastavení" + +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "v navigaci" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 +msgid "Add child page" +msgstr "Přidej podřízenou stránku" + +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 +msgid "View on site" +msgstr "Zobrazena na sajtě." + +#: module/page/modeladmins.py:142 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "Originálnío překlad zkopírován do nově vytvořené stránky." + +#: module/page/modeladmins.py:197 +msgid "You don't have the necessary permissions to edit this object" +msgstr "Nemáte dostatečná oprávnění pro editaci tohoto objektu" + +#: module/page/modeladmins.py:223 +msgid "inherited" +msgstr "zděděný" + +#: module/page/modeladmins.py:229 +msgid "extensions" +msgstr "rozšíření" + +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" +msgstr "je aktivní" + +#: module/page/models.py:169 +msgid "active" +msgstr "aktivní" + +#: module/page/models.py:173 +#, fuzzy +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "Toto je také použito pro generovanou navigaci" + +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" +msgstr "" + +#: module/page/models.py:184 +msgid "override URL" +msgstr "vynucené URL" + +#: module/page/models.py:186 +msgid "" +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." +msgstr "" +"Přepíše cílové URL. Ujisti se, že máš \"/\" na začátku a na konci pro " +"lokální URL.Toto ovlivňuje navigaci cílové stránky i podstránek" + +#: module/page/models.py:190 +msgid "redirect to" +msgstr "přesměruj na" + +#: module/page/models.py:193 +#, fuzzy +msgid "Target URL for automatic redirects or the primary key of a page." +msgstr "Cílová adresa pro automatické přeměrování" + +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "" + +#: module/page/models.py:298 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" +msgstr "" + +#: module/page/models.py:426 +msgid "page" +msgstr "stránka" + +#: module/page/models.py:427 +msgid "pages" +msgstr "stránky" + +#: templates/admin/feincms/_messages_js.html:4 +msgid "Really delete item?" +msgstr "" + +#: templates/admin/feincms/_messages_js.html:4 +msgid "Confirm to delete item" +msgstr "" + +#: templates/admin/feincms/_messages_js.html:5 +msgid "Item deleted successfully." +msgstr "" + +#: templates/admin/feincms/_messages_js.html:5 +msgid "Cannot delete item" +msgstr "" + +#: templates/admin/feincms/_messages_js.html:6 +msgid "Cannot delete item, because it is parent of at least one other item." +msgstr "Nemůžeš smazat tuto položku, protože má podřazená jiné." + +#: templates/admin/feincms/_messages_js.html:7 +msgid "Change template" +msgstr "Změň šablonu" + +#: templates/admin/feincms/_messages_js.html:8 +msgid "Really change template?
    All changes are saved." +msgstr "Opravdu změnit šablonu?
    Všechny změny uloženy." + +#: templates/admin/feincms/_messages_js.html:9 +#, fuzzy, python-format +msgid "" +"Really change template?
    All changes are saved and content from " +"%%(source_regions)s is moved to %%(target_region)s." +msgstr "" +"Opravdu změnit šablonu?
    Všechny změny uloženy a obsah " +"%(source_regions)s je přesunut do %(target_region)s." + +#: templates/admin/feincms/_messages_js.html:12 +msgid "Hide" +msgstr "Schovat" + +#: templates/admin/feincms/_messages_js.html:13 +msgid "Show" +msgstr "Ukázat" + +#: templates/admin/feincms/_messages_js.html:14 +msgid "After" +msgstr "Po" + +#: templates/admin/feincms/_messages_js.html:15 +msgid "Before" +msgstr "Před" + +#: templates/admin/feincms/_messages_js.html:16 +msgid "Insert new:" +msgstr "Přidat novou:" + +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 +msgid "Region empty" +msgstr "Region je prázdný" + +#: templates/admin/feincms/content_editor.html:25 +msgid "" +"Content from the parent site is automatically inherited. To override this " +"behaviour, add some content." +msgstr "" +"Obsah z nadřazené stránky je automaticky zděděn. Přidej nějaký obsah, který " +"ho přepíše." + +#: templates/admin/feincms/content_editor.html:33 +msgid "Add new item" +msgstr "Přidej novou položku" + +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "Přidej nové %(verbose_name)s" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "Smaž" + +#: templates/admin/feincms/fe_editor.html:30 +msgid "Save" +msgstr "Ulož" + +#: templates/admin/feincms/fe_tools.html:28 +msgid "Stop Editing" +msgstr "Ukončit editaci" + +#: templates/admin/feincms/fe_tools.html:33 +msgid "edit" +msgstr "edituj" + +#: templates/admin/feincms/fe_tools.html:35 +msgid "new" +msgstr "nový" + +#: templates/admin/feincms/fe_tools.html:36 +msgid "up" +msgstr "nahoru" + +#: templates/admin/feincms/fe_tools.html:37 +msgid "down" +msgstr "dolů" + +#: templates/admin/feincms/fe_tools.html:38 +msgid "remove" +msgstr "odeber" + +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "Edituj přímo na webu" + +#: templates/admin/feincms/page/page/item_editor.html:23 +#: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 +msgid "Home" +msgstr "Domů" + +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "Přidej" + +#: templates/admin/feincms/recover_form.html:11 +#, python-format +msgid "Recover deleted %(verbose_name)s" +msgstr "Obnov smazané %(verbose_name)s" + +#: templates/admin/feincms/recover_form.html:17 +msgid "Press the save button below to recover this version of the object." +msgstr "Zmáčkni tlačítko ulož níže pro obnovení této verze objektu." + +#: templates/admin/feincms/revision_form.html:12 +msgid "History" +msgstr "Historie" + +#: templates/admin/feincms/revision_form.html:13 +#, python-format +msgid "Revert %(verbose_name)s" +msgstr "Revertuj %(verbose_name)s" + +#: templates/admin/feincms/revision_form.html:24 +msgid "Press the save button below to revert to this version of the object." +msgstr "Zmáčkni tlačítko ulož níže pro revertování na tuto verzi objektu." + +#: templates/admin/feincms/tree_editor.html:22 +msgid "Shortcuts" +msgstr "Zkratky" + +#: templates/admin/feincms/tree_editor.html:24 +msgid "Collapse tree" +msgstr "Rozbal strom" + +#: templates/admin/feincms/tree_editor.html:25 +msgid "Expand tree" +msgstr "Zabal strom" + +#: templates/admin/feincms/tree_editor.html:29 +msgid "Filter" +msgstr "Filtruj" + +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr "" + +#: templates/admin/medialibrary/add_to_category.html:5 +#: templates/admin/medialibrary/add_to_category.html:9 +msgid "Add media files to category" +msgstr "Přidej mediální soubory do kategorie" + +#: templates/admin/medialibrary/add_to_category.html:11 +msgid "Select category to apply:" +msgstr "Vytvoř kategorii:" + +#: templates/admin/medialibrary/add_to_category.html:17 +msgid "The following media files will be added to the selected category:" +msgstr "Následující mediální soubory budou přidány do vybrané kategorie:" + +#: templates/admin/medialibrary/add_to_category.html:22 +msgid "Add to category" +msgstr "Přidej do kategorie" + +#: templates/admin/medialibrary/add_to_category.html:23 +msgid "Cancel" +msgstr "" + +#: templates/admin/medialibrary/mediafile/change_list.html:11 +msgid "Bulk upload a ZIP file:" +msgstr "Nahrát dávku v ZIP souboru:" + +#: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "Přepsat" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 +msgid "Send" +msgstr "Poslat" + +#: templates/content/comments/default.html:10 +#, python-format +msgid "%(comment_count)s comments." +msgstr "%(comment_count)s komentářů" + +#: templates/content/comments/default.html:18 +#, fuzzy, python-format +msgid "" +"\n" +" %(comment_username)s said on %(comment_submit_date)s
    \n" +" " +msgstr "%(comment_username)s řekl v %(comment_submit_date)s
    " + +#: templates/content/comments/default.html:28 +msgid "No comments." +msgstr "Žádné komentáře" + +#: templates/content/comments/default.html:36 +msgid "Post Comment" +msgstr "Poslat komentář" + +#: templates/content/contactform/form.html:9 +msgid "Submit" +msgstr "Odeslat" + +#: templates/content/contactform/thanks.html:3 +msgid "Thanks!" +msgstr "Díky!" + +#~ msgid "plain" +#~ msgstr "prostý" + +#~ msgid "title row" +#~ msgstr "titulní řádka" + +#~ msgid "title row and column" +#~ msgstr "titulní řádka a sloupec" + +#~ msgid "table" +#~ msgstr "tabulka" + +#~ msgid "tables" +#~ msgstr "tabulky" + +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Toto bude přidáno k výchozím klíčovým slovům" + +#~ msgid "This will be prepended to the default description." +#~ msgstr "Toto bude přidáno k výchozímu popisu" + +#~ msgid "(no caption)" +#~ msgstr "(bez názvu)" + +#~ msgid "block" +#~ msgstr "do bloku" + +#~ msgid "left" +#~ msgstr "vlevo" + +#~ msgid "right" +#~ msgstr "vpravo" + +#~ msgid "Search" +#~ msgstr "Vyhledat" + +#~ msgid "Symlinked page" +#~ msgstr "Propojená stránka" diff --git a/feincms/locale/de/LC_MESSAGES/django.mo b/feincms/locale/de/LC_MESSAGES/django.mo index a416160b0..764b5548c 100644 Binary files a/feincms/locale/de/LC_MESSAGES/django.mo and b/feincms/locale/de/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/de/LC_MESSAGES/django.po b/feincms/locale/de/LC_MESSAGES/django.po index fea929f44..3d165754f 100644 --- a/feincms/locale/de/LC_MESSAGES/django.po +++ b/feincms/locale/de/LC_MESSAGES/django.po @@ -1,716 +1,605 @@ # 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 , YEAR. # -#, fuzzy +# Translators: +# Matthias Kestenholz , 2011 +# sbaechler , 2013 msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:19-0500\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" +"POT-Creation-Date: 2015-09-02 22:19+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Matthias Kestenholz \n" +"Language-Team: German (http://www.transifex.com/projects/p/feincms/language/" +"de/)\n" +"Language: de\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" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "Template" - -#: models.py:553 -msgid "ordering" -msgstr "Sortierung" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "Sprache" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filters.py:49 admin/filters.py:97 msgid "All" msgstr "Alle" -#: admin/filterspecs.py:57 module/page/models.py:261 +#: admin/filters.py:60 module/page/models.py:165 msgid "Parent" msgstr "Übergeordnet" -#: admin/filterspecs.py:95 +#: admin/filters.py:108 +#: templates/admin/medialibrary/mediafile/change_list.html:13 msgid "Category" msgstr "Kategorie" -#: admin/item_editor.py:150 -#, python-format -msgid "Change %s" -msgstr "%s ändern" - -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:259 -#: module/page/models.py:338 +#: admin/tree_editor.py:290 module/medialibrary/models.py:45 +#: module/page/models.py:159 module/page/models.py:231 msgid "title" msgstr "Titel" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:349 admin/tree_editor.py:366 +msgid "You do not have permission to modify this object" +msgstr "" +"Sie haben die nötige Berechtigung nicht, um dieses Objekt zu bearbeiten" + +#: admin/tree_editor.py:482 +msgid "No permission" +msgstr "Keine Berechtigung" + +#: admin/tree_editor.py:500 #, python-format msgid "%s has been moved to a new position." msgstr "%s wurde an eine neue Position verschoben." -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:503 msgid "Did not understand moving instruction." msgstr "Unbekannter Verschiebe-Befehl." -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:514 msgid "actions" msgstr "Aktionen" -#: content/application/models.py:241 +#: admin/tree_editor.py:543 +#, python-format +#| msgid "Successfully deleted %(count)d items." +msgid "Successfully deleted %(count)d items." +msgstr "Erfolgreich %(count)d Einträge gelöscht." + +#: admin/tree_editor.py:556 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "Ausgewählte %(verbose_name_plural)s löschen" + +#: apps/contents.py:32 msgid "application content" msgstr "Applikation" -#: content/application/models.py:242 +#: apps/contents.py:33 msgid "application contents" msgstr "Applikationen" -#: content/application/models.py:273 +#: apps/contents.py:64 msgid "application" msgstr "Applikation" -#: content/comments/models.py:28 -msgid "enabled" -msgstr "aktiviert" - -#: content/comments/models.py:28 -msgid "New comments may be added" -msgstr "Neue Kommentare können hinzugefügt werden" - -#: content/comments/models.py:32 content/comments/models.py:33 -msgid "comments" -msgstr "Kommentare" - -#: content/comments/models.py:48 -msgid "public" -msgstr "veröffentlicht" - -#: content/comments/models.py:48 -msgid "not public" -msgstr "nicht öffentlich" - -#: content/contactform/models.py:18 -msgid "name" -msgstr "Name" - -#: content/contactform/models.py:19 -msgid "email" -msgstr "E-Mail" - -#: content/contactform/models.py:20 -msgid "subject" -msgstr "Betreff" - -#: content/contactform/models.py:23 content/raw/models.py:14 -msgid "content" -msgstr "Inhalt" - -#: content/contactform/models.py:34 -msgid "contact form" -msgstr "Kontaktformular" - -#: content/contactform/models.py:35 -msgid "contact forms" -msgstr "Kontaktformulare" - -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 -msgid "file" -msgstr "Datei" - -#: content/file/models.py:21 -msgid "files" -msgstr "Dateien" - -#: content/image/models.py:24 content/image/models.py:28 -msgid "image" -msgstr "Bild" - -#: content/image/models.py:29 -msgid "images" -msgstr "Bilder" - -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 -msgid "position" -msgstr "Position" - -#: content/medialibrary/models.py:38 -msgid "(no caption)" -msgstr "(Keine Legende)" - -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 +#: contents.py:50 module/medialibrary/fields.py:72 +#: module/medialibrary/models.py:112 msgid "media file" msgstr "Mediendatei" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 +#: contents.py:51 module/medialibrary/models.py:113 msgid "media files" msgstr "Mediendateien" -#: content/medialibrary/models.py:131 -msgid "block" -msgstr "Block" - -#: content/medialibrary/models.py:132 -msgid "left" -msgstr "Links" - -#: content/medialibrary/models.py:133 -msgid "right" -msgstr "Rechts" - -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: contents.py:63 contents.py:268 msgid "type" msgstr "Typ" -#: content/raw/models.py:18 +#: contents.py:89 +msgid "content" +msgstr "Inhalt" + +#: contents.py:93 msgid "raw content" msgstr "Roh-Inhalt" -#: content/raw/models.py:19 +#: contents.py:94 msgid "raw contents" msgstr "Roh-Inhalte" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 +#: contents.py:120 msgid "text" msgstr "Text" -#: content/richtext/models.py:26 -msgid "HTML Tidy" -msgstr "HTML Tidy" - -#: content/richtext/models.py:27 -msgid "Ignore the HTML validation warnings" -msgstr "HTML-Validierungswarnungen ignorieren" - -#: content/richtext/models.py:51 -#, python-format -msgid "" -"HTML validation produced %(count)d warnings. Please review the updated " -"content below before continuing: %(messages)s" -msgstr "" -"Die HTML-Validierung ergab %(count)d Warnungen. Bitte überprüfen Sie den " -"aktualisierten Inhalt bevor Sie fortfahren: %(messages)s" - -#: content/richtext/models.py:89 +#: contents.py:124 msgid "rich text" msgstr "Text" -#: content/richtext/models.py:90 +#: contents.py:125 msgid "rich texts" msgstr "Texte" -#: content/rss/models.py:21 -msgid "" -"The rss field is updated several times a day. A change in the title will " -"only be visible on the home page after the next feed update." -msgstr "" -"Der RSS Feed wird mehrmals täglich aktualisiert. Eine Änderung des Titels " -"erscheint erst nach der nächsten Feed-Aktualisierung auf der Webseite." +#: contents.py:166 +msgid "template content" +msgstr "Template" + +#: contents.py:167 +msgid "template contents" +msgstr "Templates" -#: content/rss/models.py:22 -msgid "link" -msgstr "Link" +#: contents.py:172 models.py:396 +msgid "template" +msgstr "Template" + +#: contents.py:214 contents.py:220 module/medialibrary/models.py:94 +msgid "file" +msgstr "Datei" + +#: contents.py:221 +msgid "files" +msgstr "Dateien" -#: content/rss/models.py:23 -msgid "pre-rendered content" -msgstr "Vor-gerenderter Inhalt" +#: contents.py:250 contents.py:255 +msgid "image" +msgstr "Bild" -#: content/rss/models.py:24 -msgid "last updated" -msgstr "Letzte Aktualisierung" +#: contents.py:256 +msgid "images" +msgstr "Bilder" -#: content/rss/models.py:25 -msgid "max. items" -msgstr "Maximale Anzahl" +#: contrib/tagging.py:138 +msgid "Tagging" +msgstr "Tagging" -#: content/rss/models.py:29 -msgid "RSS feed" -msgstr "RSS Feed" +#: extensions/changedate.py:41 +msgid "creation date" +msgstr "Erstellt um" -#: content/rss/models.py:30 -msgid "RSS feeds" -msgstr "RSS Feeds" +#: extensions/changedate.py:43 +msgid "modification date" +msgstr "Verändert um" -#: content/section/models.py:37 -msgid "section" -msgstr "Sektion" +#: extensions/ct_tracker.py:154 +msgid "content types" +msgstr "Inhaltstypen" -#: content/section/models.py:38 -msgid "sections" -msgstr "Sektionen" +#: extensions/datepublisher.py:82 +msgid "publication date" +msgstr "Veröffentlichen am" -#: content/table/models.py:62 -msgid "plain" -msgstr "schlicht" +#: extensions/datepublisher.py:86 +msgid "publication end date" +msgstr "Veröffentlicht bis" -#: content/table/models.py:63 -msgid "title row" -msgstr "Titelzeile" +#: extensions/datepublisher.py:89 +msgid "Leave empty if the entry should stay active forever." +msgstr "Leer lassen wenn das Element ewig aktiv bleiben soll." -#: content/table/models.py:65 -msgid "title row and column" -msgstr "Titelzeile und -spalte" +#: extensions/datepublisher.py:125 +msgid "visible from - to" +msgstr "sichtbar von – bis" -#: content/table/models.py:71 -msgid "table" -msgstr "Tabelle" +#: extensions/datepublisher.py:136 +msgid "Date-based publishing" +msgstr "Datumsbasierte Veröffentlichung" -#: content/table/models.py:72 -msgid "tables" -msgstr "Tabellen" +#: extensions/featured.py:15 +msgid "featured" +msgstr "Feature" -#: content/table/models.py:86 -msgid "data" -msgstr "Daten" +#: extensions/featured.py:18 +msgid "Featured" +msgstr "Feature" -#: content/template/models.py:62 -msgid "template content" -msgstr "Template" +#: extensions/seo.py:16 +msgid "meta keywords" +msgstr "Meta Begriffe" -#: content/template/models.py:63 -msgid "template contents" -msgstr "Templates" +#: extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "Begriffe werden von den meisten Suchmaschinen ignoriert." -#: content/video/models.py:23 -msgid "video link" -msgstr "Video-Link" +#: extensions/seo.py:20 +msgid "meta description" +msgstr "Meta Beschreibung" -#: content/video/models.py:24 +#: extensions/seo.py:22 msgid "" -"This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." -"com/watch?v=zmj1rpzDRZ0" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." msgstr "" -"Dies sollte ein Link zu einem Youtube- oder vimeo-Video sein, z.B.: http://" -"www.youtube.com/watch?v=zmj1rpzDRZ0" +"Dieser Text wird auf der Suchresultatsseite angezeigt. Für das SEO Ranking " +"wird er nicht verwendet. Sollte nicht länger als 140 Zeichen sein." -#: content/video/models.py:28 -msgid "video" -msgstr "Video" +#: extensions/seo.py:32 +msgid "Search engine optimization" +msgstr "Suchmaschinenoptimierung" -#: content/video/models.py:29 -msgid "videos" -msgstr "Videos" +#: extensions/translations.py:154 translations.py:282 +msgid "language" +msgstr "Sprache" -#: module/blog/models.py:31 -msgid "published" -msgstr "veröffentlicht" +#: extensions/translations.py:162 +msgid "translation of" +msgstr "Übersetzung von" + +#: extensions/translations.py:166 +msgid "Leave this empty for entries in the primary language." +msgstr "Dieses Feld für Einträge in der Primärsprache leer lassen." -#: module/blog/models.py:33 -msgid "This is used for the generated navigation too." -msgstr "Dies wird auch für die generierte Navigation verwendet." +#: extensions/translations.py:267 +msgid "Edit translation" +msgstr "Übersetzungen bearbeiten" -#: module/blog/models.py:36 -msgid "published on" -msgstr "veröffentlicht am" +#: extensions/translations.py:274 +msgid "Create translation" +msgstr "Übersetzung erstellen" -#: module/blog/models.py:37 -msgid "Will be set automatically once you tick the `published` checkbox above." -msgstr "Wird automatisch gesetzt, wenn `veröffentlicht` aktiviert ist." +#: extensions/translations.py:282 +msgid "translations" +msgstr "Übersetzungen" -#: module/blog/models.py:42 -msgid "entry" -msgstr "Eintrag" +#: models.py:511 +msgid "ordering" +msgstr "Sortierung" -#: module/blog/models.py:43 -msgid "entries" -msgstr "Einträge" +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "Dies würde eine Endlosschleife in der Hierarchie erzeugen." -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "Begriffe" +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" +"Dateien verschiedenen Typs können nicht überschrieben werden. (%(old_ext)s " +"durch %(new_ext)s)." -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "Übersetzung von" +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "Erfolgreich %(count)d Mediendatei zu %(category)s hinzugefügt." +msgstr[1] "Erfolgreich %(count)d Mediendateien zu %(category)s hinzugefügt." -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -msgid "Leave this empty for entries in the primary language." -msgstr "Dieses Feld für Einträge in der Primärsprache leer lassen." +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "Ausgewählte Mediendateien zu Kategorie hinzufügen" -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" -msgstr "Verfügbare Übersetzungen" +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "ZIP-Datei als %s exportiert." -#: module/extensions/changedate.py:38 -msgid "creation date" -msgstr "Erstellt um" +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "ZIP Export fehlgeschlagen: %s" -#: module/extensions/changedate.py:39 -msgid "modification date" -msgstr "Verändert um" +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "Ausgewählte Mediendateien als ZIP-Archiv exportieren." -#: module/extensions/ct_tracker.py:134 -msgid "content types" -msgstr "Inhaltstypen" +#: module/medialibrary/modeladmins.py:154 +#: templates/admin/feincms/page/page/item_editor.html:11 +msgid "Preview" +msgstr "Vorschau" -#: module/extensions/featured.py:9 -msgid "featured" -msgstr "Feature" +#: module/medialibrary/modeladmins.py:159 module/medialibrary/models.py:103 +msgid "file size" +msgstr "Dateigrösse" -#: module/extensions/featured.py:14 -msgid "Featured" -msgstr "Feature" +#: module/medialibrary/modeladmins.py:164 module/medialibrary/models.py:100 +msgid "created" +msgstr "Erstellt" -#: module/extensions/seo.py:9 -msgid "meta keywords" -msgstr "Meta Begriffe" +#: module/medialibrary/modeladmins.py:184 module/medialibrary/models.py:97 +msgid "file type" +msgstr "Dateityp" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "Diese Begriffe werden vor die Standard-Begriffsliste eingefügt." +#: module/medialibrary/modeladmins.py:208 +msgid "file info" +msgstr "Dateiinfo" -#: module/extensions/seo.py:11 -msgid "meta description" -msgstr "Meta Beschreibung" +#: module/medialibrary/modeladmins.py:221 +#, python-format +msgid "%d files imported" +msgstr "%d Dateien importiert" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." -msgstr "Diese Beschreibung wird vor der Standard-Beschreibung eingefügt." +#: module/medialibrary/modeladmins.py:223 +#, python-format +msgid "ZIP import failed: %s" +msgstr "ZIP Import fehlgeschlagen: %s" -#: module/extensions/seo.py:18 -msgid "Search engine optimization" -msgstr "Suchmaschinenoptimierung" +#: module/medialibrary/modeladmins.py:225 +msgid "No input file given" +msgstr " Keine Datei angegeben" -#: module/medialibrary/models.py:51 +#: module/medialibrary/models.py:49 msgid "parent" msgstr "Übergeordnet" -#: module/medialibrary/models.py:53 module/page/models.py:260 +#: module/medialibrary/models.py:51 module/page/models.py:162 msgid "slug" msgstr "Slug" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:55 msgid "category" msgstr "Kategorie" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 msgid "categories" msgstr "Kategorien" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" -msgstr "Dateityp" - -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" -msgstr "Erstellt" - -#: module/medialibrary/models.py:93 +#: module/medialibrary/models.py:101 msgid "copyright" msgstr "Copyright" -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 -msgid "file size" -msgstr "Dateigrösse" - -#: module/medialibrary/models.py:203 -msgid "file info" -msgstr "Dateiinfo" - -#: module/medialibrary/models.py:280 +#: module/medialibrary/models.py:219 msgid "Image" msgstr "Bild" -#: module/medialibrary/models.py:281 +#: module/medialibrary/models.py:221 msgid "Video" msgstr "Video" -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:224 msgid "Audio" msgstr "Audio" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:226 msgid "PDF document" msgstr "PDF-Dokument" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:227 msgid "Flash" msgstr "Flash" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:228 msgid "Text" msgstr "Text" -#: module/medialibrary/models.py:286 +#: module/medialibrary/models.py:229 msgid "Rich Text" msgstr "Text" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:230 msgid "Zip archive" -msgstr "" +msgstr "ZIP-Archiv" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:231 msgid "Microsoft Word" msgstr "Microsoft Word" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:233 msgid "Microsoft Excel" msgstr "Microsoft Excel" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:235 msgid "Microsoft PowerPoint" msgstr "Microsoft PowerPoint" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:237 msgid "Binary" msgstr "Binärdaten" -#: module/medialibrary/models.py:307 +#: module/medialibrary/models.py:260 msgid "caption" msgstr "Legende" -#: module/medialibrary/models.py:308 +#: module/medialibrary/models.py:261 msgid "description" msgstr "Beschreibung" -#: module/medialibrary/models.py:311 +#: module/medialibrary/models.py:264 msgid "media file translation" msgstr "Mediendatei-Übersetzung" -#: module/medialibrary/models.py:312 +#: module/medialibrary/models.py:265 msgid "media file translations" msgstr "Mediendatei-Übersetzungen" -#: module/medialibrary/models.py:335 -msgid "Preview" -msgstr "Vorschau" - -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" - -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" -msgstr "" - -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" -msgstr "" - -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" -msgstr "" - -#: module/medialibrary/models.py:448 -msgid "No input file given" -msgstr "" - -#: module/page/models.py:256 -msgid "active" -msgstr "Aktiv" - -#: module/page/models.py:263 module/page/models.py:711 -msgid "in navigation" -msgstr "Im Menü" - -#: module/page/models.py:264 -msgid "override URL" -msgstr "Überschriebene URL" - -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." -msgstr "" -"Überschreibe die URL. Am Anfang und Ende muss ein / stehen, falls es sich um " -"eine lokale URL handelt. Dieses Feld bestimmt die Navigation und die URLs " -"von Unterseiten." - -#: module/page/models.py:266 -msgid "redirect to" -msgstr "Weiterleiten zu" - -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." -msgstr "Ziel-URL für automatische Weiterleitungen." - -#: module/page/models.py:268 -msgid "Cached URL" -msgstr "Zwischengespeicherte URL" - -#: module/page/models.py:279 -msgid "page" -msgstr "Seite" - -#: module/page/models.py:280 -msgid "pages" -msgstr "Seiten" - -#: module/page/models.py:297 module/page/models.py:782 -msgid "is active" -msgstr "Aktiv" - -#: module/page/models.py:631 -msgid "This URL is already taken by an active page." -msgstr "Die URL wird schon von einer aktiven Seite verwendet." - -#: module/page/models.py:649 -msgid "This URL is already taken by another active page." -msgstr "Die URL wird schon von einer anderen aktiven Seite verwendet." - -#: module/page/models.py:674 -msgid "Other options" -msgstr "Weitere Optionen" - -#: module/page/models.py:721 -msgid "Add child page" -msgstr "Unterseite hinzufügen" - -#: module/page/models.py:723 -msgid "View on site" -msgstr "Auf der Webseite anzeigen" - -#: module/page/models.py:759 -msgid "You don't have the necessary permissions to edit this object" -msgstr "" - -#: module/page/models.py:774 -msgid "inherited" -msgstr "geerbt" - -#: module/page/models.py:778 -msgid "extensions" -msgstr "Erweiterungen" - -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "Veröffentlichen am" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "Veröffentlicht bis" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "Leer lassen wenn das Element ewig aktiv bleiben soll." - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "sichtbar von – bis" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" -msgstr "Datumsbasierte Veröffentlichung" - -#: module/page/extensions/excerpt.py:9 +#: module/page/extensions/excerpt.py:18 msgid "excerpt" msgstr "Auszug" -#: module/page/extensions/excerpt.py:10 +#: module/page/extensions/excerpt.py:21 msgid "Add a brief excerpt summarizing the content of this page." msgstr "Kurze Zusammenfassung des Seiteninhaltes." -#: module/page/extensions/excerpt.py:12 +#: module/page/extensions/excerpt.py:25 msgid "Excerpt" msgstr "Auszug" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 +#: module/page/extensions/navigation.py:90 +#: module/page/extensions/navigation.py:152 msgid "navigation extension" msgstr "Navigations-Erweiterung" -#: module/page/extensions/navigation.py:99 +#: module/page/extensions/navigation.py:156 msgid "" "Select the module providing subpages for this page if you need to customize " "the navigation." msgstr "Wähle das Modul aus, welches weitere Navigationspunkte erstellt." -#: module/page/extensions/navigation.py:112 +#: module/page/extensions/navigation.py:179 msgid "Navigation extension" msgstr "Navigations-Erweiterung" -#: module/page/extensions/relatedpages.py:13 +#: module/page/extensions/navigationgroups.py:17 +msgid "Default" +msgstr "Standard" + +#: module/page/extensions/navigationgroups.py:18 +msgid "Footer" +msgstr "Fusszeile" + +#: module/page/extensions/navigationgroups.py:25 +msgid "navigation group" +msgstr "Navigationsgruppe" + +#: module/page/extensions/relatedpages.py:20 msgid "Select pages that should be listed as related content." msgstr "Seiten auswählen, welche als ähnlicher Inhalt angezeigt werden sollen." -#: module/page/extensions/relatedpages.py:20 +#: module/page/extensions/relatedpages.py:25 msgid "Related pages" msgstr "Verwandte Seiten" -#: module/page/extensions/sites.py:16 +#: module/page/extensions/sites.py:21 msgid "Site" -msgstr "" +msgstr "Seite" -#: module/page/extensions/symlinks.py:15 +#: module/page/extensions/symlinks.py:22 msgid "symlinked page" msgstr "Verbundene Seite" -#: module/page/extensions/symlinks.py:16 +#: module/page/extensions/symlinks.py:23 msgid "All content is inherited from this page if given." msgstr "" "Der angezeigte Inhalt wird durch den Inhalt der angegebenen Seite ersetzt." -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" -msgstr "Verbundene Seite" - -#: module/page/extensions/titles.py:13 +#: module/page/extensions/titles.py:19 msgid "content title" msgstr "Inhaltstitel" -#: module/page/extensions/titles.py:14 +#: module/page/extensions/titles.py:22 msgid "The first line is the main title, the following lines are subtitles." msgstr "Die erste Zeile ist der Haupttitel, die weiteren Zeilen Untertitel" -#: module/page/extensions/titles.py:15 +#: module/page/extensions/titles.py:26 msgid "page title" msgstr "Seitentitel" -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." +#: module/page/extensions/titles.py:30 +#| msgid "" +#| "Page title for browser window. Same as title bydefault. Must not be " +#| "longer than 70 characters." +msgid "" +"Page title for browser window. Same as title by default. Must be 69 " +"characters or fewer." msgstr "" "Seitentitel für das Browser-Fenster. Standardmässig gleich wie der Titel." +"Darf nicht länger als 69 Zeichen sein." -#: module/page/extensions/titles.py:43 +#: module/page/extensions/titles.py:60 msgid "Titles" msgstr "Titel" -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "Übersetzungen bearbeiten" +#: module/page/forms.py:185 +msgid "This URL is already taken by an active page." +msgstr "Die URL wird schon von einer aktiven Seite verwendet." -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "Übersetzung erstellen" +#: module/page/forms.py:204 +msgid "This URL is already taken by another active page." +msgstr "Die URL wird schon von einer anderen aktiven Seite verwendet." -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "Übersetzungen" +#: module/page/forms.py:209 +msgid "This page does not allow attachment of child pages" +msgstr "Diese Seite erlaubt keine Unterseiten" -#: templates/admin/filter.html:3 +#: module/page/modeladmins.py:42 +msgid "Other options" +msgstr "Weitere Optionen" + +#: module/page/modeladmins.py:80 module/page/models.py:169 +msgid "in navigation" +msgstr "Im Menü" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 +msgid "Add child page" +msgstr "Unterseite hinzufügen" + +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 +msgid "View on site" +msgstr "Auf der Webseite anzeigen" + +#: module/page/modeladmins.py:142 #, python-format -msgid " By %(filter_title)s " -msgstr " Nach %(filter_title)s " +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "%(language)s-Übersetzung für \"%(page)s\" hinzufügen" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "Der Inhalt der Originalseite wurde auf die neue Seite kopiert." + +#: module/page/modeladmins.py:197 +msgid "You don't have the necessary permissions to edit this object" +msgstr "" +"Sie haben die nötige Berechtigung nicht, um dieses Objekt zu bearbeiten" + +#: module/page/modeladmins.py:223 +msgid "inherited" +msgstr "geerbt" + +#: module/page/modeladmins.py:229 +msgid "extensions" +msgstr "Erweiterungen" + +#: module/page/modeladmins.py:233 module/page/models.py:212 +msgid "is active" +msgstr "Aktiv" + +#: module/page/models.py:156 +msgid "active" +msgstr "Aktiv" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "Suche" +#: module/page/models.py:160 +msgid "This title is also used for navigation menu items." +msgstr "Dieser Titel wird auch für die generierte Navigation verwendet." + +#: module/page/models.py:163 +msgid "This is used to build the URL for this page" +msgstr "Dies wird verwendet um die URL der Seite zu generieren" + +#: module/page/models.py:171 +msgid "override URL" +msgstr "Überschriebene URL" + +#: module/page/models.py:173 +msgid "" +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." +msgstr "" +"Überschreibe die URL. Am Anfang und Ende muss ein / stehen, falls es sich um " +"eine lokale URL handelt. Dieses Feld bestimmt die Navigation und die URLs " +"von Unterseiten." + +#: module/page/models.py:177 +msgid "redirect to" +msgstr "Weiterleiten zu" + +#: module/page/models.py:180 +msgid "Target URL for automatic redirects or the primary key of a page." +msgstr "" +"Adresse für automatische Weiterleitungen, oder Primärschlüssel einer Seite" + +#: module/page/models.py:183 +msgid "Cached URL" +msgstr "Zwischengespeicherte URL" + +#: module/page/models.py:285 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" +msgstr "" +"Diese %(page_class)s verwendet ein Singleton Template, und " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" + +#: module/page/models.py:376 +msgid "page" +msgstr "Seite" + +#: module/page/models.py:377 +msgid "pages" +msgstr "Seiten" #: templates/admin/feincms/_messages_js.html:4 msgid "Really delete item?" @@ -744,11 +633,12 @@ msgstr "Template wirklich ändern?
    Alle Änderungen werden gespeichert." #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s
    is moved to %%(target_region)s." msgstr "" -"Template wirklich ändern?
    Alle Änderungen werden gespeichert und " -"Inhalt aus %(source_regions)s nach " -"%(target_region)s verschoben." +"Template wechseln?
    Alle Änderungen werden sofort gespeichert und " +"Inhalt von " +"%%(source_region)s nach %%(target_region)s " +"verschoben." #: templates/admin/feincms/_messages_js.html:12 msgid "Hide" @@ -770,11 +660,24 @@ msgstr "Vorher" msgid "Insert new:" msgstr "Neu einfügen:" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/_messages_js.html:17 +msgid "Move to region:" +msgstr "Nach Region verschieben:" + +#: templates/admin/feincms/_messages_js.html:18 +#| msgid "Insert new:" +msgid "Insert new content:" +msgstr "Neuen Inhalt einfügen:" + +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "Inhalt von Original übernehmen" + +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "Region leer" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." @@ -782,90 +685,76 @@ msgstr "" "Inhalt wird von der übergeordneten Seite geerbt. Füge Inhalt hinzu, um " "dieses Verhalten zu ändern" -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "Neues Element hinzufügen" -#: templates/admin/feincms/fe_editor.html:46 -msgid "Save" -msgstr "Sichern" - -#: templates/admin/feincms/fe_tools.html:27 -msgid "Stop Editing" -msgstr "Fertig bearbeitet" - -#: templates/admin/feincms/fe_tools.html:32 -msgid "edit" -msgstr "bearbeiten" - -#: templates/admin/feincms/fe_tools.html:34 -msgid "new" -msgstr "neu" - -#: templates/admin/feincms/fe_tools.html:35 -msgid "up" -msgstr "rauf" +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "Weiteres %(verbose_name)s hinzufügen" -#: templates/admin/feincms/fe_tools.html:36 -msgid "down" -msgstr "runter" +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "Entfernen" -#: templates/admin/feincms/fe_tools.html:37 -msgid "remove" -msgstr "entfernen" +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "Verfügbare Übersetzungen" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:16 +#: templates/admin/feincms/page/page/item_editor.html:19 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:7 +#: templates/admin/feincms/revision_form.html:7 msgid "Home" msgstr "Startseite" +#: templates/admin/feincms/page/page/item_editor.html:23 +msgid "Add" +msgstr "Hinzufügen" + #: templates/admin/feincms/recover_form.html:10 #, python-format msgid "Recover deleted %(verbose_name)s" -msgstr "" +msgstr "%(verbose_name)s wiederherstellen" -#: templates/admin/feincms/recover_form.html:17 +#: templates/admin/feincms/recover_form.html:16 msgid "Press the save button below to recover this version of the object." -msgstr "" +msgstr "Speichern drücken, um diese Version dieses Objekts wiederherzustellen" -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:11 msgid "History" -msgstr "" +msgstr "Verlauf" -#: templates/admin/feincms/revision_form.html:15 +#: templates/admin/feincms/revision_form.html:12 #, python-format msgid "Revert %(verbose_name)s" -msgstr "" +msgstr "%(verbose_name)s zurücksetzen" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:23 msgid "Press the save button below to revert to this version of the object." -msgstr "" +msgstr "Speichern drücken, um zu dieser Version des Objektes zurückzukehren." -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "Schnellzugriffe" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" msgstr "Alles zuklappen" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" msgstr "Alles aufklappen" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "Filter" -#: templates/admin/feincms/page/page/item_editor.html:9 -msgid "Edit on site" -msgstr "In der Seite bearbeiten" - -#: templates/admin/feincms/page/page/item_editor.html:20 -msgid "Add" -msgstr "" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr " Nach %(filter_title)s " #: templates/admin/medialibrary/add_to_category.html:5 #: templates/admin/medialibrary/add_to_category.html:9 @@ -878,7 +767,8 @@ msgstr "Kategorie auswählen:" #: templates/admin/medialibrary/add_to_category.html:17 msgid "The following media files will be added to the selected category:" -msgstr "Die folgenden Mediendateien werdene zur ausgewählten Kategorie hinzugefügt:" +msgstr "" +"Die folgenden Mediendateien werdene zur ausgewählten Kategorie hinzugefügt:" #: templates/admin/medialibrary/add_to_category.html:22 msgid "Add to category" @@ -888,38 +778,18 @@ msgstr "Zu Kategorie hinzufügen" msgid "Cancel" msgstr "Abbrechen" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:10 msgid "Bulk upload a ZIP file:" msgstr "Massenupload mit ZIP Dateien:" -#: templates/admin/medialibrary/mediafile/change_list.html:21 +#: templates/admin/medialibrary/mediafile/change_list.html:20 +msgid "Overwrite" +msgstr "Überschreiben" + +#: templates/admin/medialibrary/mediafile/change_list.html:23 msgid "Send" msgstr "Senden" -#: templates/content/comments/default.html:10 -#, python-format -msgid "%(comment_count)s comments." -msgstr "%(comment_count)s Kommentare." - -#: templates/content/comments/default.html:18 -#, python-format -msgid "" -"\n" -" %(comment_username)s said on %(comment_submit_date)s
    \n" -" " -msgstr "" -"\n" -"%(comment_username)s schrieb am %(comment_submit_date)s
    " - -#: templates/content/comments/default.html:28 -msgid "No comments." -msgstr "Keine Kommentare." - -#: templates/content/comments/default.html:36 -msgid "Post Comment" -msgstr "Kommentar abschicken" - #: templates/content/contactform/form.html:9 msgid "Submit" msgstr "Senden" @@ -928,28 +798,195 @@ msgstr "Senden" msgid "Thanks!" msgstr "Danke!" -#~ msgid "rich text (ckeditor)" -#~ msgstr "Text (ckeditor)" +#~ msgid "Change %s" +#~ msgstr "%s ändern" + +#~ msgid "enabled" +#~ msgstr "aktiviert" -#~ msgid "rich texts (ckeditor)" -#~ msgstr "Texte (ckeditor)" +#~ msgid "New comments may be added" +#~ msgstr "Neue Kommentare können hinzugefügt werden" -#~ msgid "You may edit the copied page below." -#~ msgstr "Die kopierte Seite kann jetzt erneut bearbeitet werden." +#~ msgid "comments" +#~ msgstr "Kommentare" + +#~ msgid "public" +#~ msgstr "veröffentlicht" + +#~ msgid "not public" +#~ msgstr "nicht öffentlich" + +#~ msgid "name" +#~ msgstr "Name" + +#~ msgid "email" +#~ msgstr "E-Mail" + +#~ msgid "subject" +#~ msgstr "Betreff" + +#~ msgid "contact form" +#~ msgstr "Kontaktformular" + +#~ msgid "contact forms" +#~ msgstr "Kontaktformulare" + +#~ msgid "alternate text" +#~ msgstr "Alternativtext" + +#~ msgid "Description of image" +#~ msgstr "Bildlegende" + +#~ msgid "position" +#~ msgstr "Position" + +#~ msgid "format" +#~ msgstr "Format" + +#~ msgid "HTML Tidy" +#~ msgstr "HTML Tidy" + +#~ msgid "Ignore the HTML validation warnings" +#~ msgstr "HTML-Validierungswarnungen ignorieren" + +#~ msgid "" +#~ "HTML validation produced %(count)d warnings. Please review the updated " +#~ "content below before continuing: %(messages)s" +#~ msgstr "" +#~ "Die HTML-Validierung ergab %(count)d Warnungen. Bitte überprüfen Sie den " +#~ "aktualisierten Inhalt bevor Sie fortfahren: %(messages)s" + +#~ msgid "" +#~ "The rss field is updated several times a day. A change in the title will " +#~ "only be visible on the home page after the next feed update." +#~ msgstr "" +#~ "Der RSS Feed wird mehrmals täglich aktualisiert. Eine Änderung des Titels " +#~ "erscheint erst nach der nächsten Feed-Aktualisierung auf der Webseite." + +#~ msgid "link" +#~ msgstr "Link" + +#~ msgid "pre-rendered content" +#~ msgstr "Vor-gerenderter Inhalt" + +#~ msgid "last updated" +#~ msgstr "Letzte Aktualisierung" + +#~ msgid "max. items" +#~ msgstr "Maximale Anzahl" + +#~ msgid "RSS feed" +#~ msgstr "RSS Feed" + +#~ msgid "RSS feeds" +#~ msgstr "RSS Feeds" + +#~ msgid "section" +#~ msgstr "Sektion" + +#~ msgid "sections" +#~ msgstr "Sektionen" + +#~ msgid "video link" +#~ msgstr "Video-Link" #~ msgid "" -#~ "You have replaced %s. You may continue editing the now-active page below." +#~ "This should be a link to a youtube or vimeo video, i.e.: http://www." +#~ "youtube.com/watch?v=zmj1rpzDRZ0" #~ msgstr "" -#~ "Sie haben %s ersetzt und können nun die aktive Seite weiterbearbeiten." +#~ "Dies sollte ein Link zu einem Youtube- oder vimeo-Video sein, z.B.: " +#~ "http://www.youtube.com/watch?v=zmj1rpzDRZ0" + +#~ msgid "video" +#~ msgstr "Video" + +#~ msgid "videos" +#~ msgstr "Videos" + +#~ msgid "tags" +#~ msgstr "Begriffe" + +#~ msgid "published" +#~ msgstr "veröffentlicht" + +#~ msgid "This is used for the generated navigation too." +#~ msgstr "Dies wird auch für die generierte Navigation verwendet." + +#~ msgid "published on" +#~ msgstr "veröffentlicht am" + +#~ msgid "" +#~ "Will be set automatically once you tick the `published` checkbox above." +#~ msgstr "Wird automatisch gesetzt, wenn `veröffentlicht` aktiviert ist." + +#~ msgid "entry" +#~ msgstr "Eintrag" + +#~ msgid "entries" +#~ msgstr "Einträge" + +#~ msgid "Save" +#~ msgstr "Sichern" + +#~ msgid "Stop Editing" +#~ msgstr "Fertig bearbeitet" + +#~ msgid "edit" +#~ msgstr "bearbeiten" + +#~ msgid "new" +#~ msgstr "neu" + +#~ msgid "up" +#~ msgstr "rauf" + +#~ msgid "down" +#~ msgstr "runter" + +#~ msgid "remove" +#~ msgstr "entfernen" + +#~ msgid "Edit on site" +#~ msgstr "In der Seite bearbeiten" + +#~ msgid "%(comment_count)s comments." +#~ msgstr "%(comment_count)s Kommentare." + +#~ msgid "" +#~ "\n" +#~ " %(comment_username)s said on " +#~ "%(comment_submit_date)s
    \n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ "%(comment_username)s schrieb am %(comment_submit_date)s
    " + +#~ msgid "No comments." +#~ msgstr "Keine Kommentare." + +#~ msgid "Post Comment" +#~ msgstr "Kommentar abschicken" + +#~ msgid "plain" +#~ msgstr "schlicht" + +#~ msgid "title row" +#~ msgstr "Titelzeile" + +#~ msgid "title row and column" +#~ msgstr "Titelzeile und -spalte" + +#~ msgid "table" +#~ msgstr "Tabelle" -#~ msgid "Move to" -#~ msgstr "Verschieben nach" +#~ msgid "tables" +#~ msgstr "Tabellen" -#~ msgid "Move" -#~ msgstr "Verschieben nach" +#~ msgid "data" +#~ msgstr "Daten" -#~ msgid "Replace page %(to_replace)s" -#~ msgstr "Seite %(to_replace)s ersetzen" +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Diese Begriffe werden vor die Standard-Begriffsliste eingefügt." -#~ msgid "Create hidden copy of this page" -#~ msgstr "Versteckte Kopie dieser Seite erstellen" +#~ msgid "This will be prepended to the default description." +#~ msgstr "Diese Beschreibung wird vor der Standard-Beschreibung eingefügt." diff --git a/feincms/locale/en/LC_MESSAGES/django.mo b/feincms/locale/en/LC_MESSAGES/django.mo new file mode 100644 index 000000000..90b3c9b9e Binary files /dev/null and b/feincms/locale/en/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/en/LC_MESSAGES/django.po b/feincms/locale/en/LC_MESSAGES/django.po index a7e1462c7..da4414571 100644 --- a/feincms/locale/en/LC_MESSAGES/django.po +++ b/feincms/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-15 13:37+0200\n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,689 +17,737 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "" - -#: models.py:553 -msgid "ordering" -msgstr "" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filterspecs.py:47 admin/filterspecs.py:86 msgid "All" msgstr "" -#: admin/filterspecs.py:57 module/page/models.py:261 +#: admin/filterspecs.py:58 module/page/models.py:178 msgid "Parent" msgstr "" -#: admin/filterspecs.py:95 +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 msgid "Category" msgstr "" -#: admin/item_editor.py:152 +#: admin/item_editor.py:190 #, python-format msgid "Change %s" msgstr "" -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:258 -#: module/page/models.py:338 +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 msgid "title" msgstr "" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +msgid "You do not have permission to modify this object" +msgstr "" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 #, python-format msgid "%s has been moved to a new position." msgstr "" -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:512 msgid "Did not understand moving instruction." msgstr "" -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:523 msgid "actions" msgstr "" -#: content/application/models.py:190 +#: admin/tree_editor.py:552 +#, python-format +msgid "Successfully deleted %(count)d items." +msgstr "" + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +#: content/application/models.py:147 msgid "application content" msgstr "" -#: content/application/models.py:191 +#: content/application/models.py:148 msgid "application contents" msgstr "" -#: content/application/models.py:222 +#: content/application/models.py:179 msgid "application" msgstr "" -#: content/comments/models.py:28 +#: content/comments/models.py:32 msgid "enabled" msgstr "" -#: content/comments/models.py:28 +#: content/comments/models.py:33 msgid "New comments may be added" msgstr "" -#: content/comments/models.py:32 content/comments/models.py:33 +#: content/comments/models.py:37 content/comments/models.py:38 msgid "comments" msgstr "" -#: content/comments/models.py:48 +#: content/comments/models.py:60 msgid "public" msgstr "" -#: content/comments/models.py:48 +#: content/comments/models.py:61 msgid "not public" msgstr "" -#: content/contactform/models.py:18 +#: content/contactform/models.py:20 msgid "name" msgstr "" -#: content/contactform/models.py:19 +#: content/contactform/models.py:21 msgid "email" msgstr "" -#: content/contactform/models.py:20 +#: content/contactform/models.py:22 msgid "subject" msgstr "" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:26 content/raw/models.py:16 msgid "content" msgstr "" -#: content/contactform/models.py:34 +#: content/contactform/models.py:37 msgid "contact form" msgstr "" -#: content/contactform/models.py:35 +#: content/contactform/models.py:38 msgid "contact forms" msgstr "" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 msgid "file" msgstr "" -#: content/file/models.py:21 +#: content/file/models.py:29 msgid "files" msgstr "" -#: content/image/models.py:24 content/image/models.py:28 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "" -#: content/image/models.py:29 -msgid "images" +#: content/image/models.py:49 +msgid "alternate text" msgstr "" -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 -msgid "position" +#: content/image/models.py:50 +msgid "Description of image" msgstr "" -#: content/medialibrary/models.py:38 -msgid "(no caption)" +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" msgstr "" -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 -msgid "media file" +#: content/image/models.py:56 +msgid "images" msgstr "" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 -msgid "media files" +#: content/image/models.py:82 +msgid "position" msgstr "" -#: content/medialibrary/models.py:131 -msgid "block" +#: content/image/models.py:90 +msgid "format" msgstr "" -#: content/medialibrary/models.py:132 -msgid "left" +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 +msgid "media file" msgstr "" -#: content/medialibrary/models.py:133 -msgid "right" +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 +msgid "media files" msgstr "" -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: content/medialibrary/models.py:64 content/section/models.py:59 msgid "type" msgstr "" -#: content/raw/models.py:18 +#: content/raw/models.py:20 msgid "raw content" msgstr "" -#: content/raw/models.py:19 +#: content/raw/models.py:21 msgid "raw contents" msgstr "" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "" - -#: content/richtext/models.py:26 +#: content/richtext/models.py:24 msgid "HTML Tidy" msgstr "" -#: content/richtext/models.py:27 +#: content/richtext/models.py:25 msgid "Ignore the HTML validation warnings" msgstr "" -#: content/richtext/models.py:51 +#: content/richtext/models.py:55 #, python-format msgid "" "HTML validation produced %(count)d warnings. Please review the updated " "content below before continuing: %(messages)s" msgstr "" -#: content/richtext/models.py:89 +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "" + +#: content/richtext/models.py:103 msgid "rich text" msgstr "" -#: content/richtext/models.py:90 +#: content/richtext/models.py:104 msgid "rich texts" msgstr "" -#: content/rss/models.py:21 +#: content/rss/models.py:25 msgid "" "The rss field is updated several times a day. A change in the title will " "only be visible on the home page after the next feed update." msgstr "" -#: content/rss/models.py:22 +#: content/rss/models.py:28 msgid "link" msgstr "" -#: content/rss/models.py:23 +#: content/rss/models.py:30 msgid "pre-rendered content" msgstr "" -#: content/rss/models.py:24 +#: content/rss/models.py:32 msgid "last updated" msgstr "" -#: content/rss/models.py:25 +#: content/rss/models.py:33 msgid "max. items" msgstr "" -#: content/rss/models.py:29 +#: content/rss/models.py:37 msgid "RSS feed" msgstr "" -#: content/rss/models.py:30 +#: content/rss/models.py:38 msgid "RSS feeds" msgstr "" -#: content/section/models.py:37 +#: content/section/models.py:43 msgid "section" msgstr "" -#: content/section/models.py:38 +#: content/section/models.py:44 msgid "sections" msgstr "" -#: content/table/models.py:62 -msgid "plain" +#: content/template/models.py:53 +msgid "template content" msgstr "" -#: content/table/models.py:63 -msgid "title row" +#: content/template/models.py:54 +msgid "template contents" msgstr "" -#: content/table/models.py:65 -msgid "title row and column" +#: content/template/models.py:63 models.py:400 +msgid "template" msgstr "" -#: content/table/models.py:71 -msgid "table" +#: content/video/models.py:34 +msgid "video link" msgstr "" -#: content/table/models.py:72 -msgid "tables" +#: content/video/models.py:36 +msgid "" +"This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." +"com/watch?v=zmj1rpzDRZ0" msgstr "" -#: content/table/models.py:86 -msgid "data" +#: content/video/models.py:41 +msgid "video" msgstr "" -#: content/template/models.py:62 -msgid "template content" +#: content/video/models.py:42 +msgid "videos" msgstr "" -#: content/template/models.py:63 -msgid "template contents" +#: contrib/tagging.py:132 +msgid "Tagging" msgstr "" -#: content/video/models.py:23 -msgid "video link" +#: models.py:550 +msgid "ordering" msgstr "" -#: content/video/models.py:24 -msgid "" -"This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." -"com/watch?v=zmj1rpzDRZ0" +#: module/blog/extensions/tags.py:17 +msgid "tags" msgstr "" -#: content/video/models.py:28 -msgid "video" +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" msgstr "" -#: content/video/models.py:29 -msgid "videos" +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." msgstr "" -#: module/blog/models.py:31 +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "" + +#: module/blog/models.py:33 msgid "published" msgstr "" -#: module/blog/models.py:33 module/page/models.py:259 +#: module/blog/models.py:36 msgid "This is used for the generated navigation too." msgstr "" -#: module/blog/models.py:36 +#: module/blog/models.py:40 msgid "published on" msgstr "" -#: module/blog/models.py:37 +#: module/blog/models.py:42 msgid "Will be set automatically once you tick the `published` checkbox above." msgstr "" -#: module/blog/models.py:42 +#: module/blog/models.py:48 msgid "entry" msgstr "" -#: module/blog/models.py:43 +#: module/blog/models.py:49 msgid "entries" msgstr "" -#: module/blog/extensions/tags.py:12 -msgid "tags" +#: module/extensions/changedate.py:41 +msgid "creation date" msgstr "" -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" +#: module/extensions/changedate.py:43 +msgid "modification date" msgstr "" -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -msgid "Leave this empty for entries in the primary language." +#: module/extensions/ct_tracker.py:156 +msgid "content types" msgstr "" -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" +#: module/extensions/datepublisher.py:86 +msgid "publication date" msgstr "" -#: module/extensions/changedate.py:38 -msgid "creation date" +#: module/extensions/datepublisher.py:90 +msgid "publication end date" msgstr "" -#: module/extensions/changedate.py:39 -msgid "modification date" +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." msgstr "" -#: module/extensions/ct_tracker.py:133 -msgid "content types" +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" msgstr "" -#: module/extensions/featured.py:9 +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "" + +#: module/extensions/featured.py:15 msgid "featured" msgstr "" -#: module/extensions/featured.py:14 +#: module/extensions/featured.py:21 msgid "Featured" msgstr "" -#: module/extensions/seo.py:9 +#: module/extensions/seo.py:16 msgid "meta keywords" msgstr "" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." msgstr "" -#: module/extensions/seo.py:11 +#: module/extensions/seo.py:20 msgid "meta description" msgstr "" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." msgstr "" -#: module/extensions/seo.py:18 +#: module/extensions/seo.py:32 msgid "Search engine optimization" msgstr "" -#: module/medialibrary/models.py:51 -msgid "parent" +#: module/extensions/translations.py:249 +msgid "Edit translation" msgstr "" -#: module/medialibrary/models.py:53 module/page/models.py:260 -msgid "slug" +#: module/extensions/translations.py:256 +msgid "Create translation" msgstr "" -#: module/medialibrary/models.py:57 -msgid "category" +#: module/extensions/translations.py:264 +msgid "translations" msgstr "" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 -msgid "categories" +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" msgstr "" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" msgstr "" -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "" +msgstr[1] "" + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" msgstr "" -#: module/medialibrary/models.py:93 -msgid "copyright" +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" msgstr "" -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 msgid "file size" msgstr "" -#: module/medialibrary/models.py:203 -msgid "file info" +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" msgstr "" -#: module/medialibrary/models.py:280 -msgid "Image" +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" msgstr "" -#: module/medialibrary/models.py:281 -msgid "Video" +#: module/medialibrary/modeladmins.py:211 +msgid "file info" msgstr "" -#: module/medialibrary/models.py:282 -msgid "Audio" +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" msgstr "" -#: module/medialibrary/models.py:283 -msgid "PDF document" +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" msgstr "" -#: module/medialibrary/models.py:284 -msgid "Flash" +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" msgstr "" -#: module/medialibrary/models.py:285 -msgid "Text" +#: module/medialibrary/models.py:49 +msgid "parent" msgstr "" -#: module/medialibrary/models.py:286 -msgid "Rich Text" +#: module/medialibrary/models.py:51 module/page/models.py:175 +msgid "slug" msgstr "" -#: module/medialibrary/models.py:287 -msgid "Zip archive" +#: module/medialibrary/models.py:55 +msgid "category" msgstr "" -#: module/medialibrary/models.py:288 -msgid "Microsoft Word" +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 +msgid "categories" msgstr "" -#: module/medialibrary/models.py:289 -msgid "Microsoft Excel" +#: module/medialibrary/models.py:101 +msgid "copyright" msgstr "" -#: module/medialibrary/models.py:290 -msgid "Microsoft PowerPoint" +#: module/medialibrary/models.py:219 +msgid "Image" msgstr "" -#: module/medialibrary/models.py:291 -msgid "Binary" +#: module/medialibrary/models.py:221 +msgid "Video" msgstr "" -#: module/medialibrary/models.py:307 -msgid "caption" +#: module/medialibrary/models.py:224 +msgid "Audio" msgstr "" -#: module/medialibrary/models.py:308 -msgid "description" +#: module/medialibrary/models.py:226 +msgid "PDF document" msgstr "" -#: module/medialibrary/models.py:311 -msgid "media file translation" +#: module/medialibrary/models.py:227 +msgid "Flash" msgstr "" -#: module/medialibrary/models.py:312 -msgid "media file translations" +#: module/medialibrary/models.py:228 +msgid "Text" msgstr "" -#: module/medialibrary/models.py:335 -#: templates/admin/feincms/page/page/item_editor.html:14 -msgid "Preview" +#: module/medialibrary/models.py:229 +msgid "Rich Text" msgstr "" -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" +#: module/medialibrary/models.py:230 +msgid "Zip archive" +msgstr "" -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" +#: module/medialibrary/models.py:231 +msgid "Microsoft Word" msgstr "" -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" +#: module/medialibrary/models.py:233 +msgid "Microsoft Excel" msgstr "" -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" +#: module/medialibrary/models.py:235 +msgid "Microsoft PowerPoint" msgstr "" -#: module/medialibrary/models.py:448 -msgid "No input file given" +#: module/medialibrary/models.py:237 +msgid "Binary" msgstr "" -#: module/page/models.py:255 -msgid "active" +#: module/medialibrary/models.py:261 +msgid "description" msgstr "" -#: module/page/models.py:263 module/page/models.py:812 -msgid "in navigation" +#: module/medialibrary/models.py:264 +msgid "media file translation" msgstr "" -#: module/page/models.py:264 -msgid "override URL" +#: module/medialibrary/models.py:265 +msgid "media file translations" msgstr "" -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" msgstr "" -#: module/page/models.py:266 -msgid "redirect to" +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." msgstr "" -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" msgstr "" -#: module/page/models.py:268 -msgid "Cached URL" +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 +msgid "navigation extension" msgstr "" -#: module/page/models.py:279 -msgid "page" +#: module/page/extensions/navigation.py:119 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." msgstr "" -#: module/page/models.py:280 -msgid "pages" +#: module/page/extensions/navigation.py:134 +msgid "Navigation extension" msgstr "" -#: module/page/models.py:297 module/page/models.py:884 -msgid "is active" +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" msgstr "" -#: module/page/models.py:735 -msgid "This URL is already taken by an active page." +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" msgstr "" -#: module/page/models.py:753 -msgid "This URL is already taken by another active page." +#: module/page/extensions/navigationgroups.py:28 +msgid "navigation group" msgstr "" -#: module/page/models.py:775 -msgid "Other options" +#: module/page/extensions/relatedpages.py:21 +msgid "Select pages that should be listed as related content." msgstr "" -#: module/page/models.py:823 -msgid "Add child page" +#: module/page/extensions/relatedpages.py:26 +msgid "Related pages" msgstr "" -#: module/page/models.py:825 templates/admin/feincms/content_inline.html:9 -msgid "View on site" +#: module/page/extensions/sites.py:21 +msgid "Site" msgstr "" -#: module/page/models.py:861 -msgid "You don't have the necessary permissions to edit this object" +#: module/page/extensions/symlinks.py:22 +msgid "symlinked page" msgstr "" -#: module/page/models.py:876 -msgid "inherited" +#: module/page/extensions/symlinks.py:23 +msgid "All content is inherited from this page if given." msgstr "" -#: module/page/models.py:880 -msgid "extensions" +#: module/page/extensions/titles.py:19 +msgid "content title" msgstr "" -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." msgstr "" -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" +#: module/page/extensions/titles.py:26 +msgid "page title" msgstr "" -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." +#: module/page/extensions/titles.py:30 +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." msgstr "" -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" +#: module/page/extensions/titles.py:60 +msgid "Titles" msgstr "" -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" +#: module/page/forms.py:187 +msgid "This URL is already taken by an active page." msgstr "" -#: module/page/extensions/excerpt.py:9 -msgid "excerpt" +#: module/page/forms.py:206 +msgid "This URL is already taken by another active page." msgstr "" -#: module/page/extensions/excerpt.py:10 -msgid "Add a brief excerpt summarizing the content of this page." +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" msgstr "" -#: module/page/extensions/excerpt.py:12 -msgid "Excerpt" +#: module/page/modeladmins.py:42 +msgid "Other options" msgstr "" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 -msgid "navigation extension" +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" msgstr "" -#: module/page/extensions/navigation.py:99 -msgid "" -"Select the module providing subpages for this page if you need to customize " -"the navigation." +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 +msgid "Add child page" msgstr "" -#: module/page/extensions/navigation.py:112 -msgid "Navigation extension" +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 +msgid "View on site" msgstr "" -#: module/page/extensions/relatedpages.py:13 -msgid "Select pages that should be listed as related content." +#: module/page/modeladmins.py:142 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" msgstr "" -#: module/page/extensions/relatedpages.py:20 -msgid "Related pages" +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." msgstr "" -#: module/page/extensions/sites.py:16 -msgid "Site" +#: module/page/modeladmins.py:197 +msgid "You don't have the necessary permissions to edit this object" msgstr "" -#: module/page/extensions/symlinks.py:15 -msgid "symlinked page" +#: module/page/modeladmins.py:223 +msgid "inherited" msgstr "" -#: module/page/extensions/symlinks.py:16 -msgid "All content is inherited from this page if given." +#: module/page/modeladmins.py:229 +msgid "extensions" msgstr "" -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" msgstr "" -#: module/page/extensions/titles.py:13 -msgid "content title" +#: module/page/models.py:169 +msgid "active" msgstr "" -#: module/page/extensions/titles.py:14 -msgid "The first line is the main title, the following lines are subtitles." +#: module/page/models.py:173 +msgid "This title is also used for navigation menu items." msgstr "" -#: module/page/extensions/titles.py:15 -msgid "page title" +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" msgstr "" -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." +#: module/page/models.py:184 +msgid "override URL" msgstr "" -#: module/page/extensions/titles.py:43 -msgid "Titles" +#: module/page/models.py:186 +msgid "" +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." msgstr "" -#: module/page/extensions/translations.py:171 -msgid "Edit translation" +#: module/page/models.py:190 +msgid "redirect to" msgstr "" -#: module/page/extensions/translations.py:174 -msgid "Create translation" +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." msgstr "" -#: module/page/extensions/translations.py:179 -msgid "translations" +#: module/page/models.py:196 +msgid "Cached URL" msgstr "" -#: templates/admin/filter.html:3 +#: module/page/models.py:298 #, python-format -msgid " By %(filter_title)s " +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" +msgstr "" + +#: module/page/models.py:426 +msgid "page" msgstr "" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" +#: module/page/models.py:427 +msgid "pages" msgstr "" #: templates/admin/feincms/_messages_js.html:4 @@ -734,7 +782,7 @@ msgstr "" #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s is moved to %%(target_region)s." msgstr "" #: templates/admin/feincms/_messages_js.html:12 @@ -757,65 +805,77 @@ msgstr "" msgid "Insert new:" msgstr "" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." msgstr "" -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "" -#: templates/admin/feincms/content_inline.html:91 +#: templates/admin/feincms/content_inline.html:93 #, python-format msgid "Add another %(verbose_name)s" msgstr "" -#: templates/admin/feincms/content_inline.html:94 +#: templates/admin/feincms/content_inline.html:96 msgid "Remove" msgstr "" -#: templates/admin/feincms/fe_editor.html:46 +#: templates/admin/feincms/fe_editor.html:30 msgid "Save" msgstr "" -#: templates/admin/feincms/fe_tools.html:27 +#: templates/admin/feincms/fe_tools.html:28 msgid "Stop Editing" msgstr "" -#: templates/admin/feincms/fe_tools.html:32 +#: templates/admin/feincms/fe_tools.html:33 msgid "edit" msgstr "" -#: templates/admin/feincms/fe_tools.html:34 +#: templates/admin/feincms/fe_tools.html:35 msgid "new" msgstr "" -#: templates/admin/feincms/fe_tools.html:35 +#: templates/admin/feincms/fe_tools.html:36 msgid "up" msgstr "" -#: templates/admin/feincms/fe_tools.html:36 +#: templates/admin/feincms/fe_tools.html:37 msgid "down" msgstr "" -#: templates/admin/feincms/fe_tools.html:37 +#: templates/admin/feincms/fe_tools.html:38 msgid "remove" msgstr "" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:22 +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "" + +#: templates/admin/feincms/page/page/item_editor.html:23 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 msgid "Home" msgstr "" -#: templates/admin/feincms/recover_form.html:10 +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "" + +#: templates/admin/feincms/recover_form.html:11 #, python-format msgid "Recover deleted %(verbose_name)s" msgstr "" @@ -824,41 +884,38 @@ msgstr "" msgid "Press the save button below to recover this version of the object." msgstr "" -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:12 msgid "History" msgstr "" -#: templates/admin/feincms/revision_form.html:15 +#: templates/admin/feincms/revision_form.html:13 #, python-format msgid "Revert %(verbose_name)s" msgstr "" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:24 msgid "Press the save button below to revert to this version of the object." msgstr "" -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" msgstr "" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" msgstr "" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "" -#: templates/admin/feincms/page/page/item_editor.html:9 -msgid "Edit on site" -msgstr "" - -#: templates/admin/feincms/page/page/item_editor.html:26 -msgid "Add" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " msgstr "" #: templates/admin/medialibrary/add_to_category.html:5 @@ -882,11 +939,15 @@ msgstr "" msgid "Cancel" msgstr "" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:11 msgid "Bulk upload a ZIP file:" msgstr "" #: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 msgid "Send" msgstr "" diff --git a/feincms/locale/es/LC_MESSAGES/django.mo b/feincms/locale/es/LC_MESSAGES/django.mo index 23fccec1a..377a4c155 100644 Binary files a/feincms/locale/es/LC_MESSAGES/django.mo and b/feincms/locale/es/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/es/LC_MESSAGES/django.po b/feincms/locale/es/LC_MESSAGES/django.po index 3edb94dc1..d0781b932 100644 --- a/feincms/locale/es/LC_MESSAGES/django.po +++ b/feincms/locale/es/LC_MESSAGES/django.po @@ -1,207 +1,203 @@ -# Traducción española de FeinCMS -# Copyright (C) 2009 proycon -# This file is distributed under the same license as the FeinCMS package. -# Maarten van Gompel (proycon) , 2009. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. # +# Translators: +# proycon , 2009 msgid "" msgstr "" "Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:19-0500\n" -"PO-Revision-Date: 2010-09-20 20:24+0100\n" -"Last-Translator: antoni aloy \n" -"Language-Team: es \n" -"Language: \n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Matthias Kestenholz \n" +"Language-Team: Spanish (http://www.transifex.com/projects/p/feincms/language/" +"es/)\n" +"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Poedit-Language: Spanish\n" -"X-Poedit-SourceCharset: utf-8\n" -"X-Poedit-Country: SPAIN\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "plantilla" - -#: models.py:553 -msgid "ordering" -msgstr "orden" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "idioma" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filterspecs.py:47 admin/filterspecs.py:86 msgid "All" msgstr "Todos" -#: admin/filterspecs.py:57 module/page/models.py:261 +#: admin/filterspecs.py:58 module/page/models.py:178 msgid "Parent" msgstr "Padre" -#: admin/filterspecs.py:95 +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 msgid "Category" msgstr "Categoría" -#: admin/item_editor.py:150 +#: admin/item_editor.py:190 #, python-format msgid "Change %s" msgstr "Modificar %s" -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:259 -#: module/page/models.py:338 +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 msgid "title" msgstr "título" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +msgid "You do not have permission to modify this object" +msgstr "" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 #, python-format msgid "%s has been moved to a new position." msgstr "%s ha sido movido a una nueva posición" -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:512 msgid "Did not understand moving instruction." msgstr "No entiendo la orden de movimiento" -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:523 msgid "actions" msgstr "acciones" -#: content/application/models.py:241 +#: admin/tree_editor.py:552 +#, python-format +msgid "Successfully deleted %(count)d items." +msgstr "" + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +#: content/application/models.py:147 msgid "application content" msgstr "contenido de la aplicación" -#: content/application/models.py:242 +#: content/application/models.py:148 msgid "application contents" msgstr "contenidos de la aplicación" -#: content/application/models.py:273 +#: content/application/models.py:179 msgid "application" msgstr "aplicación" -#: content/comments/models.py:28 +#: content/comments/models.py:32 msgid "enabled" msgstr "activo" -#: content/comments/models.py:28 +#: content/comments/models.py:33 msgid "New comments may be added" msgstr "Se podrán añadir nuevos comentarios" -#: content/comments/models.py:32 content/comments/models.py:33 +#: content/comments/models.py:37 content/comments/models.py:38 msgid "comments" msgstr "comentarios" -#: content/comments/models.py:48 +#: content/comments/models.py:60 msgid "public" msgstr "público" -#: content/comments/models.py:48 +#: content/comments/models.py:61 msgid "not public" msgstr "privado" -#: content/contactform/models.py:18 +#: content/contactform/models.py:20 msgid "name" msgstr "nombre" -#: content/contactform/models.py:19 +#: content/contactform/models.py:21 msgid "email" msgstr "e-mail" -#: content/contactform/models.py:20 +#: content/contactform/models.py:22 msgid "subject" msgstr "asunto" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:26 content/raw/models.py:16 msgid "content" msgstr "contenido" -#: content/contactform/models.py:34 +#: content/contactform/models.py:37 msgid "contact form" msgstr "formulario de contacto" -#: content/contactform/models.py:35 +#: content/contactform/models.py:38 msgid "contact forms" msgstr "formularios de contacto" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 msgid "file" msgstr "archivo" -#: content/file/models.py:21 +#: content/file/models.py:29 msgid "files" msgstr "archivos" -#: content/image/models.py:24 content/image/models.py:28 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "imagen" -#: content/image/models.py:29 +#: content/image/models.py:49 +msgid "alternate text" +msgstr "" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "leyenda" + +#: content/image/models.py:56 msgid "images" msgstr "imágenes" -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 +#: content/image/models.py:82 msgid "position" msgstr "posición" -#: content/medialibrary/models.py:38 -msgid "(no caption)" -msgstr "(sin leyenda)" +#: content/image/models.py:90 +msgid "format" +msgstr "" -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 msgid "media file" msgstr "archivo de medios" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 msgid "media files" msgstr "archivos de medios" -#: content/medialibrary/models.py:131 -msgid "block" -msgstr "bloque" - -#: content/medialibrary/models.py:132 -msgid "left" -msgstr "izquierda" - -#: content/medialibrary/models.py:133 -msgid "right" -msgstr "derecha" - -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: content/medialibrary/models.py:64 content/section/models.py:59 msgid "type" msgstr "tipo" -#: content/raw/models.py:18 +#: content/raw/models.py:20 msgid "raw content" msgstr "contenido crudo" -#: content/raw/models.py:19 +#: content/raw/models.py:21 msgid "raw contents" msgstr "contenidos crudos" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "texto" - -#: content/richtext/models.py:26 +#: content/richtext/models.py:24 msgid "HTML Tidy" msgstr "HTML Tidy" -#: content/richtext/models.py:27 +#: content/richtext/models.py:25 msgid "Ignore the HTML validation warnings" msgstr "Ignorar los avisos de validación HTML" -#: content/richtext/models.py:51 +#: content/richtext/models.py:55 #, python-format msgid "" "HTML validation produced %(count)d warnings. Please review the updated " @@ -210,15 +206,19 @@ msgstr "" "La validación del HTML produjo %(count)d avisos. Por favor revisa el " "contenido actualizado de la parte inferior antes de continuar: %(messages)s" -#: content/richtext/models.py:89 +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "texto" + +#: content/richtext/models.py:103 msgid "rich text" msgstr "texto rico" -#: content/richtext/models.py:90 +#: content/richtext/models.py:104 msgid "rich texts" msgstr "textos ricos" -#: content/rss/models.py:21 +#: content/rss/models.py:25 msgid "" "The rss field is updated several times a day. A change in the title will " "only be visible on the home page after the next feed update." @@ -226,75 +226,55 @@ msgstr "" "El feed RSS es actualizado varias veces por dia. Cambios en el título solo " "aparecen en la página después de la actualización del feed RSS siguiente." -#: content/rss/models.py:22 +#: content/rss/models.py:28 msgid "link" msgstr "enlace" -#: content/rss/models.py:23 +#: content/rss/models.py:30 msgid "pre-rendered content" msgstr "contenido pre-renderizado" -#: content/rss/models.py:24 +#: content/rss/models.py:32 msgid "last updated" msgstr "última actualización" -#: content/rss/models.py:25 +#: content/rss/models.py:33 msgid "max. items" msgstr "número máximo" -#: content/rss/models.py:29 +#: content/rss/models.py:37 msgid "RSS feed" msgstr "feed RSS" -#: content/rss/models.py:30 +#: content/rss/models.py:38 msgid "RSS feeds" msgstr "feeds RSS" -#: content/section/models.py:37 +#: content/section/models.py:43 msgid "section" msgstr "sección" -#: content/section/models.py:38 +#: content/section/models.py:44 msgid "sections" msgstr "secciones" -#: content/table/models.py:62 -msgid "plain" -msgstr "plano" - -#: content/table/models.py:63 -msgid "title row" -msgstr "título de la fila" - -#: content/table/models.py:65 -msgid "title row and column" -msgstr "título de fila y columna" - -#: content/table/models.py:71 -msgid "table" -msgstr "tabla" - -#: content/table/models.py:72 -msgid "tables" -msgstr "tablas" - -#: content/table/models.py:86 -msgid "data" -msgstr "datos" - -#: content/template/models.py:62 +#: content/template/models.py:53 msgid "template content" msgstr "plantilla de contenido" -#: content/template/models.py:63 +#: content/template/models.py:54 msgid "template contents" msgstr "plantilla de contenidos" -#: content/video/models.py:23 +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "plantilla" + +#: content/video/models.py:34 msgid "video link" msgstr "enlace de vídeo" -#: content/video/models.py:24 +#: content/video/models.py:36 msgid "" "This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." "com/watch?v=zmj1rpzDRZ0" @@ -302,341 +282,313 @@ msgstr "" "Debe ser un enlace a un vídeo de YouTube o Vimeo. Por ejemplo: http://www." "youtube.com/watch?v=zmj1rpzDRZ0" -#: content/video/models.py:28 +#: content/video/models.py:41 msgid "video" msgstr "vídeo" -#: content/video/models.py:29 +#: content/video/models.py:42 msgid "videos" msgstr "vídeos" -#: module/blog/models.py:31 +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "" + +#: models.py:550 +msgid "ordering" +msgstr "orden" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "etiquetas" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "idioma" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "traducción de" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "Deja este campo vacío para las entradas en el idioma base." + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "traducciones disponibles" + +#: module/blog/models.py:33 msgid "published" msgstr "publicado" -#: module/blog/models.py:33 +#: module/blog/models.py:36 msgid "This is used for the generated navigation too." msgstr "También será usado para la navegación generada automáticamente." -#: module/blog/models.py:36 +#: module/blog/models.py:40 msgid "published on" msgstr "publicado en" -#: module/blog/models.py:37 +#: module/blog/models.py:42 msgid "Will be set automatically once you tick the `published` checkbox above." msgstr "Se establecerá automáticamente cuando se marque en 'publicado'." -#: module/blog/models.py:42 +#: module/blog/models.py:48 msgid "entry" msgstr "entrada" -#: module/blog/models.py:43 +#: module/blog/models.py:49 msgid "entries" msgstr "entradas" -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "etiquetas" - -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "traducción de" - -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -msgid "Leave this empty for entries in the primary language." -msgstr "Deja este campo vacío para las entradas en el idioma base." - -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" -msgstr "traducciones disponibles" - -#: module/extensions/changedate.py:38 +#: module/extensions/changedate.py:41 msgid "creation date" msgstr "fecha de creación" -#: module/extensions/changedate.py:39 +#: module/extensions/changedate.py:43 msgid "modification date" msgstr "fecha de modificación" -#: module/extensions/ct_tracker.py:134 +#: module/extensions/ct_tracker.py:156 msgid "content types" msgstr "tipos de contenido" -#: module/extensions/featured.py:9 +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "fecha de publicación" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "publicar hasta" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "Si se deja en blanco la entrada permanecerá activa para siempre." + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "visible de - hasta" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "Publicación según la fecha" + +#: module/extensions/featured.py:15 msgid "featured" msgstr "categorizado" -#: module/extensions/featured.py:14 +#: module/extensions/featured.py:21 msgid "Featured" msgstr "Categorizado" -#: module/extensions/seo.py:9 +#: module/extensions/seo.py:16 msgid "meta keywords" msgstr "meta palabras clave" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "Será incluido antes de la lista de palabras clave." +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" -#: module/extensions/seo.py:11 +#: module/extensions/seo.py:20 msgid "meta description" msgstr "meta descripción" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." -msgstr "Será incluido antes de la meta descripción por defecto." +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" -#: module/extensions/seo.py:18 +#: module/extensions/seo.py:32 msgid "Search engine optimization" msgstr "Optimización para motores de búsqueda" -#: module/medialibrary/models.py:51 +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "Editar traducción" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Crear traducción" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "traducciones" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "" +msgstr[1] "" + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "Pre-visualización" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "tamaño de archivo" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "creado en" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "tipo de archivo" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "información del archivo" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "" + +#: module/medialibrary/models.py:49 msgid "parent" msgstr "padre" -#: module/medialibrary/models.py:53 module/page/models.py:260 +#: module/medialibrary/models.py:51 module/page/models.py:175 msgid "slug" msgstr "slug" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:55 msgid "category" msgstr "categoría" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 msgid "categories" msgstr "categorías" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" -msgstr "tipo de archivo" - -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" -msgstr "creado en" - -#: module/medialibrary/models.py:93 +#: module/medialibrary/models.py:101 msgid "copyright" msgstr "copyright" -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 -msgid "file size" -msgstr "tamaño de archivo" - -#: module/medialibrary/models.py:203 -msgid "file info" -msgstr "información del archivo" - -#: module/medialibrary/models.py:280 +#: module/medialibrary/models.py:219 msgid "Image" msgstr "Imagen" -#: module/medialibrary/models.py:281 +#: module/medialibrary/models.py:221 msgid "Video" msgstr "Vídeo" -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:224 msgid "Audio" msgstr "Audio" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:226 msgid "PDF document" msgstr "documento PDF" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:227 msgid "Flash" msgstr "Flash" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:228 msgid "Text" msgstr "Texto" -#: module/medialibrary/models.py:286 +#: module/medialibrary/models.py:229 msgid "Rich Text" msgstr "Texto rico" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:230 msgid "Zip archive" msgstr "" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:231 msgid "Microsoft Word" msgstr "Microsoft Word" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:233 msgid "Microsoft Excel" msgstr "Microsoft Excel" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:235 msgid "Microsoft PowerPoint" msgstr "Microsoft PowerPoint" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:237 msgid "Binary" msgstr "Binario" -#: module/medialibrary/models.py:307 -msgid "caption" -msgstr "leyenda" - -#: module/medialibrary/models.py:308 +#: module/medialibrary/models.py:261 msgid "description" msgstr "descripción" -#: module/medialibrary/models.py:311 +#: module/medialibrary/models.py:264 msgid "media file translation" msgstr "traducción del archivo de media" -#: module/medialibrary/models.py:312 +#: module/medialibrary/models.py:265 msgid "media file translations" msgstr "traducciones del archivo de media" -#: module/medialibrary/models.py:335 -msgid "Preview" -msgstr "Pre-visualización" - -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" - -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" -msgstr "" - -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" -msgstr "" - -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" -msgstr "" - -#: module/medialibrary/models.py:448 -msgid "No input file given" -msgstr "" - -#: module/page/models.py:256 -msgid "active" -msgstr "activo" - -#: module/page/models.py:263 module/page/models.py:711 -msgid "in navigation" -msgstr "en la navegación" - -#: module/page/models.py:264 -msgid "override URL" -msgstr "URL efectivo" - -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." -msgstr "" -"URL efectivo. Debe contener una '/' cuando se trata de un URL local. Este " -"campo afecta la navegación y los URLs de las sub-páginas." - -#: module/page/models.py:266 -msgid "redirect to" -msgstr "redirección a" - -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." -msgstr "URL de destino para redirecciones automáticas." - -#: module/page/models.py:268 -msgid "Cached URL" -msgstr "URL en cache" - -#: module/page/models.py:279 -msgid "page" -msgstr "página" - -#: module/page/models.py:280 -msgid "pages" -msgstr "páginas" - -#: module/page/models.py:297 module/page/models.py:782 -msgid "is active" -msgstr "activo" - -#: module/page/models.py:631 -msgid "This URL is already taken by an active page." -msgstr "Esta URL ya está en uso en una página activa" - -#: module/page/models.py:649 -msgid "This URL is already taken by another active page." -msgstr "Esta URL ya está en uno por otra página activa" - -#: module/page/models.py:674 -msgid "Other options" -msgstr "otras opciones" - -#: module/page/models.py:721 -msgid "Add child page" -msgstr "Añade una página hija" - -#: module/page/models.py:723 -msgid "View on site" -msgstr "Ver en sitio" - -#: module/page/models.py:759 -msgid "You don't have the necessary permissions to edit this object" -msgstr "" - -#: module/page/models.py:774 -msgid "inherited" -msgstr "heredado" - -#: module/page/models.py:778 -msgid "extensions" -msgstr "extensiones" - -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "fecha de publicación" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "publicar hasta" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "Si se deja en blanco la entrada permanecerá activa para siempre." - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "visible de - hasta" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" -msgstr "Publicación según la fecha" - -#: module/page/extensions/excerpt.py:9 +#: module/page/extensions/excerpt.py:18 msgid "excerpt" msgstr "extracto" -#: module/page/extensions/excerpt.py:10 +#: module/page/extensions/excerpt.py:21 msgid "Add a brief excerpt summarizing the content of this page." msgstr "Añade una breve descripción resumiendo el contenido de la página." -#: module/page/extensions/excerpt.py:12 +#: module/page/extensions/excerpt.py:25 msgid "Excerpt" msgstr "Extracto" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 msgid "navigation extension" msgstr "extensión de navegación" -#: module/page/extensions/navigation.py:99 +#: module/page/extensions/navigation.py:119 msgid "" "Select the module providing subpages for this page if you need to customize " "the navigation." @@ -644,77 +596,180 @@ msgstr "" "Selecciona el módulo que provee sub-páginas para esta pagina si necesitas " "personalizar la navegación." -#: module/page/extensions/navigation.py:112 +#: module/page/extensions/navigation.py:134 msgid "Navigation extension" msgstr "Extensión de navegación" -#: module/page/extensions/relatedpages.py:13 +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" +msgstr "" + +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" + +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" +msgstr "en la navegación" + +#: module/page/extensions/relatedpages.py:21 msgid "Select pages that should be listed as related content." msgstr "Selecciona las páginas que se mostrarán como contenido relacionado." -#: module/page/extensions/relatedpages.py:20 +#: module/page/extensions/relatedpages.py:26 msgid "Related pages" msgstr "Páginas relacionadas" -#: module/page/extensions/sites.py:16 +#: module/page/extensions/sites.py:21 msgid "Site" msgstr "" -#: module/page/extensions/symlinks.py:15 +#: module/page/extensions/symlinks.py:22 msgid "symlinked page" msgstr "página direccionada" -#: module/page/extensions/symlinks.py:16 +#: module/page/extensions/symlinks.py:23 msgid "All content is inherited from this page if given." msgstr "Todo el contenido es heredado de esta página." -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" -msgstr "Página enlazada" - -#: module/page/extensions/titles.py:13 +#: module/page/extensions/titles.py:19 msgid "content title" msgstr "título del contenido" -#: module/page/extensions/titles.py:14 +#: module/page/extensions/titles.py:22 msgid "The first line is the main title, the following lines are subtitles." msgstr "" "La primera línea es el título principal. Otras líneas serán subtítulos." -#: module/page/extensions/titles.py:15 +#: module/page/extensions/titles.py:26 msgid "page title" msgstr "título de la página" -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." +#: module/page/extensions/titles.py:30 +#, fuzzy +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." msgstr "" "Título de la página para la ventana del navegador. Si se omite utilizará el " "mismo título." -#: module/page/extensions/titles.py:43 +#: module/page/extensions/titles.py:60 msgid "Titles" msgstr "Títulos" -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "Editar traducción" +#: module/page/forms.py:187 +msgid "This URL is already taken by an active page." +msgstr "Esta URL ya está en uso en una página activa" -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "Crear traducción" +#: module/page/forms.py:206 +msgid "This URL is already taken by another active page." +msgstr "Esta URL ya está en uno por otra página activa" -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "traducciones" +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" -#: templates/admin/filter.html:3 +#: module/page/modeladmins.py:42 +msgid "Other options" +msgstr "otras opciones" + +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "en la navegación" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 +msgid "Add child page" +msgstr "Añade una página hija" + +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 +msgid "View on site" +msgstr "Ver en sitio" + +#: module/page/modeladmins.py:142 #, python-format -msgid " By %(filter_title)s " -msgstr "Por %(filter_title)s" +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "" + +#: module/page/modeladmins.py:197 +msgid "You don't have the necessary permissions to edit this object" +msgstr "" + +#: module/page/modeladmins.py:223 +msgid "inherited" +msgstr "heredado" + +#: module/page/modeladmins.py:229 +msgid "extensions" +msgstr "extensiones" + +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" +msgstr "activo" + +#: module/page/models.py:169 +msgid "active" +msgstr "activo" + +#: module/page/models.py:173 +#, fuzzy +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "También será usado para la navegación generada automáticamente." + +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" +msgstr "" + +#: module/page/models.py:184 +msgid "override URL" +msgstr "URL efectivo" + +#: module/page/models.py:186 +msgid "" +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." +msgstr "" +"URL efectivo. Debe contener una '/' cuando se trata de un URL local. Este " +"campo afecta la navegación y los URLs de las sub-páginas." + +#: module/page/models.py:190 +msgid "redirect to" +msgstr "redirección a" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "Buscar" +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." +msgstr "" + +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "URL en cache" + +#: module/page/models.py:298 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" +msgstr "" + +#: module/page/models.py:426 +msgid "page" +msgstr "página" + +#: module/page/models.py:427 +msgid "pages" +msgstr "páginas" #: templates/admin/feincms/_messages_js.html:4 msgid "Really delete item?" @@ -749,11 +804,8 @@ msgstr "¿Deas cambiar la plantilla?
    Todos los cambios se guardarán." #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s is moved to %%(target_region)s." msgstr "" -"¿Deseas cambiar la plantilla?
    Todos los cambios se guardarán y el " -"contenido desde %(source_regions)s se moverá a " -"%(target_region)s" #: templates/admin/feincms/_messages_js.html:12 msgid "Hide" @@ -775,11 +827,15 @@ msgstr "Antes" msgid "Insert new:" msgstr "Insertar nuevo:" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "Región vacía" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." @@ -787,46 +843,63 @@ msgstr "" "El contenido del sitio padre se hereda automáticamente. Para sobreescribir " "este comportamiento añade algún contenido." -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "Añadir nuevo elemento" -#: templates/admin/feincms/fe_editor.html:46 +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "" + +#: templates/admin/feincms/fe_editor.html:30 msgid "Save" msgstr "Guardar" -#: templates/admin/feincms/fe_tools.html:27 +#: templates/admin/feincms/fe_tools.html:28 msgid "Stop Editing" msgstr "Terminar edición" -#: templates/admin/feincms/fe_tools.html:32 +#: templates/admin/feincms/fe_tools.html:33 msgid "edit" msgstr "editar" -#: templates/admin/feincms/fe_tools.html:34 +#: templates/admin/feincms/fe_tools.html:35 msgid "new" msgstr "nuevo" -#: templates/admin/feincms/fe_tools.html:35 +#: templates/admin/feincms/fe_tools.html:36 msgid "up" msgstr "arriba" -#: templates/admin/feincms/fe_tools.html:36 +#: templates/admin/feincms/fe_tools.html:37 msgid "down" msgstr "abajo" -#: templates/admin/feincms/fe_tools.html:37 +#: templates/admin/feincms/fe_tools.html:38 msgid "remove" msgstr "borrar" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:16 +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "Editar en el sitio" + +#: templates/admin/feincms/page/page/item_editor.html:23 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 msgid "Home" msgstr "Página inicial" -#: templates/admin/feincms/recover_form.html:10 +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "" + +#: templates/admin/feincms/recover_form.html:11 #, python-format msgid "Recover deleted %(verbose_name)s" msgstr "" @@ -835,48 +908,44 @@ msgstr "" msgid "Press the save button below to recover this version of the object." msgstr "" -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:12 msgid "History" msgstr "" -#: templates/admin/feincms/revision_form.html:15 +#: templates/admin/feincms/revision_form.html:13 #, python-format msgid "Revert %(verbose_name)s" msgstr "" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:24 msgid "Press the save button below to revert to this version of the object." msgstr "" -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "Atajos" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" msgstr "Colapsar el árbol" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" msgstr "Expandir el árbol" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "Filtrar" -#: templates/admin/feincms/page/page/item_editor.html:9 -msgid "Edit on site" -msgstr "Editar en el sitio" - -#: templates/admin/feincms/page/page/item_editor.html:20 -msgid "Add" -msgstr "" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr "Por %(filter_title)s" #: templates/admin/medialibrary/add_to_category.html:5 #: templates/admin/medialibrary/add_to_category.html:9 -#, fuzzy msgid "Add media files to category" -msgstr "traducción del archivo de media" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:11 msgid "Select category to apply:" @@ -887,19 +956,22 @@ msgid "The following media files will be added to the selected category:" msgstr "" #: templates/admin/medialibrary/add_to_category.html:22 -#, fuzzy msgid "Add to category" -msgstr "categoría" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:23 msgid "Cancel" msgstr "" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:11 msgid "Bulk upload a ZIP file:" msgstr "Sube un archivo ZIP:" #: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 msgid "Send" msgstr "Enviar" @@ -936,117 +1008,26 @@ msgstr "Enviar" msgid "Thanks!" msgstr "¡Gracias!" -#~ msgid "rich text (ckeditor)" -#~ msgstr "texto rico (ckeditor)" - -#~ msgid "rich texts (ckeditor)" -#~ msgstr "textos ricos (ckeditor)" - -#~ msgid "You may edit the copied page below." -#~ msgstr "Puedes editar la página copiada aquí abajo." - -#~ msgid "" -#~ "You have replaced %s. You may continue editing the now-active page below." -#~ msgstr "" -#~ "Acabas de reemplazar %s. Puedes continuar editando la que ahora es la " -#~ "página activa aquí abajo." - -#~ msgid "Move to" -#~ msgstr "Mover a" - -#~ msgid "Move" -#~ msgstr "Mover" - -#~ msgid "Replace page %(to_replace)s" -#~ msgstr "Reemplazar la página %(to_replace)s" - -#~ msgid "Create hidden copy of this page" -#~ msgstr "Crea una copia oculta de esta página" - -#~ msgid "The %(name)s \"%(obj)s\" was changed successfully." -#~ msgstr "%(name)s \"%(obj)s\" fue modificado con éxito." - -#~ msgid "You may edit it again below." -#~ msgstr "Puedes volver a editar el elemento." - -#~ msgid "You may add another %s below." -#~ msgstr "Puedes añadir un nuevo %s aquí debajo." - -#~ msgid "Insert as child" -#~ msgstr "Incluir como descendente" - -#~ msgid "Insert before" -#~ msgstr "Incluir antes" - -#~ msgid "is visible" -#~ msgstr "es visible" - -#~ msgid "Video from unknown portal" -#~ msgstr "Vídeo de un portal desconocido" - -#~ msgid "Tree saved successfully." -#~ msgstr "Árbol guardado con éxito." - -#~ msgid "Cannot make a node a child of itself." -#~ msgstr "No se puede ser dependiente de si mismo." - -#~ msgid "Delete" -#~ msgstr "Borrar" - -#~ msgid "Save and add another" -#~ msgstr "Guardar y añadir otro" - -#~ msgid "Save and continue editing" -#~ msgstr "Guardar y continuar a editar" - -#~ msgid "Properties" -#~ msgstr "Propiedades" - -#~ msgid "Move selected item to" -#~ msgstr "Mover elemento seleccionado para" - -#~ msgid "OK" -#~ msgstr "Vale" - -#~ msgid "Add %(name)s" -#~ msgstr "Añadir %(name)s" - -#~ msgid "Edit" -#~ msgstr "editar" - -#~ msgid "Database error" -#~ msgstr "Error de base de dados" - -#~ msgid "view content" -#~ msgstr "mostrar contenido" - -#~ msgid "" -#~ "Could not parse the view content because the view is excluded from " -#~ "infanta handling." -#~ msgstr "" -#~ "No fue posible leer el contenido de la vista, porque esta está excluida " -#~ "del tratamiento de infanta." - -#~ msgid "Placeholder for the %(viewname)s calling %(viewfunc)s" -#~ msgstr "Substituto para %(viewname)s, que llama %(viewfunc)s" +#~ msgid "plain" +#~ msgstr "plano" -#~ msgid "Placeholder for calling %(viewfunc)s" -#~ msgstr "Substituto para llamar %(viewfunc)s" +#~ msgid "title row" +#~ msgstr "título de la fila" -#~ msgid "not active" -#~ msgstr "inactivo" +#~ msgid "title row and column" +#~ msgstr "título de fila y columna" -#~ msgid "Save tree" -#~ msgstr "Guardar árbol" +#~ msgid "table" +#~ msgstr "tabla" -#~ msgid "%(icon)s (not active)" -#~ msgstr "%(icon)s (inactivo)" +#~ msgid "tables" +#~ msgstr "tablas" -#~ msgid "%(icon)s (until %(from)s)" -#~ msgstr "%(icon)s (hasta %(from)s)" +#~ msgid "data" +#~ msgstr "datos" -#~ msgid "%(icon)s (since %(to)s)" -#~ msgstr "%(icon)s (a partir de %(to)s)" +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Será incluido antes de la lista de palabras clave." -#~ msgid "%(icon)s (%(from)s – %(to)s)" -#~ msgstr "%(icon)s (%(from)s – %(to)s)" +#~ msgid "This will be prepended to the default description." +#~ msgstr "Será incluido antes de la meta descripción por defecto." diff --git a/feincms/locale/fr/LC_MESSAGES/django.mo b/feincms/locale/fr/LC_MESSAGES/django.mo index 911ea5a21..c728d82d9 100644 Binary files a/feincms/locale/fr/LC_MESSAGES/django.mo and b/feincms/locale/fr/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/fr/LC_MESSAGES/django.po b/feincms/locale/fr/LC_MESSAGES/django.po index 8c5897b43..c52cd5149 100644 --- a/feincms/locale/fr/LC_MESSAGES/django.po +++ b/feincms/locale/fr/LC_MESSAGES/django.po @@ -1,224 +1,221 @@ # 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 , YEAR. # +# Translators: msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:19-0500\n" -"PO-Revision-Date: 2009-10-17 22:00\n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" "Last-Translator: Matthias Kestenholz \n" -"Language-Team: LANGUAGE \n" -"Language: \n" +"Language-Team: French (http://www.transifex.com/projects/p/feincms/language/" +"fr/)\n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Translated-Using: django-rosetta 0.4.7\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "modèle" - -#: models.py:553 -msgid "ordering" -msgstr "séquence" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "langue" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filterspecs.py:47 admin/filterspecs.py:86 msgid "All" msgstr "Tous" -#: admin/filterspecs.py:57 module/page/models.py:261 +#: admin/filterspecs.py:58 module/page/models.py:178 msgid "Parent" msgstr "Parent" -#: admin/filterspecs.py:95 -#, fuzzy +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 msgid "Category" -msgstr "catégorie" +msgstr "" -#: admin/item_editor.py:150 +#: admin/item_editor.py:190 #, python-format msgid "Change %s" msgstr "Changement %s" -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:259 -#: module/page/models.py:338 +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 msgid "title" msgstr "titre" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +msgid "You do not have permission to modify this object" +msgstr "" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 #, python-format msgid "%s has been moved to a new position." msgstr "" -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:512 msgid "Did not understand moving instruction." msgstr "" -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:523 msgid "actions" msgstr "actions" -#: content/application/models.py:241 +#: admin/tree_editor.py:552 +#, python-format +msgid "Successfully deleted %(count)d items." +msgstr "" + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +#: content/application/models.py:147 msgid "application content" msgstr "contenu d'application" -#: content/application/models.py:242 +#: content/application/models.py:148 msgid "application contents" msgstr "contenus d'application" -#: content/application/models.py:273 +#: content/application/models.py:179 msgid "application" msgstr "application" -#: content/comments/models.py:28 -#, fuzzy +#: content/comments/models.py:32 msgid "enabled" -msgstr "tableau" +msgstr "" -#: content/comments/models.py:28 +#: content/comments/models.py:33 msgid "New comments may be added" msgstr "" -#: content/comments/models.py:32 content/comments/models.py:33 -#, fuzzy +#: content/comments/models.py:37 content/comments/models.py:38 msgid "comments" -msgstr "Contenu" +msgstr "" -#: content/comments/models.py:48 -#, fuzzy +#: content/comments/models.py:60 msgid "public" -msgstr "publié" +msgstr "" -#: content/comments/models.py:48 +#: content/comments/models.py:61 msgid "not public" msgstr "" -#: content/contactform/models.py:18 +#: content/contactform/models.py:20 msgid "name" msgstr "nom" -#: content/contactform/models.py:19 +#: content/contactform/models.py:21 msgid "email" msgstr "courriel" -#: content/contactform/models.py:20 +#: content/contactform/models.py:22 msgid "subject" msgstr "sujet" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:26 content/raw/models.py:16 msgid "content" msgstr "Contenu" -#: content/contactform/models.py:34 +#: content/contactform/models.py:37 msgid "contact form" msgstr "formulaire de contact" -#: content/contactform/models.py:35 +#: content/contactform/models.py:38 msgid "contact forms" msgstr "formulaires de contact" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 msgid "file" msgstr "fichier" -#: content/file/models.py:21 +#: content/file/models.py:29 msgid "files" msgstr "fichiers" -#: content/image/models.py:24 content/image/models.py:28 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "image" -#: content/image/models.py:29 +#: content/image/models.py:49 +msgid "alternate text" +msgstr "" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "légende" + +#: content/image/models.py:56 msgid "images" msgstr "images" -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 +#: content/image/models.py:82 msgid "position" msgstr "position" -#: content/medialibrary/models.py:38 -msgid "(no caption)" -msgstr "(aucune légende)" +#: content/image/models.py:90 +msgid "format" +msgstr "" -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 msgid "media file" msgstr "fichier de média" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 msgid "media files" msgstr "fichiers de média" -#: content/medialibrary/models.py:131 -msgid "block" -msgstr "bloc" - -#: content/medialibrary/models.py:132 -msgid "left" -msgstr "gauche" - -#: content/medialibrary/models.py:133 -msgid "right" -msgstr "droite" - -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: content/medialibrary/models.py:64 content/section/models.py:59 msgid "type" msgstr "type" -#: content/raw/models.py:18 +#: content/raw/models.py:20 msgid "raw content" msgstr "contenu cru" -#: content/raw/models.py:19 +#: content/raw/models.py:21 msgid "raw contents" msgstr "contenus crus" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "texte" - -#: content/richtext/models.py:26 +#: content/richtext/models.py:24 msgid "HTML Tidy" msgstr "" -#: content/richtext/models.py:27 +#: content/richtext/models.py:25 msgid "Ignore the HTML validation warnings" msgstr "" -#: content/richtext/models.py:51 +#: content/richtext/models.py:55 #, python-format msgid "" "HTML validation produced %(count)d warnings. Please review the updated " "content below before continuing: %(messages)s" msgstr "" -#: content/richtext/models.py:89 +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "texte" + +#: content/richtext/models.py:103 msgid "rich text" msgstr "texte" -#: content/richtext/models.py:90 +#: content/richtext/models.py:104 msgid "rich texts" msgstr "textes" -#: content/rss/models.py:21 +#: content/rss/models.py:25 msgid "" "The rss field is updated several times a day. A change in the title will " "only be visible on the home page after the next feed update." @@ -227,77 +224,55 @@ msgstr "" "titre ne sera visible que sur la page d'accueil après la mise à jour " "prochaine." -#: content/rss/models.py:22 +#: content/rss/models.py:28 msgid "link" msgstr "lien" -#: content/rss/models.py:23 +#: content/rss/models.py:30 msgid "pre-rendered content" msgstr "contenu généré" -#: content/rss/models.py:24 +#: content/rss/models.py:32 msgid "last updated" msgstr "mise à jour" -#: content/rss/models.py:25 +#: content/rss/models.py:33 msgid "max. items" msgstr "max. éléments" -#: content/rss/models.py:29 +#: content/rss/models.py:37 msgid "RSS feed" msgstr "Fil RSS" -#: content/rss/models.py:30 +#: content/rss/models.py:38 msgid "RSS feeds" msgstr "Fils RSS" -#: content/section/models.py:37 +#: content/section/models.py:43 msgid "section" msgstr "section" -#: content/section/models.py:38 +#: content/section/models.py:44 msgid "sections" msgstr "sections" -#: content/table/models.py:62 -msgid "plain" -msgstr "plaine" - -#: content/table/models.py:63 -msgid "title row" -msgstr "titre de la ligne" - -#: content/table/models.py:65 -msgid "title row and column" -msgstr "ligne de titre et de la colonne" - -#: content/table/models.py:71 -msgid "table" -msgstr "tableau" - -#: content/table/models.py:72 -msgid "tables" -msgstr "tableaux" - -#: content/table/models.py:86 -msgid "data" -msgstr "données" - -#: content/template/models.py:62 -#, fuzzy +#: content/template/models.py:53 msgid "template content" -msgstr "contenu d'application" +msgstr "" -#: content/template/models.py:63 -#, fuzzy +#: content/template/models.py:54 msgid "template contents" -msgstr "contenus d'application" +msgstr "" -#: content/video/models.py:23 +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "modèle" + +#: content/video/models.py:34 msgid "video link" msgstr "lien du vidéo" -#: content/video/models.py:24 +#: content/video/models.py:36 msgid "" "This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." "com/watch?v=zmj1rpzDRZ0" @@ -305,431 +280,496 @@ msgstr "" "Cela devrait être un lien vers une vidéo YouTube ou Vimeo, à savoir: http://" "www.youtube.com/watch?v=zmj1rpzDRZ0" -#: content/video/models.py:28 +#: content/video/models.py:41 msgid "video" msgstr "vidéo" -#: content/video/models.py:29 +#: content/video/models.py:42 msgid "videos" msgstr "vidéos" -#: module/blog/models.py:31 +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "" + +#: models.py:550 +msgid "ordering" +msgstr "séquence" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "mots-clé" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "langue" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "traduction de" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "" + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "traductions disponibles" + +#: module/blog/models.py:33 msgid "published" msgstr "publié" -#: module/blog/models.py:33 +#: module/blog/models.py:36 msgid "This is used for the generated navigation too." msgstr "Il est utilisé pour la navigation généré aussi." -#: module/blog/models.py:36 +#: module/blog/models.py:40 msgid "published on" msgstr "publié le" -#: module/blog/models.py:37 +#: module/blog/models.py:42 msgid "Will be set automatically once you tick the `published` checkbox above." msgstr "" "Sera mis automatiquement une fois que vous cochez la case «publié» ci-dessus." -#: module/blog/models.py:42 +#: module/blog/models.py:48 msgid "entry" msgstr "entrée" -#: module/blog/models.py:43 +#: module/blog/models.py:49 msgid "entries" msgstr "entrées" -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "mots-clé" - -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "traduction de" - -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -#, fuzzy -msgid "Leave this empty for entries in the primary language." -msgstr "" -"Laissez cette case vide pour les entrées dans la langue primaire (% s)." - -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" -msgstr "traductions disponibles" - -#: module/extensions/changedate.py:38 +#: module/extensions/changedate.py:41 msgid "creation date" msgstr "date de création" -#: module/extensions/changedate.py:39 +#: module/extensions/changedate.py:43 msgid "modification date" msgstr "date de modification" -#: module/extensions/ct_tracker.py:134 +#: module/extensions/ct_tracker.py:156 msgid "content types" msgstr "types de contenu" -#: module/extensions/featured.py:9 -#, fuzzy +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "date de la publication" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "date de termination de la publication" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "Laissez vide si l'entrée doit rester active pour toujours." + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "visible de - à" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "" + +#: module/extensions/featured.py:15 msgid "featured" -msgstr "crée" +msgstr "" -#: module/extensions/featured.py:14 -#, fuzzy +#: module/extensions/featured.py:21 msgid "Featured" -msgstr "crée" +msgstr "" -#: module/extensions/seo.py:9 +#: module/extensions/seo.py:16 msgid "meta keywords" msgstr "termes meta" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "Ce sera ajouté à la liste de mots clés par défaut." +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" -#: module/extensions/seo.py:11 +#: module/extensions/seo.py:20 msgid "meta description" msgstr "description meta" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." -msgstr "Ce sera ajouté à la description par défaut." +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" -#: module/extensions/seo.py:18 +#: module/extensions/seo.py:32 msgid "Search engine optimization" msgstr "" -#: module/medialibrary/models.py:51 +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "Modifier la traduction" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Créer traduction" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "traductions" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "" +msgstr[1] "" + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "Prévisualiser" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "taille" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "crée" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "type de fichier" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "" + +#: module/medialibrary/models.py:49 msgid "parent" msgstr "parent" -#: module/medialibrary/models.py:53 module/page/models.py:260 +#: module/medialibrary/models.py:51 module/page/models.py:175 msgid "slug" msgstr "télougou" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:55 msgid "category" msgstr "catégorie" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 msgid "categories" msgstr "catégories" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" -msgstr "type de fichier" - -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" -msgstr "crée" - -#: module/medialibrary/models.py:93 +#: module/medialibrary/models.py:101 msgid "copyright" msgstr "copyright" -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 -msgid "file size" -msgstr "taille" - -#: module/medialibrary/models.py:203 -#, fuzzy -msgid "file info" -msgstr "info du fichier" - -#: module/medialibrary/models.py:280 +#: module/medialibrary/models.py:219 msgid "Image" msgstr "image" -#: module/medialibrary/models.py:281 -#, fuzzy +#: module/medialibrary/models.py:221 msgid "Video" -msgstr "vidéo" +msgstr "" -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:224 msgid "Audio" msgstr "" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:226 msgid "PDF document" msgstr "Document PDF" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:227 msgid "Flash" msgstr "Flash" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:228 msgid "Text" msgstr "texte" -#: module/medialibrary/models.py:286 -#, fuzzy +#: module/medialibrary/models.py:229 msgid "Rich Text" -msgstr "texte" +msgstr "" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:230 msgid "Zip archive" msgstr "" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:231 msgid "Microsoft Word" msgstr "" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:233 msgid "Microsoft Excel" msgstr "" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:235 msgid "Microsoft PowerPoint" msgstr "" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:237 msgid "Binary" msgstr "Données binaires" -#: module/medialibrary/models.py:307 -msgid "caption" -msgstr "légende" - -#: module/medialibrary/models.py:308 +#: module/medialibrary/models.py:261 msgid "description" msgstr "description" -#: module/medialibrary/models.py:311 +#: module/medialibrary/models.py:264 msgid "media file translation" msgstr "traductions du fichier de média" -#: module/medialibrary/models.py:312 +#: module/medialibrary/models.py:265 msgid "media file translations" msgstr "traductions des fichiers de média" -#: module/medialibrary/models.py:335 -msgid "Preview" -msgstr "Prévisualiser" +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" +msgstr "" -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." +msgstr "" -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" msgstr "" -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 +msgid "navigation extension" +msgstr "additif de la navigation" + +#: module/page/extensions/navigation.py:119 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." msgstr "" +"Sélectionnez le module fournissant pages pour cette page si vous avez besoin " +"de personnaliser la navigation." -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" +#: module/page/extensions/navigation.py:134 +msgid "Navigation extension" msgstr "" -#: module/medialibrary/models.py:448 -msgid "No input file given" +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" msgstr "" -#: module/page/models.py:256 -msgid "active" -msgstr "actif" +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" -#: module/page/models.py:263 module/page/models.py:711 -msgid "in navigation" +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" msgstr "à la navigation" -#: module/page/models.py:264 -msgid "override URL" -msgstr "adresse URL forcée" +#: module/page/extensions/relatedpages.py:21 +msgid "Select pages that should be listed as related content." +msgstr "" -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." +#: module/page/extensions/relatedpages.py:26 +msgid "Related pages" msgstr "" -"Outrepasser l'URL cible. N'oubliez pas d'inclure des barres obliques au " -"début et à la fin s'il s'agit d'une URL locale. Cela affecte la navigation " -"et les adresses URL des sous-pages." -#: module/page/models.py:266 -msgid "redirect to" -msgstr "rediriger à" +#: module/page/extensions/sites.py:21 +msgid "Site" +msgstr "" -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." -msgstr "URL pour redirections automatiques" +#: module/page/extensions/symlinks.py:22 +msgid "symlinked page" +msgstr "" -#: module/page/models.py:268 -msgid "Cached URL" -msgstr "adresse URL temporairement enregistrée" +#: module/page/extensions/symlinks.py:23 +msgid "All content is inherited from this page if given." +msgstr "Tout le contenu est hérité de cette page si donnée." -#: module/page/models.py:279 -msgid "page" -msgstr "page" +#: module/page/extensions/titles.py:19 +msgid "content title" +msgstr "titre du contenu" -#: module/page/models.py:280 -msgid "pages" -msgstr "pages" +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." +msgstr "" +"La première ligne est le titre principal, les lignes suivantes sont des sous-" +"titres." -#: module/page/models.py:297 module/page/models.py:782 +#: module/page/extensions/titles.py:26 +msgid "page title" +msgstr "titre de la page" + +#: module/page/extensions/titles.py:30 #, fuzzy -msgid "is active" -msgstr "actif" +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." +msgstr "" +"Titre de la page pour fenêtre de navigateur. Même que le titre par défaut." -#: module/page/models.py:631 +#: module/page/extensions/titles.py:60 +msgid "Titles" +msgstr "" + +#: module/page/forms.py:187 msgid "This URL is already taken by an active page." msgstr "Cette URL est déjà prise par une page active." -#: module/page/models.py:649 +#: module/page/forms.py:206 msgid "This URL is already taken by another active page." msgstr "Cette URL est déjà pris par une autre page active." -#: module/page/models.py:674 +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" + +#: module/page/modeladmins.py:42 msgid "Other options" msgstr "Autres options" -#: module/page/models.py:721 +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "à la navigation" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 msgid "Add child page" msgstr "Ajouter page enfant" -#: module/page/models.py:723 +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 msgid "View on site" msgstr "Voir sur le site" -#: module/page/models.py:759 +#: module/page/modeladmins.py:142 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "" + +#: module/page/modeladmins.py:197 msgid "You don't have the necessary permissions to edit this object" msgstr "" -#: module/page/models.py:774 +#: module/page/modeladmins.py:223 msgid "inherited" msgstr "hérité" -#: module/page/models.py:778 +#: module/page/modeladmins.py:229 msgid "extensions" msgstr "extensions" -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "date de la publication" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "date de termination de la publication" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "Laissez vide si l'entrée doit rester active pour toujours." - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "visible de - à" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" -msgstr "" - -#: module/page/extensions/excerpt.py:9 -msgid "excerpt" -msgstr "" - -#: module/page/extensions/excerpt.py:10 -msgid "Add a brief excerpt summarizing the content of this page." -msgstr "" - -#: module/page/extensions/excerpt.py:12 -msgid "Excerpt" +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" msgstr "" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 -msgid "navigation extension" -msgstr "additif de la navigation" - -#: module/page/extensions/navigation.py:99 -msgid "" -"Select the module providing subpages for this page if you need to customize " -"the navigation." -msgstr "" -"Sélectionnez le module fournissant pages pour cette page si vous avez besoin " -"de personnaliser la navigation." +#: module/page/models.py:169 +msgid "active" +msgstr "actif" -#: module/page/extensions/navigation.py:112 +#: module/page/models.py:173 #, fuzzy -msgid "Navigation extension" -msgstr "additif de la navigation" - -#: module/page/extensions/relatedpages.py:13 -msgid "Select pages that should be listed as related content." -msgstr "" - -#: module/page/extensions/relatedpages.py:20 -msgid "Related pages" -msgstr "" - -#: module/page/extensions/sites.py:16 -msgid "Site" -msgstr "" +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "Il est utilisé pour la navigation généré aussi." -#: module/page/extensions/symlinks.py:15 -msgid "symlinked page" +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" msgstr "" -#: module/page/extensions/symlinks.py:16 -msgid "All content is inherited from this page if given." -msgstr "Tout le contenu est hérité de cette page si donnée." +#: module/page/models.py:184 +msgid "override URL" +msgstr "adresse URL forcée" -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" +#: module/page/models.py:186 +msgid "" +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." msgstr "" +"Outrepasser l'URL cible. N'oubliez pas d'inclure des barres obliques au " +"début et à la fin s'il s'agit d'une URL locale. Cela affecte la navigation " +"et les adresses URL des sous-pages." -#: module/page/extensions/titles.py:13 -msgid "content title" -msgstr "titre du contenu" +#: module/page/models.py:190 +msgid "redirect to" +msgstr "rediriger à" -#: module/page/extensions/titles.py:14 -msgid "The first line is the main title, the following lines are subtitles." +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." msgstr "" -"La première ligne est le titre principal, les lignes suivantes sont des sous-" -"titres." -#: module/page/extensions/titles.py:15 -msgid "page title" -msgstr "titre de la page" +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "adresse URL temporairement enregistrée" -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." +#: module/page/models.py:298 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" msgstr "" -"Titre de la page pour fenêtre de navigateur. Même que le titre par défaut." - -#: module/page/extensions/titles.py:43 -#, fuzzy -msgid "Titles" -msgstr "titre" - -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "Modifier la traduction" - -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "Créer traduction" - -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "traductions" -#: templates/admin/filter.html:3 -#, python-format -msgid " By %(filter_title)s " -msgstr "Par %(filter_title)s " +#: module/page/models.py:426 +msgid "page" +msgstr "page" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "Recherche" +#: module/page/models.py:427 +msgid "pages" +msgstr "pages" #: templates/admin/feincms/_messages_js.html:4 msgid "Really delete item?" @@ -767,16 +807,12 @@ msgstr "" #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s is moved to %%(target_region)s." msgstr "" -"Réellement changer de modèle?
    Toutes les modifications sont " -"enregistrées et le contenu de %(source_regions)s est " -"déplacé vers %(target_region)s." #: templates/admin/feincms/_messages_js.html:12 -#, fuzzy msgid "Hide" -msgstr "vidéo" +msgstr "" #: templates/admin/feincms/_messages_js.html:13 msgid "Show" @@ -791,15 +827,18 @@ msgid "Before" msgstr "" #: templates/admin/feincms/_messages_js.html:16 -#, fuzzy msgid "Insert new:" -msgstr "Insérer après" +msgstr "" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "Région vide" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." @@ -807,46 +846,63 @@ msgstr "" "Contenu du site parent est automatiquement hérité. Pour contourner ce " "comportement, ajoutez un peu de contenu." -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "Ajouter un autre" -#: templates/admin/feincms/fe_editor.html:46 +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "" + +#: templates/admin/feincms/fe_editor.html:30 msgid "Save" msgstr "Enregistrer" -#: templates/admin/feincms/fe_tools.html:27 +#: templates/admin/feincms/fe_tools.html:28 msgid "Stop Editing" msgstr "" -#: templates/admin/feincms/fe_tools.html:32 +#: templates/admin/feincms/fe_tools.html:33 msgid "edit" msgstr "traiter" -#: templates/admin/feincms/fe_tools.html:34 +#: templates/admin/feincms/fe_tools.html:35 msgid "new" msgstr "nouveau" -#: templates/admin/feincms/fe_tools.html:35 +#: templates/admin/feincms/fe_tools.html:36 msgid "up" msgstr "vers le haut" -#: templates/admin/feincms/fe_tools.html:36 +#: templates/admin/feincms/fe_tools.html:37 msgid "down" msgstr "vers le bas" -#: templates/admin/feincms/fe_tools.html:37 +#: templates/admin/feincms/fe_tools.html:38 msgid "remove" msgstr "supprimer" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:16 +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "" + +#: templates/admin/feincms/page/page/item_editor.html:23 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 msgid "Home" msgstr "Page d'accueil" -#: templates/admin/feincms/recover_form.html:10 +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "" + +#: templates/admin/feincms/recover_form.html:11 #, python-format msgid "Recover deleted %(verbose_name)s" msgstr "" @@ -855,49 +911,44 @@ msgstr "" msgid "Press the save button below to recover this version of the object." msgstr "" -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:12 msgid "History" msgstr "" -#: templates/admin/feincms/revision_form.html:15 +#: templates/admin/feincms/revision_form.html:13 #, python-format msgid "Revert %(verbose_name)s" msgstr "" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:24 msgid "Press the save button below to revert to this version of the object." msgstr "" -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "Raccourcis" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" msgstr "Réduire l'arbre" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" msgstr "Développer l'arborescence" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "Filtre" -#: templates/admin/feincms/page/page/item_editor.html:9 -#, fuzzy -msgid "Edit on site" -msgstr "Voir sur le site" - -#: templates/admin/feincms/page/page/item_editor.html:20 -msgid "Add" -msgstr "" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr "Par %(filter_title)s " #: templates/admin/medialibrary/add_to_category.html:5 #: templates/admin/medialibrary/add_to_category.html:9 -#, fuzzy msgid "Add media files to category" -msgstr "traductions du fichier de média" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:11 msgid "Select category to apply:" @@ -908,19 +959,22 @@ msgid "The following media files will be added to the selected category:" msgstr "" #: templates/admin/medialibrary/add_to_category.html:22 -#, fuzzy msgid "Add to category" -msgstr "catégorie" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:23 msgid "Cancel" msgstr "" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:11 msgid "Bulk upload a ZIP file:" msgstr "" #: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 msgid "Send" msgstr "" @@ -954,139 +1008,26 @@ msgstr "Envoyer" msgid "Thanks!" msgstr "Merci!" -#, fuzzy -#~ msgid "rich text (ckeditor)" -#~ msgstr "texte" - -#, fuzzy -#~ msgid "rich texts (ckeditor)" -#~ msgstr "textes" - -#~ msgid "You may edit the copied page below." -#~ msgstr "Vous pouvez continuer l'édition ci-dessous." - -#~ msgid "" -#~ "You have replaced %s. You may continue editing the now-active page below." -#~ msgstr "" -#~ "Vous avez remplacé% s. Vous pouvez continuer à modifier le page active ci-" -#~ "dessous." - -#, fuzzy -#~ msgid "Move" -#~ msgstr "supprimer" - -#~ msgid "Replace page %(to_replace)s" -#~ msgstr "Remplacer la page %(to_replace)s " - -#~ msgid "Create hidden copy of this page" -#~ msgstr "Créer une copie cachée de cette page" - -#~ msgid "The %(name)s \"%(obj)s\" was changed successfully." -#~ msgstr "L'objet %(name)s « %(obj)s » a été modifié avec succès." - -#~ msgid "You may edit it again below." -#~ msgstr "Vous pouvez continuer l'édition ci-dessous." - -#~ msgid "You may add another %s below." -#~ msgstr "Vous pouvez ajouter un autre %s ci-dessous." - -#~ msgid "Cut" -#~ msgstr "Couper" - -#~ msgid "Insert as child" -#~ msgstr "Insérer comme enfant" - -#~ msgid "Insert before" -#~ msgstr "Insérer avant" - -#~ msgid "is visible" -#~ msgstr "visible" - -#~ msgid "Video from unknown portal" -#~ msgstr "Vidéo de portail inconnu" - -#~ msgid "Tree saved successfully." -#~ msgstr "Structure arborescente enregistré avec succès." - -#~ msgid "Cannot make a node a child of itself." -#~ msgstr "Impossible de faire un noeud d'un enfant de lui-même." - -#~ msgid "No item has been selected." -#~ msgstr "Aucun élément n'a été sélectionné." - -#~ msgid "Delete" -#~ msgstr "Supprimer" - -#~ msgid "Save and add another" -#~ msgstr "Enregistrer et ajouter un nouveau" - -#~ msgid "Save and continue editing" -#~ msgstr "Enregistrer et continuer les modifications" - -#~ msgid "Please correct the error below." -#~ msgid_plural "Please correct the errors below." -#~ msgstr[0] "SVP corrigez l'erreur ci-après" -#~ msgstr[1] "SVP corrigez les erreurs ci-après" - -#~ msgid "Properties" -#~ msgstr "Propriétés" - -#~ msgid "Move selected item to" -#~ msgstr "Déplacer l'élément choisie" - -#~ msgid "OK" -#~ msgstr "OK" - -#~ msgid "Add %(name)s" -#~ msgstr "Ajout %(name)s" - -#~ msgid "Select an item on the left side if you want to edit it." -#~ msgstr "" -#~ "Sélectionnez un élément sur le côté gauche si vous voulez le modifier." - -#~ msgid "" -#~ "You can change the structure of the tree by drag-dropping elements. " -#~ "Please note that changes will be saved immediately. " -#~ msgstr "" -#~ "Vous pouvez changer la structure de l'arbre par glisser-déposer les " -#~ "éléments. S'il vous plaît noter que les modifications seront enregistrées " -#~ "immédiatement." - -#~ msgid "" -#~ "The context menu on the tree root and tree nodes provide you with " -#~ "additional modes of operation." -#~ msgstr "" -#~ "Le menu contextuel sur la racine d'arbre et de nœuds de l'arborescence " -#~ "vous fournir d'autres modes de fonctionnement." - -#~ msgid "Cannot remove this frame while inside this admin section." -#~ msgstr "" -#~ "Impossible de supprimer ce cadre tandis qu'à l'intérieur du présent " -#~ "article admin." - -#~ msgid "Reload" -#~ msgstr "Recharger" - -#~ msgid "Edit" -#~ msgstr "Modifier" +#~ msgid "plain" +#~ msgstr "plaine" -#~ msgid "Database error" -#~ msgstr "Erreur de base de données" +#~ msgid "title row" +#~ msgstr "titre de la ligne" -#~ msgid "view content" -#~ msgstr "contenu cru" +#~ msgid "title row and column" +#~ msgstr "ligne de titre et de la colonne" -#~ msgid "%(icon)s (not active)" -#~ msgstr "%(icon)s (pas active)" +#~ msgid "table" +#~ msgstr "tableau" -#~ msgid "%(icon)s (until %(from)s)" -#~ msgstr "%(icon)s (jusqu'à %(from)s)" +#~ msgid "tables" +#~ msgstr "tableaux" -#~ msgid "%(icon)s (since %(to)s)" -#~ msgstr "%(icon)s (depuis %(to)s)" +#~ msgid "data" +#~ msgstr "données" -#~ msgid "%(icon)s (%(from)s – %(to)s)" -#~ msgstr "%(icon)s (%(from)s – %(to)s)" +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Ce sera ajouté à la liste de mots clés par défaut." -#~ msgid "Save tree" -#~ msgstr "Enregistrer la structure arborescente" +#~ msgid "This will be prepended to the default description." +#~ msgstr "Ce sera ajouté à la description par défaut." diff --git a/feincms/locale/hr/LC_MESSAGES/django.mo b/feincms/locale/hr/LC_MESSAGES/django.mo index 04de9a169..8f944905c 100644 Binary files a/feincms/locale/hr/LC_MESSAGES/django.mo and b/feincms/locale/hr/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/hr/LC_MESSAGES/django.po b/feincms/locale/hr/LC_MESSAGES/django.po index ee6f1f689..2ef4eef95 100644 --- a/feincms/locale/hr/LC_MESSAGES/django.po +++ b/feincms/locale/hr/LC_MESSAGES/django.po @@ -1,206 +1,204 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# Bojan Mihelac , 2010. # +# Translators: +# Bojan Mihelač , 2010 msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:20-0500\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Bojan Mihelac \n" -"Language-Team: LANGUAGE \n" -"Language: \n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Matthias Kestenholz \n" +"Language-Team: Croatian (http://www.transifex.com/projects/p/feincms/" +"language/hr/)\n" +"Language: hr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n" -"%100==4 ? 2 : 3);\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" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "predložak" - -#: models.py:553 -msgid "ordering" -msgstr "poredak" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "jezik" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filterspecs.py:47 admin/filterspecs.py:86 msgid "All" msgstr "Svi" -#: admin/filterspecs.py:57 module/page/models.py:261 +#: admin/filterspecs.py:58 module/page/models.py:178 msgid "Parent" msgstr "Nadređeni" -#: admin/filterspecs.py:95 +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 msgid "Category" msgstr "Kategorija" -#: admin/item_editor.py:150 +#: admin/item_editor.py:190 #, python-format msgid "Change %s" msgstr "Promjeni %s" -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:259 -#: module/page/models.py:338 +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 msgid "title" msgstr "naziv" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +msgid "You do not have permission to modify this object" +msgstr "" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 #, python-format msgid "%s has been moved to a new position." msgstr "%s je premještena na novu poziciju." -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:512 msgid "Did not understand moving instruction." msgstr "Uputa za premještanje nije uspješno interpretirana. " -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:523 msgid "actions" msgstr "akcije" -#: content/application/models.py:241 +#: admin/tree_editor.py:552 +#, python-format +msgid "Successfully deleted %(count)d items." +msgstr "" + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +#: content/application/models.py:147 msgid "application content" msgstr "sadržaj aplikacije" -#: content/application/models.py:242 +#: content/application/models.py:148 msgid "application contents" msgstr "sadržaji aplikacije" -#: content/application/models.py:273 +#: content/application/models.py:179 msgid "application" msgstr "aplikacija" -#: content/comments/models.py:28 +#: content/comments/models.py:32 msgid "enabled" msgstr "omogućeno" -#: content/comments/models.py:28 +#: content/comments/models.py:33 msgid "New comments may be added" msgstr "Novi komentari mogu biti dodani" -#: content/comments/models.py:32 content/comments/models.py:33 +#: content/comments/models.py:37 content/comments/models.py:38 msgid "comments" msgstr "komentari" -#: content/comments/models.py:48 +#: content/comments/models.py:60 msgid "public" msgstr "objavljeno" -#: content/comments/models.py:48 +#: content/comments/models.py:61 msgid "not public" msgstr "neobjavljeno" -#: content/contactform/models.py:18 +#: content/contactform/models.py:20 msgid "name" msgstr "ime" -#: content/contactform/models.py:19 +#: content/contactform/models.py:21 msgid "email" msgstr "email" -#: content/contactform/models.py:20 +#: content/contactform/models.py:22 msgid "subject" msgstr "naslov" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:26 content/raw/models.py:16 msgid "content" msgstr "sadržaj" -#: content/contactform/models.py:34 +#: content/contactform/models.py:37 msgid "contact form" msgstr "kontaktni obrazac" -#: content/contactform/models.py:35 +#: content/contactform/models.py:38 msgid "contact forms" msgstr "kontaktni obrazci" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 msgid "file" msgstr "datoteka" -#: content/file/models.py:21 +#: content/file/models.py:29 msgid "files" msgstr "datoteke" -#: content/image/models.py:24 content/image/models.py:28 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "slika" -#: content/image/models.py:29 +#: content/image/models.py:49 +msgid "alternate text" +msgstr "" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "naslov" + +#: content/image/models.py:56 msgid "images" msgstr "slike" -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 +#: content/image/models.py:82 msgid "position" msgstr "pozicija" -#: content/medialibrary/models.py:38 -msgid "(no caption)" -msgstr "(bez naslova)" +#: content/image/models.py:90 +msgid "format" +msgstr "" -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 msgid "media file" msgstr "medijska datoteka" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 msgid "media files" msgstr "medijske datoteke" -#: content/medialibrary/models.py:131 -msgid "block" -msgstr "blok" - -#: content/medialibrary/models.py:132 -msgid "left" -msgstr "lijevo" - -#: content/medialibrary/models.py:133 -msgid "right" -msgstr "desno" - -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: content/medialibrary/models.py:64 content/section/models.py:59 msgid "type" msgstr "tip" -#: content/raw/models.py:18 +#: content/raw/models.py:20 msgid "raw content" msgstr "neformatirani sadržaj" -#: content/raw/models.py:19 +#: content/raw/models.py:21 msgid "raw contents" msgstr "neformatirani sadržaji" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "tekst" - -#: content/richtext/models.py:26 +#: content/richtext/models.py:24 msgid "HTML Tidy" msgstr "HTML Tidy" -#: content/richtext/models.py:27 +#: content/richtext/models.py:25 msgid "Ignore the HTML validation warnings" msgstr "Zanemariti upozorenja HTML provjere" -#: content/richtext/models.py:51 +#: content/richtext/models.py:55 #, python-format msgid "" "HTML validation produced %(count)d warnings. Please review the updated " @@ -209,15 +207,19 @@ msgstr "" "HTML provjera je pokazala %(count)d upozorenja. Molimo pregledajte ažurirani " "sadržaj ispod prije nastavka: %(messages)s" -#: content/richtext/models.py:89 +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "tekst" + +#: content/richtext/models.py:103 msgid "rich text" msgstr "formatirani tekst" -#: content/richtext/models.py:90 +#: content/richtext/models.py:104 msgid "rich texts" msgstr "formatirani tekstovi" -#: content/rss/models.py:21 +#: content/rss/models.py:25 msgid "" "The rss field is updated several times a day. A change in the title will " "only be visible on the home page after the next feed update." @@ -225,75 +227,55 @@ msgstr "" "RSS polje se ažurira nekoliko puta dnevno. Promjena u naslovu će biti " "vidljiva na naslovnici nakon sljedećeg ažuriranja kanala." -#: content/rss/models.py:22 +#: content/rss/models.py:28 msgid "link" msgstr "link" -#: content/rss/models.py:23 +#: content/rss/models.py:30 msgid "pre-rendered content" msgstr "pred pripremljen sadržaj" -#: content/rss/models.py:24 +#: content/rss/models.py:32 msgid "last updated" msgstr "zadnji put osvježeno" -#: content/rss/models.py:25 +#: content/rss/models.py:33 msgid "max. items" msgstr "najviše zapisa" -#: content/rss/models.py:29 +#: content/rss/models.py:37 msgid "RSS feed" msgstr "RSS kanal" -#: content/rss/models.py:30 +#: content/rss/models.py:38 msgid "RSS feeds" msgstr "RSS kanali" -#: content/section/models.py:37 +#: content/section/models.py:43 msgid "section" msgstr "sekcija" -#: content/section/models.py:38 +#: content/section/models.py:44 msgid "sections" msgstr "sekcije" -#: content/table/models.py:62 -msgid "plain" -msgstr "jednostavno" - -#: content/table/models.py:63 -msgid "title row" -msgstr "naslovni redak" - -#: content/table/models.py:65 -msgid "title row and column" -msgstr "naslovni redak i kolona" - -#: content/table/models.py:71 -msgid "table" -msgstr "tablica" - -#: content/table/models.py:72 -msgid "tables" -msgstr "tablice" - -#: content/table/models.py:86 -msgid "data" -msgstr "podaci" - -#: content/template/models.py:62 +#: content/template/models.py:53 msgid "template content" msgstr "sadržaj predloška" -#: content/template/models.py:63 +#: content/template/models.py:54 msgid "template contents" msgstr "sadržaji predloška" -#: content/video/models.py:23 +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "predložak" + +#: content/video/models.py:34 msgid "video link" msgstr "link na video" -#: content/video/models.py:24 +#: content/video/models.py:36 msgid "" "This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." "com/watch?v=zmj1rpzDRZ0" @@ -301,422 +283,496 @@ msgstr "" "Polje treba sadržavati link na youtube ili vimeo video, i.e.: http://www." "youtube.com/watch?v=zmj1rpzDRZ0" -#: content/video/models.py:28 +#: content/video/models.py:41 msgid "video" msgstr "video" -#: content/video/models.py:29 +#: content/video/models.py:42 msgid "videos" msgstr "video zapisi" -#: module/blog/models.py:31 +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "" + +#: models.py:550 +msgid "ordering" +msgstr "poredak" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "etikete" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "jezik" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "prijevod od" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "Ostavite ovo prazno za zapise u primarnom jeziku." + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "dostupni prijevodi" + +#: module/blog/models.py:33 msgid "published" msgstr "objavljeno" -#: module/blog/models.py:33 +#: module/blog/models.py:36 msgid "This is used for the generated navigation too." msgstr "Ovo se koristi i za generiranu navigaciju." -#: module/blog/models.py:36 +#: module/blog/models.py:40 msgid "published on" msgstr "objavljeno na" -#: module/blog/models.py:37 +#: module/blog/models.py:42 msgid "Will be set automatically once you tick the `published` checkbox above." msgstr "Biti će automatski postavljeno kada označite `objavljeno` polje iznad." -#: module/blog/models.py:42 +#: module/blog/models.py:48 msgid "entry" msgstr "članak" -#: module/blog/models.py:43 +#: module/blog/models.py:49 msgid "entries" msgstr "članci" -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "etikete" - -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "prijevod od" - -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -msgid "Leave this empty for entries in the primary language." -msgstr "Ostavite ovo prazno za zapise u primarnom jeziku." - -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" -msgstr "dostupni prijevodi" - -#: module/extensions/changedate.py:38 +#: module/extensions/changedate.py:41 msgid "creation date" msgstr "datum kreiranja" -#: module/extensions/changedate.py:39 +#: module/extensions/changedate.py:43 msgid "modification date" msgstr "datum izmjene" -#: module/extensions/ct_tracker.py:134 +#: module/extensions/ct_tracker.py:156 msgid "content types" msgstr "tip sadržaja" -#: module/extensions/featured.py:9 +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "datum objave" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "datum kraja objave" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "" +"Ostavite praznim ukoliko članak treba biti aktivan neograničeno vrijeme." + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "vidljiv od - do" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "Objava vezana na datum" + +#: module/extensions/featured.py:15 msgid "featured" msgstr "istaknuti" -#: module/extensions/featured.py:14 +#: module/extensions/featured.py:21 msgid "Featured" msgstr "Istaknuti" -#: module/extensions/seo.py:9 +#: module/extensions/seo.py:16 msgid "meta keywords" msgstr "meta ključne riječi" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "Ovo će biti dodano predefiniranoj listi ključnih riječi." +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" -#: module/extensions/seo.py:11 +#: module/extensions/seo.py:20 msgid "meta description" msgstr "meta opis" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." -msgstr "Ovo će biti dodano predefiniranom opisu." +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" -#: module/extensions/seo.py:18 +#: module/extensions/seo.py:32 msgid "Search engine optimization" msgstr "Optimizacija za tražilice" -#: module/medialibrary/models.py:51 +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "Uredi prijevod" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Kreiraj prijevod" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "prijevodi" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "Pregled" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "veličina datoteke" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "kreiran" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "tip datoteke" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "informacije o datoteci" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "" + +#: module/medialibrary/models.py:49 msgid "parent" msgstr "nadređeni" -#: module/medialibrary/models.py:53 module/page/models.py:260 +#: module/medialibrary/models.py:51 module/page/models.py:175 msgid "slug" msgstr "slug" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:55 msgid "category" msgstr "kategorija" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 msgid "categories" msgstr "kategorije" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" -msgstr "tip datoteke" - -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" -msgstr "kreiran" - -#: module/medialibrary/models.py:93 +#: module/medialibrary/models.py:101 msgid "copyright" msgstr "autorsko pravo" -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 -msgid "file size" -msgstr "veličina datoteke" - -#: module/medialibrary/models.py:203 -msgid "file info" -msgstr "informacije o datoteci" - -#: module/medialibrary/models.py:280 +#: module/medialibrary/models.py:219 msgid "Image" msgstr "Slika" -#: module/medialibrary/models.py:281 +#: module/medialibrary/models.py:221 msgid "Video" msgstr "Video" -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:224 msgid "Audio" msgstr "Audio" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:226 msgid "PDF document" msgstr "PDF dokument" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:227 msgid "Flash" msgstr "Flash" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:228 msgid "Text" msgstr "Tekst" -#: module/medialibrary/models.py:286 +#: module/medialibrary/models.py:229 msgid "Rich Text" msgstr "Formatirani tekst" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:230 msgid "Zip archive" msgstr "" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:231 msgid "Microsoft Word" msgstr "Microsoft Word" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:233 msgid "Microsoft Excel" msgstr "Microsoft Excel" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:235 msgid "Microsoft PowerPoint" msgstr "Microsoft PowerPoint" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:237 msgid "Binary" msgstr "Binarni" -#: module/medialibrary/models.py:307 -msgid "caption" -msgstr "naslov" - -#: module/medialibrary/models.py:308 +#: module/medialibrary/models.py:261 msgid "description" msgstr "opis" -#: module/medialibrary/models.py:311 +#: module/medialibrary/models.py:264 msgid "media file translation" msgstr "prijevod medijske datoteke" -#: module/medialibrary/models.py:312 +#: module/medialibrary/models.py:265 msgid "media file translations" msgstr "prijevodi medijske datoteke" -#: module/medialibrary/models.py:335 -msgid "Preview" -msgstr "Pregled" +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" +msgstr "sažetak" -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." +msgstr "Dodajte kratak sažetak sadržaja ove stranice." -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" -msgstr "" +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" +msgstr "Sažetak" -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" -msgstr "" +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 +msgid "navigation extension" +msgstr "navigacijska ekstenzija" -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" +#: module/page/extensions/navigation.py:119 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." msgstr "" +"Odaberite modul koji će dostaviti podstranice za ovu stranicu ukoliko je " +"potrebno prilagoditi navigaciju." -#: module/medialibrary/models.py:448 -msgid "No input file given" +#: module/page/extensions/navigation.py:134 +msgid "Navigation extension" +msgstr "navigacijska ekstenzija" + +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" msgstr "" -#: module/page/models.py:256 -msgid "active" -msgstr "aktivan" +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" -#: module/page/models.py:263 module/page/models.py:711 -msgid "in navigation" +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" msgstr "u navigaciji" -#: module/page/models.py:264 -msgid "override URL" -msgstr "nadjačati URL" +#: module/page/extensions/relatedpages.py:21 +msgid "Select pages that should be listed as related content." +msgstr "Odaberite stranice koje trebaju biti navedene kao povezani sadržaj." -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." +#: module/page/extensions/relatedpages.py:26 +msgid "Related pages" +msgstr "Povezane stranice" + +#: module/page/extensions/sites.py:21 +msgid "Site" msgstr "" -"Nadjačati ciljnu adresu (URL). Budite sigurni da uključite kose crte na " -"početku i kraju ukoliko se odnosi na lokalnu adresu. Ovo se odnosi i na " -"navigaciju i na adrese podstranica" -#: module/page/models.py:266 -msgid "redirect to" -msgstr "preusmjeriti na" +#: module/page/extensions/symlinks.py:22 +msgid "symlinked page" +msgstr "simbolički povezana stranica" -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." -msgstr "Ciljna adresa (URL) za automatsko preusmjeravanje" +#: module/page/extensions/symlinks.py:23 +msgid "All content is inherited from this page if given." +msgstr "Sav sadržaj je nasljeđen od ove stranice ukoliko je postavljena." -#: module/page/models.py:268 -msgid "Cached URL" -msgstr "Keširana adresa (URL)" +#: module/page/extensions/titles.py:19 +msgid "content title" +msgstr "naslov sadržaja" -#: module/page/models.py:279 -msgid "page" -msgstr "stranica" +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." +msgstr "Prva linija je glavni naslov, slijedeće linije su podnaslovi." -#: module/page/models.py:280 -msgid "pages" -msgstr "stranice" +#: module/page/extensions/titles.py:26 +msgid "page title" +msgstr "naslov stranice" -#: module/page/models.py:297 module/page/models.py:782 -msgid "is active" -msgstr "je aktivna" +#: module/page/extensions/titles.py:30 +#, fuzzy +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." +msgstr "" +"Naslov stranice u prozoru browsera. Podrazumijevana vrijednost je jednako " +"kao naslov." -#: module/page/models.py:631 +#: module/page/extensions/titles.py:60 +msgid "Titles" +msgstr "Nazivi" + +#: module/page/forms.py:187 msgid "This URL is already taken by an active page." msgstr "Ova adresa (URL) je već zauzeta aktivnom stranicom." -#: module/page/models.py:649 +#: module/page/forms.py:206 msgid "This URL is already taken by another active page." msgstr "Ova adresa (URL) je već zauzeta drugom aktivnom stranicom." -#: module/page/models.py:674 +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" + +#: module/page/modeladmins.py:42 msgid "Other options" msgstr "Druge mogućnosti" -#: module/page/models.py:721 +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "u navigaciji" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 msgid "Add child page" msgstr "Dodaj podređenu stranicu" -#: module/page/models.py:723 +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 msgid "View on site" msgstr "Pogledaj na internetnim stranicama" -#: module/page/models.py:759 +#: module/page/modeladmins.py:142 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "" + +#: module/page/modeladmins.py:197 msgid "You don't have the necessary permissions to edit this object" msgstr "" -#: module/page/models.py:774 +#: module/page/modeladmins.py:223 msgid "inherited" msgstr "naslijeđeno" -#: module/page/models.py:778 +#: module/page/modeladmins.py:229 msgid "extensions" msgstr "ekstenzije" -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "datum objave" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "datum kraja objave" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "" -"Ostavite praznim ukoliko članak treba biti aktivan neograničeno vrijeme." - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "vidljiv od - do" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" -msgstr "Objava vezana na datum" +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" +msgstr "je aktivna" -#: module/page/extensions/excerpt.py:9 -msgid "excerpt" -msgstr "sažetak" +#: module/page/models.py:169 +msgid "active" +msgstr "aktivan" -#: module/page/extensions/excerpt.py:10 -msgid "Add a brief excerpt summarizing the content of this page." -msgstr "Dodajte kratak sažetak sadržaja ove stranice." +#: module/page/models.py:173 +#, fuzzy +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "Ovo se koristi i za generiranu navigaciju." -#: module/page/extensions/excerpt.py:12 -msgid "Excerpt" -msgstr "Sažetak" +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" +msgstr "" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 -msgid "navigation extension" -msgstr "navigacijska ekstenzija" +#: module/page/models.py:184 +msgid "override URL" +msgstr "nadjačati URL" -#: module/page/extensions/navigation.py:99 +#: module/page/models.py:186 msgid "" -"Select the module providing subpages for this page if you need to customize " -"the navigation." +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." msgstr "" -"Odaberite modul koji će dostaviti podstranice za ovu stranicu ukoliko je " -"potrebno prilagoditi navigaciju." - -#: module/page/extensions/navigation.py:112 -msgid "Navigation extension" -msgstr "navigacijska ekstenzija" - -#: module/page/extensions/relatedpages.py:13 -msgid "Select pages that should be listed as related content." -msgstr "Odaberite stranice koje trebaju biti navedene kao povezani sadržaj." +"Nadjačati ciljnu adresu (URL). Budite sigurni da uključite kose crte na " +"početku i kraju ukoliko se odnosi na lokalnu adresu. Ovo se odnosi i na " +"navigaciju i na adrese podstranica" -#: module/page/extensions/relatedpages.py:20 -msgid "Related pages" -msgstr "Povezane stranice" +#: module/page/models.py:190 +msgid "redirect to" +msgstr "preusmjeriti na" -#: module/page/extensions/sites.py:16 -msgid "Site" +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." msgstr "" -#: module/page/extensions/symlinks.py:15 -msgid "symlinked page" -msgstr "simbolički povezana stranica" - -#: module/page/extensions/symlinks.py:16 -msgid "All content is inherited from this page if given." -msgstr "Sav sadržaj je nasljeđen od ove stranice ukoliko je postavljena." - -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" -msgstr "simbolički povezana stranica" - -#: module/page/extensions/titles.py:13 -msgid "content title" -msgstr "naslov sadržaja" - -#: module/page/extensions/titles.py:14 -msgid "The first line is the main title, the following lines are subtitles." -msgstr "Prva linija je glavni naslov, slijedeće linije su podnaslovi." - -#: module/page/extensions/titles.py:15 -msgid "page title" -msgstr "naslov stranice" +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "Keširana adresa (URL)" -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." +#: module/page/models.py:298 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" msgstr "" -"Naslov stranice u prozoru browsera. Podrazumijevana vrijednost je jednako " -"kao naslov." - -#: module/page/extensions/titles.py:43 -msgid "Titles" -msgstr "Nazivi" -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "Uredi prijevod" - -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "Kreiraj prijevod" - -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "prijevodi" - -#: templates/admin/filter.html:3 -#, python-format -msgid " By %(filter_title)s " -msgstr " Po %(filter_title)s " +#: module/page/models.py:426 +msgid "page" +msgstr "stranica" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "Pretraživanje" +#: module/page/models.py:427 +msgid "pages" +msgstr "stranice" #: templates/admin/feincms/_messages_js.html:4 msgid "Really delete item?" @@ -751,11 +807,8 @@ msgstr "Zaista zamijeniti predložak?
    Sve izmjene će biti spremljene." #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s is moved to %%(target_region)s." msgstr "" -"Zaista zamijeniti predložak?
    Sve izmjene su spremljene. i sadržaj iz " -"%(source_regions)s će biti premješten u " -"%(target_region)s." #: templates/admin/feincms/_messages_js.html:12 msgid "Hide" @@ -777,11 +830,15 @@ msgstr "Prije" msgid "Insert new:" msgstr "Umetni ispred:" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "Regija je prazna" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." @@ -789,46 +846,63 @@ msgstr "" "Sadržaj je automatski naslijeđen od nadređene stranice. Ukoliko želite to " "promjeniti, dodajte neke sadržaje." -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "Dodaj novi unos" -#: templates/admin/feincms/fe_editor.html:46 +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "" + +#: templates/admin/feincms/fe_editor.html:30 msgid "Save" msgstr "Spremi" -#: templates/admin/feincms/fe_tools.html:27 +#: templates/admin/feincms/fe_tools.html:28 msgid "Stop Editing" msgstr "Prekini uređivanje" -#: templates/admin/feincms/fe_tools.html:32 +#: templates/admin/feincms/fe_tools.html:33 msgid "edit" msgstr "uredi" -#: templates/admin/feincms/fe_tools.html:34 +#: templates/admin/feincms/fe_tools.html:35 msgid "new" msgstr "novi" -#: templates/admin/feincms/fe_tools.html:35 +#: templates/admin/feincms/fe_tools.html:36 msgid "up" msgstr "gore" -#: templates/admin/feincms/fe_tools.html:36 +#: templates/admin/feincms/fe_tools.html:37 msgid "down" msgstr "dolje" -#: templates/admin/feincms/fe_tools.html:37 +#: templates/admin/feincms/fe_tools.html:38 msgid "remove" msgstr "izbriši" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:16 +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "Uredi na stranicama" + +#: templates/admin/feincms/page/page/item_editor.html:23 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 msgid "Home" msgstr "Home" -#: templates/admin/feincms/recover_form.html:10 +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "" + +#: templates/admin/feincms/recover_form.html:11 #, python-format msgid "Recover deleted %(verbose_name)s" msgstr "" @@ -837,48 +911,44 @@ msgstr "" msgid "Press the save button below to recover this version of the object." msgstr "" -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:12 msgid "History" msgstr "" -#: templates/admin/feincms/revision_form.html:15 +#: templates/admin/feincms/revision_form.html:13 #, python-format msgid "Revert %(verbose_name)s" msgstr "" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:24 msgid "Press the save button below to revert to this version of the object." msgstr "" -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "Kratice" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" msgstr "Sažmi drvo" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" msgstr "Proširi drvo" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "Filter" -#: templates/admin/feincms/page/page/item_editor.html:9 -msgid "Edit on site" -msgstr "Uredi na stranicama" - -#: templates/admin/feincms/page/page/item_editor.html:20 -msgid "Add" -msgstr "" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr " Po %(filter_title)s " #: templates/admin/medialibrary/add_to_category.html:5 #: templates/admin/medialibrary/add_to_category.html:9 -#, fuzzy msgid "Add media files to category" -msgstr "prijevod medijske datoteke" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:11 msgid "Select category to apply:" @@ -889,19 +959,22 @@ msgid "The following media files will be added to the selected category:" msgstr "" #: templates/admin/medialibrary/add_to_category.html:22 -#, fuzzy msgid "Add to category" -msgstr "kategorija" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:23 msgid "Cancel" msgstr "" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:11 msgid "Bulk upload a ZIP file:" msgstr "Masovni prijenos ZIP datotekom:" #: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 msgid "Send" msgstr "Pošalji" @@ -939,115 +1012,26 @@ msgstr "Potvrdi" msgid "Thanks!" msgstr "Hvala!" -#~ msgid "rich text (ckeditor)" -#~ msgstr "formatirani tekst (ckeditor)" - -#~ msgid "rich texts (ckeditor)" -#~ msgstr "formatirani tekstovi (ckeditor)" - -#~ msgid "You may edit the copied page below." -#~ msgstr "Možete urediti kopiranu stranicu ispod." - -#~ msgid "" -#~ "You have replaced %s. You may continue editing the now-active page below." -#~ msgstr "" -#~ "Zamijenili ste %s. Možete nastaviti uređivati neaktivnu stranicu ispod." - -#~ msgid "Move to" -#~ msgstr "Premjesti u" - -#~ msgid "Move" -#~ msgstr "Premjesti" - -#~ msgid "Replace page %(to_replace)s" -#~ msgstr "Zamjeni stranicu %(to_replace)s" - -#~ msgid "Create hidden copy of this page" -#~ msgstr "Kreiraj skrivenu kopiju ove stranice" - -#~ msgid "Click save to replace the current content with this version" -#~ msgstr "Kliknite spremi da zamjenite trenutni sadržaj sa ovom verzijom" - -#~ msgid "The %(name)s \"%(obj)s\" was changed successfully." -#~ msgstr "%(name)s \"%(obj)s\" je uspješno promijenjen." - -#~ msgid "You may edit it again below." -#~ msgstr "Možete urediti ponovno." - -#~ msgid "You may add another %s below." -#~ msgstr "Možete dodati još jedan %s." - -#~ msgid "Cut" -#~ msgstr "Izreži" - -#~ msgid "Insert as child" -#~ msgstr "Umetni kao podređen" - -#~ msgid "Video from unknown portal" -#~ msgstr "Video sa nepoznatog portala" - -#~ msgid "Tree saved successfully." -#~ msgstr "Drvo je uspješno spremljeno." - -#~ msgid "Cannot make a node a child of itself." -#~ msgstr "Nije moguće napraviti objekt koji je podređen samom sebi." - -#~ msgid "No item has been selected." -#~ msgstr "Nijedan objekt nije odabran." - -#~ msgid "Add Child Page" -#~ msgstr "Dodaj podređenu stranicu" - -#~ msgid "Delete" -#~ msgstr "Izbriši" - -#~ msgid "Save and add another" -#~ msgstr "Spremi i dodaj novi unos" - -#~ msgid "Save and continue editing" -#~ msgstr "Spremi i nastavi uređivati" - -#~ msgid "Please correct the error below." -#~ msgid_plural "Please correct the errors below." -#~ msgstr[0] "Molimo ispravite navedenu grešku." -#~ msgstr[1] "Molimo ispravite navedene greške." -#~ msgstr[2] "Molimo ispravite navedene greške." - -#~ msgid "Properties" -#~ msgstr "Osobine" - -#~ msgid "Move selected item to" -#~ msgstr "Premjesti odabrani objekt na" - -#~ msgid "OK" -#~ msgstr "U redu" +#~ msgid "plain" +#~ msgstr "jednostavno" -#~ msgid "Add %(name)s" -#~ msgstr "Dodaj %(name)s" +#~ msgid "title row" +#~ msgstr "naslovni redak" -#~ msgid "Select an item on the left side if you want to edit it." -#~ msgstr "Odaberite objekt sa lijeve strane ukoliko ga želite urediti." +#~ msgid "title row and column" +#~ msgstr "naslovni redak i kolona" -#~ msgid "" -#~ "You can change the structure of the tree by drag-dropping elements. " -#~ "Please note that changes will be saved immediately. " -#~ msgstr "" -#~ "Možete izmjeniti strukturu drveta koristeći drag and drop na elementima. " -#~ "Napomena: promijene će biti odmah zapamćene. " +#~ msgid "table" +#~ msgstr "tablica" -#~ msgid "" -#~ "The context menu on the tree root and tree nodes provide you with " -#~ "additional modes of operation." -#~ msgstr "" -#~ "Kontekstni menu na korijenu i čvorovima drveta omogućavaju dodatne načine " -#~ "rada." +#~ msgid "tables" +#~ msgstr "tablice" -#~ msgid "Cannot remove this frame while inside this admin section." -#~ msgstr "" -#~ "Nije moguće obrisati ovaj frame dok ste u administracijskoj sekciji." +#~ msgid "data" +#~ msgstr "podaci" -#~ msgid "Reload" -#~ msgstr "Ponovno učitaj" +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Ovo će biti dodano predefiniranoj listi ključnih riječi." -#~ msgid "Edit" -#~ msgstr "Uredi" +#~ msgid "This will be prepended to the default description." +#~ msgstr "Ovo će biti dodano predefiniranom opisu." diff --git a/feincms/locale/it/LC_MESSAGES/django.mo b/feincms/locale/it/LC_MESSAGES/django.mo index a9d7c656a..12d01f0e0 100644 Binary files a/feincms/locale/it/LC_MESSAGES/django.mo and b/feincms/locale/it/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/it/LC_MESSAGES/django.po b/feincms/locale/it/LC_MESSAGES/django.po index 6679784ce..f76e0ec25 100644 --- a/feincms/locale/it/LC_MESSAGES/django.po +++ b/feincms/locale/it/LC_MESSAGES/django.po @@ -1,224 +1,221 @@ # 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 , YEAR. # +# Translators: msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:19-0500\n" -"PO-Revision-Date: 2009-10-17 22:15\n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" "Last-Translator: Matthias Kestenholz \n" -"Language-Team: LANGUAGE \n" -"Language: \n" +"Language-Team: Italian (http://www.transifex.com/projects/p/feincms/language/" +"it/)\n" +"Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Translated-Using: django-rosetta 0.4.7\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "template" - -#: models.py:553 -msgid "ordering" -msgstr "ordinazione" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "lingua" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filterspecs.py:47 admin/filterspecs.py:86 msgid "All" msgstr "Tutto" -#: admin/filterspecs.py:57 module/page/models.py:261 +#: admin/filterspecs.py:58 module/page/models.py:178 msgid "Parent" msgstr "Parent" -#: admin/filterspecs.py:95 -#, fuzzy +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 msgid "Category" -msgstr "categoria" +msgstr "" -#: admin/item_editor.py:150 +#: admin/item_editor.py:190 #, python-format msgid "Change %s" msgstr "Variazione% s" -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:259 -#: module/page/models.py:338 +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 msgid "title" msgstr "Titolo" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +msgid "You do not have permission to modify this object" +msgstr "" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 #, python-format msgid "%s has been moved to a new position." msgstr "" -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:512 msgid "Did not understand moving instruction." msgstr "" -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:523 msgid "actions" msgstr "azioni" -#: content/application/models.py:241 +#: admin/tree_editor.py:552 +#, python-format +msgid "Successfully deleted %(count)d items." +msgstr "" + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +#: content/application/models.py:147 msgid "application content" msgstr "" -#: content/application/models.py:242 +#: content/application/models.py:148 msgid "application contents" msgstr "" -#: content/application/models.py:273 +#: content/application/models.py:179 msgid "application" msgstr "applicazione" -#: content/comments/models.py:28 -#, fuzzy +#: content/comments/models.py:32 msgid "enabled" -msgstr "tavolo" +msgstr "" -#: content/comments/models.py:28 +#: content/comments/models.py:33 msgid "New comments may be added" msgstr "" -#: content/comments/models.py:32 content/comments/models.py:33 -#, fuzzy +#: content/comments/models.py:37 content/comments/models.py:38 msgid "comments" -msgstr "contenuto" +msgstr "" -#: content/comments/models.py:48 -#, fuzzy +#: content/comments/models.py:60 msgid "public" -msgstr "pubblicato" +msgstr "" -#: content/comments/models.py:48 +#: content/comments/models.py:61 msgid "not public" msgstr "" -#: content/contactform/models.py:18 +#: content/contactform/models.py:20 msgid "name" msgstr "nome" -#: content/contactform/models.py:19 +#: content/contactform/models.py:21 msgid "email" msgstr "e-mail" -#: content/contactform/models.py:20 +#: content/contactform/models.py:22 msgid "subject" msgstr "soggetto" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:26 content/raw/models.py:16 msgid "content" msgstr "contenuto" -#: content/contactform/models.py:34 +#: content/contactform/models.py:37 msgid "contact form" msgstr "forme di contatto" -#: content/contactform/models.py:35 +#: content/contactform/models.py:38 msgid "contact forms" msgstr "forme di contatto" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 msgid "file" msgstr "file" -#: content/file/models.py:21 +#: content/file/models.py:29 msgid "files" msgstr "files" -#: content/image/models.py:24 content/image/models.py:28 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "image" -#: content/image/models.py:29 +#: content/image/models.py:49 +msgid "alternate text" +msgstr "" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "caption" + +#: content/image/models.py:56 msgid "images" msgstr "immagini" -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 +#: content/image/models.py:82 msgid "position" msgstr "Posizione" -#: content/medialibrary/models.py:38 -msgid "(no caption)" -msgstr "(no caption)" +#: content/image/models.py:90 +msgid "format" +msgstr "" -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 msgid "media file" msgstr "file multimediale" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 msgid "media files" msgstr "file multimediali" -#: content/medialibrary/models.py:131 -msgid "block" -msgstr "blocco" - -#: content/medialibrary/models.py:132 -msgid "left" -msgstr "sinistra" - -#: content/medialibrary/models.py:133 -msgid "right" -msgstr "destra" - -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: content/medialibrary/models.py:64 content/section/models.py:59 msgid "type" msgstr "tipo" -#: content/raw/models.py:18 +#: content/raw/models.py:20 msgid "raw content" msgstr "raw contenuti" -#: content/raw/models.py:19 +#: content/raw/models.py:21 msgid "raw contents" msgstr "raw contenuti" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "testo" - -#: content/richtext/models.py:26 +#: content/richtext/models.py:24 msgid "HTML Tidy" msgstr "" -#: content/richtext/models.py:27 +#: content/richtext/models.py:25 msgid "Ignore the HTML validation warnings" msgstr "" -#: content/richtext/models.py:51 +#: content/richtext/models.py:55 #, python-format msgid "" "HTML validation produced %(count)d warnings. Please review the updated " "content below before continuing: %(messages)s" msgstr "" -#: content/richtext/models.py:89 +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "testo" + +#: content/richtext/models.py:103 msgid "rich text" msgstr "rich text" -#: content/richtext/models.py:90 +#: content/richtext/models.py:104 msgid "rich texts" msgstr "" -#: content/rss/models.py:21 +#: content/rss/models.py:25 msgid "" "The rss field is updated several times a day. A change in the title will " "only be visible on the home page after the next feed update." @@ -226,77 +223,55 @@ msgstr "" "Il campo rss viene aggiornato più volte al giorno. Una modifica del titolo " "sarà visibile solo in home page dopo il prossimo aggiornamento dei mangimi." -#: content/rss/models.py:22 +#: content/rss/models.py:28 msgid "link" msgstr "collegamento" -#: content/rss/models.py:23 +#: content/rss/models.py:30 msgid "pre-rendered content" msgstr "pre-rendering di contenuti" -#: content/rss/models.py:24 +#: content/rss/models.py:32 msgid "last updated" msgstr "Ultimo aggiornamento" -#: content/rss/models.py:25 +#: content/rss/models.py:33 msgid "max. items" msgstr "max. articoli" -#: content/rss/models.py:29 +#: content/rss/models.py:37 msgid "RSS feed" msgstr "RSS feed" -#: content/rss/models.py:30 +#: content/rss/models.py:38 msgid "RSS feeds" msgstr "Feed RSS" -#: content/section/models.py:37 +#: content/section/models.py:43 msgid "section" msgstr "sezione" -#: content/section/models.py:38 +#: content/section/models.py:44 msgid "sections" msgstr "sezioni" -#: content/table/models.py:62 -msgid "plain" -msgstr "plain" - -#: content/table/models.py:63 -msgid "title row" -msgstr "titolo di fila" - -#: content/table/models.py:65 -msgid "title row and column" -msgstr "riga del titolo e colonna" - -#: content/table/models.py:71 -msgid "table" -msgstr "tavolo" - -#: content/table/models.py:72 -msgid "tables" -msgstr "tavoli" - -#: content/table/models.py:86 -msgid "data" -msgstr "dati" - -#: content/template/models.py:62 -#, fuzzy +#: content/template/models.py:53 msgid "template content" -msgstr "template" +msgstr "" -#: content/template/models.py:63 -#, fuzzy +#: content/template/models.py:54 msgid "template contents" -msgstr "raw contenuti" +msgstr "" -#: content/video/models.py:23 +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "template" + +#: content/video/models.py:34 msgid "video link" msgstr "video link" -#: content/video/models.py:24 +#: content/video/models.py:36 msgid "" "This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." "com/watch?v=zmj1rpzDRZ0" @@ -304,429 +279,497 @@ msgstr "" "Questo dovrebbe essere un link ad un video di YouTube o di Vimeo, vale a " "dire: http://www.youtube.com/watch?v=zmj1rpzDRZ0" -#: content/video/models.py:28 +#: content/video/models.py:41 msgid "video" msgstr "video" -#: content/video/models.py:29 +#: content/video/models.py:42 msgid "videos" msgstr "video" -#: module/blog/models.py:31 +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "" + +#: models.py:550 +msgid "ordering" +msgstr "ordinazione" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "tags" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "lingua" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "traduzione di" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "" + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "traduzioni disponibili" + +#: module/blog/models.py:33 msgid "published" msgstr "pubblicato" -#: module/blog/models.py:33 +#: module/blog/models.py:36 msgid "This is used for the generated navigation too." msgstr "Questo viene utilizzato per la navigazione generato troppo." -#: module/blog/models.py:36 +#: module/blog/models.py:40 msgid "published on" msgstr "pubblicato il" -#: module/blog/models.py:37 +#: module/blog/models.py:42 msgid "Will be set automatically once you tick the `published` checkbox above." msgstr "" "Verrà impostato automaticamente una volta che barrare la casella di " "controllo `` pubblicato in precedenza." -#: module/blog/models.py:42 +#: module/blog/models.py:48 msgid "entry" msgstr "" -#: module/blog/models.py:43 +#: module/blog/models.py:49 msgid "entries" msgstr "" -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "tags" - -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "traduzione di" - -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -#, fuzzy -msgid "Leave this empty for entries in the primary language." -msgstr "Lasciare vuoto questo campo per le voci in lingua primaria (% s)." - -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" -msgstr "traduzioni disponibili" - -#: module/extensions/changedate.py:38 +#: module/extensions/changedate.py:41 msgid "creation date" msgstr "data di creazione" -#: module/extensions/changedate.py:39 +#: module/extensions/changedate.py:43 msgid "modification date" msgstr "" -#: module/extensions/ct_tracker.py:134 +#: module/extensions/ct_tracker.py:156 msgid "content types" msgstr "tipi di contenuto" -#: module/extensions/featured.py:9 -#, fuzzy +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "data di pubblicazione" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "data fine pubblicazione" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "Lasciare vuoto se la voce dovrebbe rimanere attivo per sempre." + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "visibile da - a" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "" + +#: module/extensions/featured.py:15 msgid "featured" -msgstr "creato" +msgstr "" -#: module/extensions/featured.py:14 -#, fuzzy +#: module/extensions/featured.py:21 msgid "Featured" -msgstr "creato" +msgstr "" -#: module/extensions/seo.py:9 +#: module/extensions/seo.py:16 msgid "meta keywords" msgstr "meta keywords" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "Questo sarà anteposto al elenco di parole chiave predefinite." +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" -#: module/extensions/seo.py:11 +#: module/extensions/seo.py:20 msgid "meta description" msgstr "meta description" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." -msgstr "Questo sarà anteposto alla descrizione di default." +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" -#: module/extensions/seo.py:18 +#: module/extensions/seo.py:32 msgid "Search engine optimization" msgstr "" -#: module/medialibrary/models.py:51 +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "Modificare la traduzione" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Crea traduzione" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "traduzioni" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "" +msgstr[1] "" + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "Anteprima" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "dimensione del file" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "creato" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "tipo di file" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "file info" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "" + +#: module/medialibrary/models.py:49 msgid "parent" msgstr "genitore" -#: module/medialibrary/models.py:53 module/page/models.py:260 +#: module/medialibrary/models.py:51 module/page/models.py:175 msgid "slug" msgstr "slug" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:55 msgid "category" msgstr "categoria" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 msgid "categories" msgstr "categorie" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" -msgstr "tipo di file" - -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" -msgstr "creato" - -#: module/medialibrary/models.py:93 +#: module/medialibrary/models.py:101 msgid "copyright" msgstr "copyright" -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 -msgid "file size" -msgstr "dimensione del file" - -#: module/medialibrary/models.py:203 -msgid "file info" -msgstr "file info" - -#: module/medialibrary/models.py:280 +#: module/medialibrary/models.py:219 msgid "Image" msgstr "Image" -#: module/medialibrary/models.py:281 -#, fuzzy +#: module/medialibrary/models.py:221 msgid "Video" -msgstr "video" +msgstr "" -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:224 msgid "Audio" msgstr "" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:226 msgid "PDF document" msgstr "PDF document" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:227 msgid "Flash" msgstr "Flash" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:228 msgid "Text" msgstr "Testo" -#: module/medialibrary/models.py:286 -#, fuzzy +#: module/medialibrary/models.py:229 msgid "Rich Text" -msgstr "rich text" +msgstr "" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:230 msgid "Zip archive" msgstr "" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:231 msgid "Microsoft Word" msgstr "" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:233 msgid "Microsoft Excel" msgstr "" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:235 msgid "Microsoft PowerPoint" msgstr "" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:237 msgid "Binary" msgstr "Binary" -#: module/medialibrary/models.py:307 -msgid "caption" -msgstr "caption" - -#: module/medialibrary/models.py:308 +#: module/medialibrary/models.py:261 msgid "description" msgstr "descrizione" -#: module/medialibrary/models.py:311 +#: module/medialibrary/models.py:264 msgid "media file translation" msgstr "traduzione di file multimediale" -#: module/medialibrary/models.py:312 +#: module/medialibrary/models.py:265 msgid "media file translations" msgstr "traduzioni di file multimediale" -#: module/medialibrary/models.py:335 -msgid "Preview" -msgstr "Anteprima" +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" +msgstr "" -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." +msgstr "" -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" msgstr "" -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 +msgid "navigation extension" msgstr "" -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" +#: module/page/extensions/navigation.py:119 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." msgstr "" +"Selezionare il modulo che fornisce sottopagine per questa pagina, se avete " +"bisogno di personalizzare la navigazione." -#: module/medialibrary/models.py:448 -msgid "No input file given" +#: module/page/extensions/navigation.py:134 +msgid "Navigation extension" msgstr "" -#: module/page/models.py:256 -msgid "active" -msgstr "attiva" +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" +msgstr "" -#: module/page/models.py:263 module/page/models.py:711 -msgid "in navigation" +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" + +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" msgstr "in navigazione" -#: module/page/models.py:264 -msgid "override URL" -msgstr "override URL" +#: module/page/extensions/relatedpages.py:21 +msgid "Select pages that should be listed as related content." +msgstr "" -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." +#: module/page/extensions/relatedpages.py:26 +msgid "Related pages" msgstr "" -"Ignorare l'URL di destinazione. Assicurati di includere le barre all'inizio " -"e alla fine se si tratta di un URL locale. Questo riguarda sia la " -"navigazione e gli URL sottopagine '." -#: module/page/models.py:266 -msgid "redirect to" -msgstr "reindirizzamento" +#: module/page/extensions/sites.py:21 +msgid "Site" +msgstr "" -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." -msgstr "URL di destinazione per i redirect automatico." +#: module/page/extensions/symlinks.py:22 +msgid "symlinked page" +msgstr "" -#: module/page/models.py:268 -msgid "Cached URL" -msgstr "Cache URL" +#: module/page/extensions/symlinks.py:23 +msgid "All content is inherited from this page if given." +msgstr "Tutti i contenuti sono ereditate da questa pagina, se somministrata." -#: module/page/models.py:279 -msgid "page" -msgstr "pagina" +#: module/page/extensions/titles.py:19 +msgid "content title" +msgstr "il titolo del contenuto" -#: module/page/models.py:280 -msgid "pages" -msgstr "pagine" +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." +msgstr "" +"La prima linea è il titolo principale, le seguenti righe sono i sottotitoli." + +#: module/page/extensions/titles.py:26 +msgid "page title" +msgstr "" -#: module/page/models.py:297 module/page/models.py:782 +#: module/page/extensions/titles.py:30 #, fuzzy -msgid "is active" -msgstr "attiva" +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." +msgstr "" +"Titolo della pagina per la finestra del browser. Stesso titolo per " +"impostazione predefinita." -#: module/page/models.py:631 +#: module/page/extensions/titles.py:60 +msgid "Titles" +msgstr "" + +#: module/page/forms.py:187 msgid "This URL is already taken by an active page." msgstr "Questo URL è già stato preso da una pagina attiva." -#: module/page/models.py:649 +#: module/page/forms.py:206 msgid "This URL is already taken by another active page." msgstr "Questo URL è già stato preso da un'altra pagina attiva." -#: module/page/models.py:674 +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" + +#: module/page/modeladmins.py:42 msgid "Other options" msgstr "Altre opzioni" -#: module/page/models.py:721 +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "in navigazione" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 msgid "Add child page" msgstr "Aggiungi pagina figlio" -#: module/page/models.py:723 +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 msgid "View on site" msgstr "Vedi sul sito" -#: module/page/models.py:759 +#: module/page/modeladmins.py:142 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "" + +#: module/page/modeladmins.py:197 msgid "You don't have the necessary permissions to edit this object" msgstr "" -#: module/page/models.py:774 +#: module/page/modeladmins.py:223 msgid "inherited" msgstr "ereditato" -#: module/page/models.py:778 +#: module/page/modeladmins.py:229 msgid "extensions" msgstr "" -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "data di pubblicazione" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "data fine pubblicazione" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "Lasciare vuoto se la voce dovrebbe rimanere attivo per sempre." - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "visibile da - a" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" msgstr "" -#: module/page/extensions/excerpt.py:9 -msgid "excerpt" -msgstr "" +#: module/page/models.py:169 +msgid "active" +msgstr "attiva" -#: module/page/extensions/excerpt.py:10 -msgid "Add a brief excerpt summarizing the content of this page." -msgstr "" +#: module/page/models.py:173 +#, fuzzy +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "Questo viene utilizzato per la navigazione generato troppo." -#: module/page/extensions/excerpt.py:12 -msgid "Excerpt" +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" msgstr "" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 -msgid "navigation extension" -msgstr "" +#: module/page/models.py:184 +msgid "override URL" +msgstr "override URL" -#: module/page/extensions/navigation.py:99 +#: module/page/models.py:186 msgid "" -"Select the module providing subpages for this page if you need to customize " -"the navigation." -msgstr "" -"Selezionare il modulo che fornisce sottopagine per questa pagina, se avete " -"bisogno di personalizzare la navigazione." - -#: module/page/extensions/navigation.py:112 -msgid "Navigation extension" -msgstr "" - -#: module/page/extensions/relatedpages.py:13 -msgid "Select pages that should be listed as related content." -msgstr "" - -#: module/page/extensions/relatedpages.py:20 -msgid "Related pages" -msgstr "" - -#: module/page/extensions/sites.py:16 -msgid "Site" -msgstr "" - -#: module/page/extensions/symlinks.py:15 -msgid "symlinked page" -msgstr "" - -#: module/page/extensions/symlinks.py:16 -msgid "All content is inherited from this page if given." -msgstr "Tutti i contenuti sono ereditate da questa pagina, se somministrata." - -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." msgstr "" +"Ignorare l'URL di destinazione. Assicurati di includere le barre all'inizio " +"e alla fine se si tratta di un URL locale. Questo riguarda sia la " +"navigazione e gli URL sottopagine '." -#: module/page/extensions/titles.py:13 -msgid "content title" -msgstr "il titolo del contenuto" +#: module/page/models.py:190 +msgid "redirect to" +msgstr "reindirizzamento" -#: module/page/extensions/titles.py:14 -msgid "The first line is the main title, the following lines are subtitles." +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." msgstr "" -"La prima linea è il titolo principale, le seguenti righe sono i sottotitoli." -#: module/page/extensions/titles.py:15 -msgid "page title" -msgstr "" +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "Cache URL" -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." +#: module/page/models.py:298 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" msgstr "" -"Titolo della pagina per la finestra del browser. Stesso titolo per " -"impostazione predefinita." -#: module/page/extensions/titles.py:43 -#, fuzzy -msgid "Titles" -msgstr "Titolo" - -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "Modificare la traduzione" - -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "Crea traduzione" - -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "traduzioni" - -#: templates/admin/filter.html:3 -#, python-format -msgid " By %(filter_title)s " -msgstr "Da %(filter_title)s " +#: module/page/models.py:426 +msgid "page" +msgstr "pagina" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "Ricerca" +#: module/page/models.py:427 +msgid "pages" +msgstr "pagine" #: templates/admin/feincms/_messages_js.html:4 msgid "Really delete item?" @@ -761,16 +804,12 @@ msgstr "Davvero cambiare modello?
    Tutte le modifiche vengono salvate." #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s is moved to %%(target_region)s." msgstr "" -"Davvero cambiare modello?
    Tutte le modifiche vengono salvate e il " -"contenuto da %(source_regions)s è spostato " -"%(target_region)s." #: templates/admin/feincms/_messages_js.html:12 -#, fuzzy msgid "Hide" -msgstr "video" +msgstr "" #: templates/admin/feincms/_messages_js.html:13 msgid "Show" @@ -785,15 +824,18 @@ msgid "Before" msgstr "" #: templates/admin/feincms/_messages_js.html:16 -#, fuzzy msgid "Insert new:" -msgstr "Inserire prima" +msgstr "" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "Regione vuota" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." @@ -801,46 +843,63 @@ msgstr "" "Contenuti dal sito padre viene automaticamente ereditate. Per ignorare " "questo comportamento, aggiungere un po 'di contenuti." -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "Aggiungi nuovo elemento" -#: templates/admin/feincms/fe_editor.html:46 +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "" + +#: templates/admin/feincms/fe_editor.html:30 msgid "Save" msgstr "Salvare" -#: templates/admin/feincms/fe_tools.html:27 +#: templates/admin/feincms/fe_tools.html:28 msgid "Stop Editing" msgstr "" -#: templates/admin/feincms/fe_tools.html:32 +#: templates/admin/feincms/fe_tools.html:33 msgid "edit" msgstr "modifica" -#: templates/admin/feincms/fe_tools.html:34 +#: templates/admin/feincms/fe_tools.html:35 msgid "new" msgstr "nuovo" -#: templates/admin/feincms/fe_tools.html:35 +#: templates/admin/feincms/fe_tools.html:36 msgid "up" msgstr "su" -#: templates/admin/feincms/fe_tools.html:36 +#: templates/admin/feincms/fe_tools.html:37 msgid "down" msgstr "giù" -#: templates/admin/feincms/fe_tools.html:37 +#: templates/admin/feincms/fe_tools.html:38 msgid "remove" msgstr "rimuovere" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:16 +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "" + +#: templates/admin/feincms/page/page/item_editor.html:23 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 msgid "Home" msgstr "Casa" -#: templates/admin/feincms/recover_form.html:10 +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "" + +#: templates/admin/feincms/recover_form.html:11 #, python-format msgid "Recover deleted %(verbose_name)s" msgstr "" @@ -849,49 +908,44 @@ msgstr "" msgid "Press the save button below to recover this version of the object." msgstr "" -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:12 msgid "History" msgstr "" -#: templates/admin/feincms/revision_form.html:15 +#: templates/admin/feincms/revision_form.html:13 #, python-format msgid "Revert %(verbose_name)s" msgstr "" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:24 msgid "Press the save button below to revert to this version of the object." msgstr "" -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "Scorciatoie" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" msgstr "" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" msgstr "" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "Filtro" -#: templates/admin/feincms/page/page/item_editor.html:9 -#, fuzzy -msgid "Edit on site" -msgstr "Vedi sul sito" - -#: templates/admin/feincms/page/page/item_editor.html:20 -msgid "Add" -msgstr "" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr "Da %(filter_title)s " #: templates/admin/medialibrary/add_to_category.html:5 #: templates/admin/medialibrary/add_to_category.html:9 -#, fuzzy msgid "Add media files to category" -msgstr "traduzione di file multimediale" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:11 msgid "Select category to apply:" @@ -902,19 +956,22 @@ msgid "The following media files will be added to the selected category:" msgstr "" #: templates/admin/medialibrary/add_to_category.html:22 -#, fuzzy msgid "Add to category" -msgstr "categoria" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:23 msgid "Cancel" msgstr "" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:11 msgid "Bulk upload a ZIP file:" msgstr "" #: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 msgid "Send" msgstr "" @@ -948,106 +1005,26 @@ msgstr "" msgid "Thanks!" msgstr "Grazie!" -#, fuzzy -#~ msgid "rich text (ckeditor)" -#~ msgstr "rich text" - -#, fuzzy -#~ msgid "rich texts (ckeditor)" -#~ msgstr "rich text" - -#~ msgid "You may edit the copied page below." -#~ msgstr "Si può modificare la pagina copiato qui sotto." - -#~ msgid "" -#~ "You have replaced %s. You may continue editing the now-active page below." -#~ msgstr "" -#~ "Hai sostituito %s. Si può continuare a modificare la pagina ora attiva al " -#~ "di sotto." - -#, fuzzy -#~ msgid "Move" -#~ msgstr "rimuovere" - -#~ msgid "Replace page %(to_replace)s" -#~ msgstr "Sostituire la pagina %(to_replace)s " - -#~ msgid "Create hidden copy of this page" -#~ msgstr "Creare copia nascosta di questa pagina" - -#~ msgid "The %(name)s \"%(obj)s\" was changed successfully." -#~ msgstr "Il %(name)s \" %(obj)s \" è stato modificato con successo." - -#~ msgid "You may edit it again below." -#~ msgstr "Si può modificare di nuovo sotto." - -#~ msgid "You may add another %s below." -#~ msgstr "Si può aggiungere un altro %s al di sotto." - -#~ msgid "Cut" -#~ msgstr "Tagliare" - -#~ msgid "Insert as child" -#~ msgstr "Inserisci come bambino" - -#~ msgid "is visible" -#~ msgstr "è visibile" - -#~ msgid "Video from unknown portal" -#~ msgstr "Video dal portale sconosciuti" - -#~ msgid "Tree saved successfully." -#~ msgstr "Albero salvato con successo." - -#~ msgid "Cannot make a node a child of itself." -#~ msgstr "Non può fare un nodo di un bambino di se stesso." - -#~ msgid "No item has been selected." -#~ msgstr "Nessun elemento è stato selezionato." - -#~ msgid "Delete" -#~ msgstr "Eliminare" - -#~ msgid "Save and add another" -#~ msgstr "Salvare e aggiungere un altro" - -#~ msgid "Save and continue editing" -#~ msgstr "Salvare e continuare ad apportare modifiche" - -#~ msgid "Properties" -#~ msgstr "Immobili" - -#~ msgid "Move selected item to" -#~ msgstr "Sposta l'elemento selezionato" +#~ msgid "plain" +#~ msgstr "plain" -#~ msgid "OK" -#~ msgstr "OK" +#~ msgid "title row" +#~ msgstr "titolo di fila" -#~ msgid "Add %(name)s" -#~ msgstr "Aggiungi %(name)s " +#~ msgid "title row and column" +#~ msgstr "riga del titolo e colonna" -#~ msgid "Select an item on the left side if you want to edit it." -#~ msgstr "Seleziona una voce sul lato sinistro, se si desidera modificarlo." +#~ msgid "table" +#~ msgstr "tavolo" -#~ msgid "" -#~ "You can change the structure of the tree by drag-dropping elements. " -#~ "Please note that changes will be saved immediately. " -#~ msgstr "" -#~ "È possibile modificare la struttura dell'albero con drag-dropping " -#~ "elementi. Si prega di notare che le modifiche verranno salvate " -#~ "immediatamente." +#~ msgid "tables" +#~ msgstr "tavoli" -#~ msgid "" -#~ "The context menu on the tree root and tree nodes provide you with " -#~ "additional modes of operation." -#~ msgstr "" -#~ "Il menu di contesto, la radice dell'albero e nodi della struttura offrire " -#~ "ulteriori modalità di funzionamento." +#~ msgid "data" +#~ msgstr "dati" -#~ msgid "Cannot remove this frame while inside this admin section." -#~ msgstr "" -#~ "Impossibile rimuovere questa cornice, mentre all'interno di questa " -#~ "sezione admin." +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Questo sarà anteposto al elenco di parole chiave predefinite." -#~ msgid "Edit" -#~ msgstr "Modifica" +#~ msgid "This will be prepended to the default description." +#~ msgstr "Questo sarà anteposto alla descrizione di default." diff --git a/feincms/locale/nb/LC_MESSAGES/django.mo b/feincms/locale/nb/LC_MESSAGES/django.mo index f6439481e..5fef81caf 100644 Binary files a/feincms/locale/nb/LC_MESSAGES/django.mo and b/feincms/locale/nb/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/nb/LC_MESSAGES/django.po b/feincms/locale/nb/LC_MESSAGES/django.po index ddb0b2704..64115e488 100644 --- a/feincms/locale/nb/LC_MESSAGES/django.po +++ b/feincms/locale/nb/LC_MESSAGES/django.po @@ -1,204 +1,203 @@ -# Norsk bokmål oversettelse av FeinCMS -# Copyright (C) 2011 Håvard Grimelid -# This file is distributed under the same license as the FeinCMS package. -# Håvard Grimelid , 2011. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. # +# Translators: +# Håvard Grimelid , 2011 msgid "" msgstr "" "Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:19-0500\n" -"Last-Translator: Håvard Grimelid \n" -"Language-Team: nb \n" -"Language: \n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Matthias Kestenholz \n" +"Language-Team: Norwegian Bokmål (http://www.transifex.com/projects/p/feincms/" +"language/nb/)\n" +"Language: nb\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Revision-Date: 2011-03-09 13:20+0100\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "mal" - -#: models.py:553 -msgid "ordering" -msgstr "sortering" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "språk" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filterspecs.py:47 admin/filterspecs.py:86 msgid "All" msgstr "Alle" -#: admin/filterspecs.py:57 module/page/models.py:261 +#: admin/filterspecs.py:58 module/page/models.py:178 msgid "Parent" msgstr "Forelder" -#: admin/filterspecs.py:95 +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 msgid "Category" msgstr "Kategori" -#: admin/item_editor.py:150 +#: admin/item_editor.py:190 #, python-format msgid "Change %s" msgstr "Endre %s" -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:259 -#: module/page/models.py:338 +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 msgid "title" msgstr "tittel" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +msgid "You do not have permission to modify this object" +msgstr "" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 #, python-format msgid "%s has been moved to a new position." msgstr "%s har blitt flytta til en ny posisjon." -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:512 msgid "Did not understand moving instruction." msgstr "Forstod ikke flytteinstruksjonen." -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:523 msgid "actions" msgstr "aktiviteter" -#: content/application/models.py:241 +#: admin/tree_editor.py:552 +#, python-format +msgid "Successfully deleted %(count)d items." +msgstr "" + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +#: content/application/models.py:147 msgid "application content" msgstr "applikasjonsinnhold" -#: content/application/models.py:242 +#: content/application/models.py:148 msgid "application contents" msgstr "applikasjonsinnhold" -#: content/application/models.py:273 +#: content/application/models.py:179 msgid "application" msgstr "applikasjon" -#: content/comments/models.py:28 +#: content/comments/models.py:32 msgid "enabled" msgstr "på" -#: content/comments/models.py:28 +#: content/comments/models.py:33 msgid "New comments may be added" msgstr "Nye kommentarer kan legges til" -#: content/comments/models.py:32 content/comments/models.py:33 +#: content/comments/models.py:37 content/comments/models.py:38 msgid "comments" msgstr "kommentarer" -#: content/comments/models.py:48 +#: content/comments/models.py:60 msgid "public" msgstr "offentlig" -#: content/comments/models.py:48 +#: content/comments/models.py:61 msgid "not public" msgstr "ikke offentlig" -#: content/contactform/models.py:18 +#: content/contactform/models.py:20 msgid "name" msgstr "navn" -#: content/contactform/models.py:19 +#: content/contactform/models.py:21 msgid "email" msgstr "epost" -#: content/contactform/models.py:20 +#: content/contactform/models.py:22 msgid "subject" msgstr "tema" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:26 content/raw/models.py:16 msgid "content" msgstr "innhold" -#: content/contactform/models.py:34 +#: content/contactform/models.py:37 msgid "contact form" msgstr "kontaktskjema" -#: content/contactform/models.py:35 +#: content/contactform/models.py:38 msgid "contact forms" msgstr "kontaktskjemaer" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 msgid "file" msgstr "fil" -#: content/file/models.py:21 +#: content/file/models.py:29 msgid "files" msgstr "filer" -#: content/image/models.py:24 content/image/models.py:28 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "bilde" -#: content/image/models.py:29 +#: content/image/models.py:49 +msgid "alternate text" +msgstr "" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "tittel" + +#: content/image/models.py:56 msgid "images" msgstr "bilder" -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 +#: content/image/models.py:82 msgid "position" msgstr "posisjon" -#: content/medialibrary/models.py:38 -msgid "(no caption)" -msgstr "(ingen tittel)" +#: content/image/models.py:90 +msgid "format" +msgstr "" -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 msgid "media file" msgstr "mediafil" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 msgid "media files" msgstr "mediafiler" -#: content/medialibrary/models.py:131 -msgid "block" -msgstr "blokk" - -#: content/medialibrary/models.py:132 -msgid "left" -msgstr "venstre" - -#: content/medialibrary/models.py:133 -msgid "right" -msgstr "høyre" - -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: content/medialibrary/models.py:64 content/section/models.py:59 msgid "type" msgstr "type" -#: content/raw/models.py:18 +#: content/raw/models.py:20 msgid "raw content" msgstr "råinnhold" -#: content/raw/models.py:19 +#: content/raw/models.py:21 msgid "raw contents" msgstr "råinnhold" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "tekst" - -#: content/richtext/models.py:26 +#: content/richtext/models.py:24 msgid "HTML Tidy" msgstr "HTML Tidy" -#: content/richtext/models.py:27 +#: content/richtext/models.py:25 msgid "Ignore the HTML validation warnings" msgstr "Ignorer advarsler fra HTML-validering" -#: content/richtext/models.py:51 +#: content/richtext/models.py:55 #, python-format msgid "" "HTML validation produced %(count)d warnings. Please review the updated " @@ -207,15 +206,19 @@ msgstr "" "HTML-validering produserte %(count)d advarsler. Revider oppdatert innhold " "under før du fortsetter: %(messages)s" -#: content/richtext/models.py:89 +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "tekst" + +#: content/richtext/models.py:103 msgid "rich text" msgstr "rik tekst" -#: content/richtext/models.py:90 +#: content/richtext/models.py:104 msgid "rich texts" msgstr "rike tekster" -#: content/rss/models.py:21 +#: content/rss/models.py:25 msgid "" "The rss field is updated several times a day. A change in the title will " "only be visible on the home page after the next feed update." @@ -223,75 +226,55 @@ msgstr "" "RSS-strømmen blir oppdatert flere ganger om dagen. En endring i tittelel vil " "ikke bli synlig på nettsiden før etter neste oppdatering." -#: content/rss/models.py:22 +#: content/rss/models.py:28 msgid "link" msgstr "link" -#: content/rss/models.py:23 +#: content/rss/models.py:30 msgid "pre-rendered content" msgstr "forhåndsrendret innhold" -#: content/rss/models.py:24 +#: content/rss/models.py:32 msgid "last updated" msgstr "sist oppdatert" -#: content/rss/models.py:25 +#: content/rss/models.py:33 msgid "max. items" msgstr "maks antall objekter" -#: content/rss/models.py:29 +#: content/rss/models.py:37 msgid "RSS feed" msgstr "RSS-strøm" -#: content/rss/models.py:30 +#: content/rss/models.py:38 msgid "RSS feeds" msgstr "RSS-strømmer" -#: content/section/models.py:37 +#: content/section/models.py:43 msgid "section" msgstr "avsnitt" -#: content/section/models.py:38 +#: content/section/models.py:44 msgid "sections" msgstr "avsnitt" -#: content/table/models.py:62 -msgid "plain" -msgstr "enkel" - -#: content/table/models.py:63 -msgid "title row" -msgstr "tittelrad" - -#: content/table/models.py:65 -msgid "title row and column" -msgstr "tittelrad og kolonne" - -#: content/table/models.py:71 -msgid "table" -msgstr "tabell" - -#: content/table/models.py:72 -msgid "tables" -msgstr "tabeller" - -#: content/table/models.py:86 -msgid "data" -msgstr "data" - -#: content/template/models.py:62 +#: content/template/models.py:53 msgid "template content" msgstr "malinnhold" -#: content/template/models.py:63 +#: content/template/models.py:54 msgid "template contents" msgstr "malinnhold" -#: content/video/models.py:23 +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "mal" + +#: content/video/models.py:34 msgid "video link" msgstr "videolink" -#: content/video/models.py:24 +#: content/video/models.py:36 msgid "" "This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." "com/watch?v=zmj1rpzDRZ0" @@ -299,341 +282,313 @@ msgstr "" "Dette må være en link til en YouTube- eller Vimeo-film. F. eks. http://www." "youtube.com/watch?v=zmj1rpzDRZ0" -#: content/video/models.py:28 +#: content/video/models.py:41 msgid "video" msgstr "video" -#: content/video/models.py:29 +#: content/video/models.py:42 msgid "videos" msgstr "videoer" -#: module/blog/models.py:31 +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "" + +#: models.py:550 +msgid "ordering" +msgstr "sortering" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "merkelapper" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "språk" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "oversettelse av" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "La denne være tom for innlegg på primærspråket." + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "tilgjengelige oversettelser" + +#: module/blog/models.py:33 msgid "published" msgstr "publisert" -#: module/blog/models.py:33 +#: module/blog/models.py:36 msgid "This is used for the generated navigation too." msgstr "Denne vil også bli brukt for navigasjon." -#: module/blog/models.py:36 +#: module/blog/models.py:40 msgid "published on" msgstr "publisert på" -#: module/blog/models.py:37 +#: module/blog/models.py:42 msgid "Will be set automatically once you tick the `published` checkbox above." msgstr "Blir satt automatisk så snart `publisert`-boksen blir kryssa av." -#: module/blog/models.py:42 +#: module/blog/models.py:48 msgid "entry" msgstr "innlegg" -#: module/blog/models.py:43 +#: module/blog/models.py:49 msgid "entries" msgstr "innlegg" -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "merkelapper" - -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "oversettelse av" - -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -msgid "Leave this empty for entries in the primary language." -msgstr "La denne være tom for innlegg på primærspråket." - -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" -msgstr "tilgjengelige oversettelser" - -#: module/extensions/changedate.py:38 +#: module/extensions/changedate.py:41 msgid "creation date" msgstr "opprettet dato" -#: module/extensions/changedate.py:39 +#: module/extensions/changedate.py:43 msgid "modification date" msgstr "endret dato" -#: module/extensions/ct_tracker.py:134 +#: module/extensions/ct_tracker.py:156 msgid "content types" msgstr "innholdstyper" -#: module/extensions/featured.py:9 +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "publiseringsdato" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "sluttdato for publisering" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "La denne være tom dersom siden alltid skal være aktiv." + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "synlig fra - til" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "Datobasert publisering" + +#: module/extensions/featured.py:15 msgid "featured" msgstr "featured" -#: module/extensions/featured.py:14 +#: module/extensions/featured.py:21 msgid "Featured" msgstr "Featured" -#: module/extensions/seo.py:9 +#: module/extensions/seo.py:16 msgid "meta keywords" msgstr "meta-nøkkelord" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "Dette vil bli lagt inn foran i standard nøkkelord-liste." +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" -#: module/extensions/seo.py:11 +#: module/extensions/seo.py:20 msgid "meta description" msgstr "meta-beskrivelse" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." -msgstr "Dette vil bli lagt inn foran standard beskrivelse." +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" -#: module/extensions/seo.py:18 +#: module/extensions/seo.py:32 msgid "Search engine optimization" msgstr "Søkemotoroptimalisering" -#: module/medialibrary/models.py:51 +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "Rediger oversettelse" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Lag oversettelse" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "oversettelser" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "" +msgstr[1] "" + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "Forhåndsvisning" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "filstørrelse" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "opprettet" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "filtype" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "filinfo" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "%d filer importert" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "Ingen inputfil gitt" + +#: module/medialibrary/models.py:49 msgid "parent" msgstr "forelder" -#: module/medialibrary/models.py:53 module/page/models.py:260 +#: module/medialibrary/models.py:51 module/page/models.py:175 msgid "slug" msgstr "slug" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:55 msgid "category" msgstr "kategori" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 msgid "categories" msgstr "kategorier" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" -msgstr "filtype" - -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" -msgstr "opprettet" - -#: module/medialibrary/models.py:93 +#: module/medialibrary/models.py:101 msgid "copyright" msgstr "copyright" -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 -msgid "file size" -msgstr "filstørrelse" - -#: module/medialibrary/models.py:203 -msgid "file info" -msgstr "filinfo" - -#: module/medialibrary/models.py:280 +#: module/medialibrary/models.py:219 msgid "Image" msgstr "Bilde" -#: module/medialibrary/models.py:281 +#: module/medialibrary/models.py:221 msgid "Video" msgstr "Video" -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:224 msgid "Audio" msgstr "Lyd" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:226 msgid "PDF document" msgstr "PDF-dokument" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:227 msgid "Flash" msgstr "Flash" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:228 msgid "Text" msgstr "Tekst" -#: module/medialibrary/models.py:286 +#: module/medialibrary/models.py:229 msgid "Rich Text" msgstr "Rik tekst" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:230 msgid "Zip archive" msgstr "ZIP-arkiv" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:231 msgid "Microsoft Word" msgstr "Microsoft Word" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:233 msgid "Microsoft Excel" msgstr "Microsoft Excel" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:235 msgid "Microsoft PowerPoint" msgstr "Microsoft PowerPoint" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:237 msgid "Binary" msgstr "Binær" -#: module/medialibrary/models.py:307 -msgid "caption" -msgstr "tittel" - -#: module/medialibrary/models.py:308 +#: module/medialibrary/models.py:261 msgid "description" msgstr "beskrivelse" -#: module/medialibrary/models.py:311 +#: module/medialibrary/models.py:264 msgid "media file translation" msgstr "mediafil-oversettelse" -#: module/medialibrary/models.py:312 +#: module/medialibrary/models.py:265 msgid "media file translations" msgstr "mediafil-oversettelser" -#: module/medialibrary/models.py:335 -msgid "Preview" -msgstr "Forhåndsvisning" - -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" - -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" -msgstr "" - -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" -msgstr "%d filer importert" - -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" -msgstr "Ugyldig ZIP-fil: %s" - -#: module/medialibrary/models.py:448 -msgid "No input file given" -msgstr "Ingen inputfil gitt" - -#: module/page/models.py:256 -msgid "active" -msgstr "aktiv" - -#: module/page/models.py:263 module/page/models.py:711 -msgid "in navigation" -msgstr "i navigasjon" - -#: module/page/models.py:264 -msgid "override URL" -msgstr "overstyr URL" - -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." -msgstr "" -"Overstyr mål-URL. Inkluder skråstrek ved starten og ved slutten dersom det " -"er en lokal URL. Dette gjelder både for navigasjons- og underside-URL-er." - -#: module/page/models.py:266 -msgid "redirect to" -msgstr "videresend til" - -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." -msgstr "Mål-URL for automatiske videresendelser." - -#: module/page/models.py:268 -msgid "Cached URL" -msgstr "Mellomlagret URL" - -#: module/page/models.py:279 -msgid "page" -msgstr "side" - -#: module/page/models.py:280 -msgid "pages" -msgstr "sider" - -#: module/page/models.py:297 module/page/models.py:782 -msgid "is active" -msgstr "er aktiv" - -#: module/page/models.py:631 -msgid "This URL is already taken by an active page." -msgstr "Denne URL-en er allerede i bruk av en aktiv side." - -#: module/page/models.py:649 -msgid "This URL is already taken by another active page." -msgstr "Denne URL-en er allerede i bruk av annen en aktiv side." - -#: module/page/models.py:674 -msgid "Other options" -msgstr "Andre valg" - -#: module/page/models.py:721 -msgid "Add child page" -msgstr "Legg til underside" - -#: module/page/models.py:723 -msgid "View on site" -msgstr "Vis på nettsted" - -#: module/page/models.py:759 -msgid "You don't have the necessary permissions to edit this object" -msgstr "" - -#: module/page/models.py:774 -msgid "inherited" -msgstr "arvet" - -#: module/page/models.py:778 -msgid "extensions" -msgstr "utvidelser" - -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "publiseringsdato" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "sluttdato for publisering" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "La denne være tom dersom siden alltid skal være aktiv." - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "synlig fra - til" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" -msgstr "Datobasert publisering" - -#: module/page/extensions/excerpt.py:9 +#: module/page/extensions/excerpt.py:18 msgid "excerpt" msgstr "utdrag" -#: module/page/extensions/excerpt.py:10 +#: module/page/extensions/excerpt.py:21 msgid "Add a brief excerpt summarizing the content of this page." msgstr "Legg til et kort sammendrag av innholdet på denne siden." -#: module/page/extensions/excerpt.py:12 +#: module/page/extensions/excerpt.py:25 msgid "Excerpt" msgstr "Utdrag" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 msgid "navigation extension" msgstr "navigasjonsutvidelse" -#: module/page/extensions/navigation.py:99 +#: module/page/extensions/navigation.py:119 msgid "" "Select the module providing subpages for this page if you need to customize " "the navigation." @@ -641,74 +596,177 @@ msgstr "" "Velg modulen som skal bidra med undersider til denne siden dersom du trenger " "å tilpasse navigasjonen." -#: module/page/extensions/navigation.py:112 +#: module/page/extensions/navigation.py:134 msgid "Navigation extension" msgstr "Navigasjonsutvidelse" -#: module/page/extensions/relatedpages.py:13 +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" +msgstr "" + +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" + +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" +msgstr "i navigasjon" + +#: module/page/extensions/relatedpages.py:21 msgid "Select pages that should be listed as related content." msgstr "Velg sidene som skal listes som relatert innhold." -#: module/page/extensions/relatedpages.py:20 +#: module/page/extensions/relatedpages.py:26 msgid "Related pages" msgstr "Relaterte sider" -#: module/page/extensions/sites.py:16 +#: module/page/extensions/sites.py:21 msgid "Site" msgstr "" -#: module/page/extensions/symlinks.py:15 +#: module/page/extensions/symlinks.py:22 msgid "symlinked page" msgstr "symbolsk linket side" -#: module/page/extensions/symlinks.py:16 +#: module/page/extensions/symlinks.py:23 msgid "All content is inherited from this page if given." msgstr "Dersom gitt, blir alt innhold blir arvet fra denne siden." -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" -msgstr "Symbolsk linket side" - -#: module/page/extensions/titles.py:13 +#: module/page/extensions/titles.py:19 msgid "content title" msgstr "innholdstittel" -#: module/page/extensions/titles.py:14 +#: module/page/extensions/titles.py:22 msgid "The first line is the main title, the following lines are subtitles." msgstr "Første linje er hovedtittelen, følgende linjer er undertitler." -#: module/page/extensions/titles.py:15 +#: module/page/extensions/titles.py:26 msgid "page title" msgstr "sidetittel" -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." +#: module/page/extensions/titles.py:30 +#, fuzzy +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." msgstr "Sidetittel for nettleservinduet. Samme som tittel som standard." -#: module/page/extensions/titles.py:43 +#: module/page/extensions/titles.py:60 msgid "Titles" msgstr "Titler" -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "Rediger oversettelse" +#: module/page/forms.py:187 +msgid "This URL is already taken by an active page." +msgstr "Denne URL-en er allerede i bruk av en aktiv side." -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "Lag oversettelse" +#: module/page/forms.py:206 +msgid "This URL is already taken by another active page." +msgstr "Denne URL-en er allerede i bruk av annen en aktiv side." -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "oversettelser" +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" -#: templates/admin/filter.html:3 +#: module/page/modeladmins.py:42 +msgid "Other options" +msgstr "Andre valg" + +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "i navigasjon" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 +msgid "Add child page" +msgstr "Legg til underside" + +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 +msgid "View on site" +msgstr "Vis på nettsted" + +#: module/page/modeladmins.py:142 #, python-format -msgid " By %(filter_title)s " -msgstr "For %(filter_title)s " +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "" + +#: module/page/modeladmins.py:197 +msgid "You don't have the necessary permissions to edit this object" +msgstr "" + +#: module/page/modeladmins.py:223 +msgid "inherited" +msgstr "arvet" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "Søk" +#: module/page/modeladmins.py:229 +msgid "extensions" +msgstr "utvidelser" + +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" +msgstr "er aktiv" + +#: module/page/models.py:169 +msgid "active" +msgstr "aktiv" + +#: module/page/models.py:173 +#, fuzzy +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "Denne vil også bli brukt for navigasjon." + +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" +msgstr "" + +#: module/page/models.py:184 +msgid "override URL" +msgstr "overstyr URL" + +#: module/page/models.py:186 +msgid "" +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." +msgstr "" +"Overstyr mål-URL. Inkluder skråstrek ved starten og ved slutten dersom det " +"er en lokal URL. Dette gjelder både for navigasjons- og underside-URL-er." + +#: module/page/models.py:190 +msgid "redirect to" +msgstr "videresend til" + +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." +msgstr "" + +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "Mellomlagret URL" + +#: module/page/models.py:298 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" +msgstr "" + +#: module/page/models.py:426 +msgid "page" +msgstr "side" + +#: module/page/models.py:427 +msgid "pages" +msgstr "sider" #: templates/admin/feincms/_messages_js.html:4 msgid "Really delete item?" @@ -743,11 +801,8 @@ msgstr "Endre mal?
    Alle endringer blir lagret." #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s is moved to %%(target_region)s." msgstr "" -"Endre mal?
    Alle endringer blir lagret og innhold fra " -"%(source_regions)s blir flyttet til %(target_region)s." #: templates/admin/feincms/_messages_js.html:12 msgid "Hide" @@ -769,11 +824,15 @@ msgstr "Før" msgid "Insert new:" msgstr "Sett inn ny:" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "Tom region" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." @@ -781,96 +840,109 @@ msgstr "" "Innhold fra forelder-side blir arvet automatisk. Overstyr dette ved å legge " "til innhold." -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "Legg til nytt objekt" -#: templates/admin/feincms/fe_editor.html:46 +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "" + +#: templates/admin/feincms/fe_editor.html:30 msgid "Save" msgstr "Lagre" -#: templates/admin/feincms/fe_tools.html:27 +#: templates/admin/feincms/fe_tools.html:28 msgid "Stop Editing" msgstr "Avslutt redigering" -#: templates/admin/feincms/fe_tools.html:32 +#: templates/admin/feincms/fe_tools.html:33 msgid "edit" msgstr "rediger" -#: templates/admin/feincms/fe_tools.html:34 +#: templates/admin/feincms/fe_tools.html:35 msgid "new" msgstr "ny" -#: templates/admin/feincms/fe_tools.html:35 +#: templates/admin/feincms/fe_tools.html:36 msgid "up" msgstr "opp" -#: templates/admin/feincms/fe_tools.html:36 +#: templates/admin/feincms/fe_tools.html:37 msgid "down" msgstr "ned" -#: templates/admin/feincms/fe_tools.html:37 +#: templates/admin/feincms/fe_tools.html:38 msgid "remove" msgstr "fjern" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:16 +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "Rediger på nettsted" + +#: templates/admin/feincms/page/page/item_editor.html:23 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 msgid "Home" msgstr "Hjem" -#: templates/admin/feincms/recover_form.html:10 -#, fuzzy, python-format +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "" + +#: templates/admin/feincms/recover_form.html:11 +#, python-format msgid "Recover deleted %(verbose_name)s" -msgstr "Legg til enda et %(verbose_name)s" +msgstr "" #: templates/admin/feincms/recover_form.html:17 msgid "Press the save button below to recover this version of the object." msgstr "" -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:12 msgid "History" msgstr "" -#: templates/admin/feincms/revision_form.html:15 -#, fuzzy, python-format +#: templates/admin/feincms/revision_form.html:13 +#, python-format msgid "Revert %(verbose_name)s" -msgstr "Legg til enda et %(verbose_name)s" +msgstr "" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:24 msgid "Press the save button below to revert to this version of the object." msgstr "" -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "Snarveier" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" msgstr "Slå sammen tre" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" msgstr "Utvid tre" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "Filter" -#: templates/admin/feincms/page/page/item_editor.html:9 -msgid "Edit on site" -msgstr "Rediger på nettsted" - -#: templates/admin/feincms/page/page/item_editor.html:20 -msgid "Add" -msgstr "" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr "For %(filter_title)s " #: templates/admin/medialibrary/add_to_category.html:5 #: templates/admin/medialibrary/add_to_category.html:9 -#, fuzzy msgid "Add media files to category" -msgstr "mediafil-oversettelse" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:11 msgid "Select category to apply:" @@ -881,19 +953,22 @@ msgid "The following media files will be added to the selected category:" msgstr "" #: templates/admin/medialibrary/add_to_category.html:22 -#, fuzzy msgid "Add to category" -msgstr "kategori" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:23 msgid "Cancel" msgstr "" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:11 msgid "Bulk upload a ZIP file:" msgstr "Last opp ZIP-filer:" #: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 msgid "Send" msgstr "Send" @@ -931,22 +1006,26 @@ msgstr "Send inn" msgid "Thanks!" msgstr "Takk!" -#~ msgid "Could not access storage" -#~ msgstr "Fikk ikke tilgang til lagringsenheten" +#~ msgid "plain" +#~ msgstr "enkel" + +#~ msgid "title row" +#~ msgstr "tittelrad" + +#~ msgid "title row and column" +#~ msgstr "tittelrad og kolonne" -#~ msgid "You may edit the copied page below." -#~ msgstr "Du kan redigere den kopierte siden under." +#~ msgid "table" +#~ msgstr "tabell" -#~ msgid "" -#~ "You have replaced %s. You may continue editing the now-active page below." -#~ msgstr "" -#~ "%s er erstattet. Du kan fortsette å redigere den nå aktive siden under." +#~ msgid "tables" +#~ msgstr "tabeller" -#~ msgid "Remove" -#~ msgstr "Fjern" +#~ msgid "data" +#~ msgstr "data" -#~ msgid "Replace page %(to_replace)s" -#~ msgstr "Erstatt side %(to_replace)s" +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Dette vil bli lagt inn foran i standard nøkkelord-liste." -#~ msgid "Create hidden copy of this page" -#~ msgstr "Opprett skjult kopi av denne siden" +#~ msgid "This will be prepended to the default description." +#~ msgstr "Dette vil bli lagt inn foran standard beskrivelse." diff --git a/feincms/locale/nl/LC_MESSAGES/django.mo b/feincms/locale/nl/LC_MESSAGES/django.mo index a1196e2c6..770fa02e7 100644 Binary files a/feincms/locale/nl/LC_MESSAGES/django.mo and b/feincms/locale/nl/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/nl/LC_MESSAGES/django.po b/feincms/locale/nl/LC_MESSAGES/django.po index 66d502c0c..aa036021d 100644 --- a/feincms/locale/nl/LC_MESSAGES/django.po +++ b/feincms/locale/nl/LC_MESSAGES/django.po @@ -1,204 +1,206 @@ # 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 , YEAR. # +# Translators: +# bjornpost , 2013 msgid "" msgstr "" -"Project-Id-Version: FeinCMS VERSION\n" +"Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:19-0500\n" -"PO-Revision-Date: 2010-09-15 11:21+0200\n" -"Last-Translator: Bjorn Post \n" -"Language-Team: Dutch\n" -"Language: \n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Matthias Kestenholz \n" +"Language-Team: Dutch (http://www.transifex.com/projects/p/feincms/language/" +"nl/)\n" +"Language: nl\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" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "template" - -#: models.py:553 -msgid "ordering" -msgstr "volgorde" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "taal" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filterspecs.py:47 admin/filterspecs.py:86 msgid "All" msgstr "Alle" -#: admin/filterspecs.py:57 module/page/models.py:261 +#: admin/filterspecs.py:58 module/page/models.py:178 msgid "Parent" msgstr "Ouder" -#: admin/filterspecs.py:95 +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 msgid "Category" msgstr "Categorie" -#: admin/item_editor.py:150 +#: admin/item_editor.py:190 #, python-format msgid "Change %s" msgstr "%s veranderen" -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:259 -#: module/page/models.py:338 +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 msgid "title" msgstr "titel" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +#, fuzzy +#| msgid "You don't have the necessary permissions to edit this object" +msgid "You do not have permission to modify this object" +msgstr "Je hebt niet de juiste rechten om dit object aan te passen" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 #, python-format msgid "%s has been moved to a new position." msgstr "%s is verplaatst." -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:512 msgid "Did not understand moving instruction." msgstr "Begreep verplaats instructie niet" -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:523 msgid "actions" msgstr "acties" -#: content/application/models.py:241 +#: admin/tree_editor.py:552 +#, fuzzy, python-format +#| msgid "Successfully deleted %s items." +msgid "Successfully deleted %(count)d items." +msgstr "%s items succesvol verwijderd." + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "Geselecteerde %(verbose_name_plural)s verwijderen" + +#: content/application/models.py:147 msgid "application content" msgstr "applicatie content" -#: content/application/models.py:242 +#: content/application/models.py:148 msgid "application contents" msgstr "applicatie contents" -#: content/application/models.py:273 +#: content/application/models.py:179 msgid "application" msgstr "applicatie" -#: content/comments/models.py:28 +#: content/comments/models.py:32 msgid "enabled" msgstr "actief" -#: content/comments/models.py:28 +#: content/comments/models.py:33 msgid "New comments may be added" msgstr "Nieuwe reacties kunnen worden toegevoegd" -#: content/comments/models.py:32 content/comments/models.py:33 +#: content/comments/models.py:37 content/comments/models.py:38 msgid "comments" msgstr "reacties" -#: content/comments/models.py:48 +#: content/comments/models.py:60 msgid "public" msgstr "gedeeld" -#: content/comments/models.py:48 +#: content/comments/models.py:61 msgid "not public" msgstr "niet gedeeld" -#: content/contactform/models.py:18 +#: content/contactform/models.py:20 msgid "name" msgstr "naam" -#: content/contactform/models.py:19 +#: content/contactform/models.py:21 msgid "email" msgstr "e-Mail" -#: content/contactform/models.py:20 +#: content/contactform/models.py:22 msgid "subject" msgstr "onderwerp" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:26 content/raw/models.py:16 msgid "content" msgstr "inhoud" -#: content/contactform/models.py:34 +#: content/contactform/models.py:37 msgid "contact form" msgstr "contactformulier" -#: content/contactform/models.py:35 +#: content/contactform/models.py:38 msgid "contact forms" msgstr "contactformulieren" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 msgid "file" msgstr "bestand" -#: content/file/models.py:21 +#: content/file/models.py:29 msgid "files" msgstr "bestanden" -#: content/image/models.py:24 content/image/models.py:28 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "afbeelding" -#: content/image/models.py:29 +#: content/image/models.py:49 +msgid "alternate text" +msgstr "alternatieve tekst" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "Omschrijving van de afbeelding" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "onderschrift" + +#: content/image/models.py:56 msgid "images" msgstr "afbeeldingen" -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 +#: content/image/models.py:82 msgid "position" msgstr "positie" -#: content/medialibrary/models.py:38 -msgid "(no caption)" -msgstr "(geen onderschrift)" +#: content/image/models.py:90 +msgid "format" +msgstr "formaat" -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 msgid "media file" msgstr "media-bestand" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 msgid "media files" msgstr "media-bestanden" -#: content/medialibrary/models.py:131 -msgid "block" -msgstr "blok" - -#: content/medialibrary/models.py:132 -msgid "left" -msgstr "links" - -#: content/medialibrary/models.py:133 -msgid "right" -msgstr "rechts" - -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: content/medialibrary/models.py:64 content/section/models.py:59 msgid "type" msgstr "type" -#: content/raw/models.py:18 +#: content/raw/models.py:20 msgid "raw content" msgstr "Ruwe Inhoud" -#: content/raw/models.py:19 +#: content/raw/models.py:21 msgid "raw contents" msgstr "ruwe inhoud" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "tekst" - -#: content/richtext/models.py:26 +#: content/richtext/models.py:24 msgid "HTML Tidy" msgstr "HTML Tidy" -#: content/richtext/models.py:27 +#: content/richtext/models.py:25 msgid "Ignore the HTML validation warnings" msgstr "Negeer de HTML validatie waarschuwingen" -#: content/richtext/models.py:51 +#: content/richtext/models.py:55 #, python-format msgid "" "HTML validation produced %(count)d warnings. Please review the updated " @@ -207,15 +209,19 @@ msgstr "" "HTML validatie leverde %(count)d waarschuwingen op. Kijk alsjeblieft de " "bijgewerkte content na voordat je verder gaat: %(messages)s" -#: content/richtext/models.py:89 +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "tekst" + +#: content/richtext/models.py:103 msgid "rich text" msgstr "opgemaakte tekst" -#: content/richtext/models.py:90 +#: content/richtext/models.py:104 msgid "rich texts" msgstr "opgemaakte teksten" -#: content/rss/models.py:21 +#: content/rss/models.py:25 msgid "" "The rss field is updated several times a day. A change in the title will " "only be visible on the home page after the next feed update." @@ -223,75 +229,55 @@ msgstr "" "Het RSS veld wordt meerdere malen per dag geactualiseerd. Een verandering in " "de titel zal pas op de pagina verschijnen na de volgende update." -#: content/rss/models.py:22 +#: content/rss/models.py:28 msgid "link" msgstr "Link" -#: content/rss/models.py:23 +#: content/rss/models.py:30 msgid "pre-rendered content" msgstr "Vooraf-gerenderde Inhoud" -#: content/rss/models.py:24 +#: content/rss/models.py:32 msgid "last updated" msgstr "Laatste update" -#: content/rss/models.py:25 +#: content/rss/models.py:33 msgid "max. items" msgstr "Maximaal aantal" -#: content/rss/models.py:29 +#: content/rss/models.py:37 msgid "RSS feed" msgstr "RSS feed" -#: content/rss/models.py:30 +#: content/rss/models.py:38 msgid "RSS feeds" msgstr "RSS feeds" -#: content/section/models.py:37 +#: content/section/models.py:43 msgid "section" msgstr "sectie" -#: content/section/models.py:38 +#: content/section/models.py:44 msgid "sections" msgstr "secties" -#: content/table/models.py:62 -msgid "plain" -msgstr "plat" - -#: content/table/models.py:63 -msgid "title row" -msgstr "titel rij" - -#: content/table/models.py:65 -msgid "title row and column" -msgstr "title rij en kolom" - -#: content/table/models.py:71 -msgid "table" -msgstr "tabel" - -#: content/table/models.py:72 -msgid "tables" -msgstr "tabellen" - -#: content/table/models.py:86 -msgid "data" -msgstr "data" - -#: content/template/models.py:62 +#: content/template/models.py:53 msgid "template content" msgstr "template content" -#: content/template/models.py:63 +#: content/template/models.py:54 msgid "template contents" msgstr "template contents" -#: content/video/models.py:23 +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "template" + +#: content/video/models.py:34 msgid "video link" msgstr "video link" -#: content/video/models.py:24 +#: content/video/models.py:36 msgid "" "This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." "com/watch?v=zmj1rpzDRZ0" @@ -299,344 +285,317 @@ msgstr "" "Dit moet een link naar een youtube of vimeo video zijn. Bijvoorbeeld: http://" "www.youtube.com/watch?v=zmj1rpzDRZ0" -#: content/video/models.py:28 +#: content/video/models.py:41 msgid "video" msgstr "video" -#: content/video/models.py:29 +#: content/video/models.py:42 msgid "videos" msgstr "videos" -#: module/blog/models.py:31 +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "Taggen" + +#: models.py:550 +msgid "ordering" +msgstr "volgorde" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "tags" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "taal" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "vertaling van" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "Laat dit veld leeg voor bijdragen in de hoofd-taal." + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "Beschikbare vertalingen" + +#: module/blog/models.py:33 msgid "published" msgstr "gepubliceerd" -#: module/blog/models.py:33 +#: module/blog/models.py:36 msgid "This is used for the generated navigation too." msgstr "Dit wordt ook gebruikt voor de gegenereerde navigatie." -#: module/blog/models.py:36 +#: module/blog/models.py:40 msgid "published on" msgstr "gepubliceerd op" -#: module/blog/models.py:37 +#: module/blog/models.py:42 msgid "Will be set automatically once you tick the `published` checkbox above." msgstr "" "Wordt automatisch eenmalig ingesteld zodra `gepubliceerd` aangevinkt is." -#: module/blog/models.py:42 +#: module/blog/models.py:48 msgid "entry" msgstr "item" -#: module/blog/models.py:43 +#: module/blog/models.py:49 msgid "entries" msgstr "items" -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "tags" - -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "vertaling van" - -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -msgid "Leave this empty for entries in the primary language." -msgstr "Laat dit veld leeg voor bijdragen in de hoofd-taal." - -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" -msgstr "Beschikbare vertalingen" - -#: module/extensions/changedate.py:38 +#: module/extensions/changedate.py:41 msgid "creation date" msgstr "gemaakt op" -#: module/extensions/changedate.py:39 +#: module/extensions/changedate.py:43 msgid "modification date" msgstr "laatst gewijzigd op" -#: module/extensions/ct_tracker.py:134 +#: module/extensions/ct_tracker.py:156 msgid "content types" msgstr "content types" -#: module/extensions/featured.py:9 +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "gepubliceerd op" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "gepubliceerd tot" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "Leeglaten als het item voor altijd actief moet blijven." + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "zichtbaar van - tot" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "Datum-gebaseerde publicering" + +#: module/extensions/featured.py:15 msgid "featured" msgstr "aanbevolen" -#: module/extensions/featured.py:14 +#: module/extensions/featured.py:21 msgid "Featured" msgstr "Aanbevolen" -#: module/extensions/seo.py:9 +#: module/extensions/seo.py:16 msgid "meta keywords" msgstr "meta trefwoorden" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "Deze begrippen worden voor in de standaard trefwoordenlijst ingevoegd." +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" -#: module/extensions/seo.py:11 +#: module/extensions/seo.py:20 msgid "meta description" msgstr "meta beschrijving" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." -msgstr "Deze beschrijving wordt voor in de standaard beschrijving ingevoegd." +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" -#: module/extensions/seo.py:18 +#: module/extensions/seo.py:32 msgid "Search engine optimization" msgstr "Zoekmachine optimalisatie" -#: module/medialibrary/models.py:51 +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "Wijzig vertaling" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Maak vertaling" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "vertalingen" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "Dit zou een oneindige lus maken in de hiërarchie" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" +"Kan niet overschrijven met ander bestandstype (poging tot overschrijven van " +"%(old_ext)s met %(new_ext)s)" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "%(count)d media file succesvol toegevoegd aan %(category)s" +msgstr[1] "%(count)d media files succesvol toegevoegd aan %(category)s" + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "Voeg geselecteerde media files toe aan categorie" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "ZIP-bestand geexporteerd als %s" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "ZIP-bestand export mislukt: %s" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "Exporteer geselecteerde mediabestanden als ZIP-bestand" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "Voorbeeld" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "bestandsgrootte" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "gemaakt" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "bestandstype" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "bestandsinformatie" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "%d bestanden geïmporteerd" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "Importeren van ZIP-bestand mislukt: %s" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "Geen inputbestand opgegeven" + +#: module/medialibrary/models.py:49 msgid "parent" msgstr "ouder" -#: module/medialibrary/models.py:53 module/page/models.py:260 +#: module/medialibrary/models.py:51 module/page/models.py:175 msgid "slug" msgstr "slug" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:55 msgid "category" msgstr "categorie" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 msgid "categories" msgstr "categorieën" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" -msgstr "bestandstype" - -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" -msgstr "gemaakt" - -#: module/medialibrary/models.py:93 +#: module/medialibrary/models.py:101 msgid "copyright" msgstr "copyright" -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 -msgid "file size" -msgstr "bestandsgrootte" - -#: module/medialibrary/models.py:203 -msgid "file info" -msgstr "bestandsinformatie" - -#: module/medialibrary/models.py:280 +#: module/medialibrary/models.py:219 msgid "Image" msgstr "Afbeelding" -#: module/medialibrary/models.py:281 +#: module/medialibrary/models.py:221 msgid "Video" msgstr "Video" -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:224 msgid "Audio" msgstr "Audio" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:226 msgid "PDF document" msgstr "PDF document" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:227 msgid "Flash" msgstr "Flash" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:228 msgid "Text" msgstr "Tekst" -#: module/medialibrary/models.py:286 +#: module/medialibrary/models.py:229 msgid "Rich Text" msgstr "Opgemaakte Tekst" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:230 msgid "Zip archive" -msgstr "" +msgstr "Zip-archief" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:231 msgid "Microsoft Word" msgstr "Microsoft Word" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:233 msgid "Microsoft Excel" msgstr "Microsoft Excel" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:235 msgid "Microsoft PowerPoint" msgstr "Microsoft PowerPoint" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:237 msgid "Binary" msgstr "Binaire data" -#: module/medialibrary/models.py:307 -msgid "caption" -msgstr "onderschrift" - -#: module/medialibrary/models.py:308 +#: module/medialibrary/models.py:261 msgid "description" msgstr "omschrijving" -#: module/medialibrary/models.py:311 +#: module/medialibrary/models.py:264 msgid "media file translation" msgstr "Mediabestand-vertaling" -#: module/medialibrary/models.py:312 +#: module/medialibrary/models.py:265 msgid "media file translations" msgstr "Mediabestand-vertalingen" -#: module/medialibrary/models.py:335 -msgid "Preview" -msgstr "Voorbeeld" - -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" - -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" -msgstr "" - -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" -msgstr "" - -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" -msgstr "" - -#: module/medialibrary/models.py:448 -msgid "No input file given" -msgstr "" - -#: module/page/models.py:256 -msgid "active" -msgstr "actief" - -#: module/page/models.py:263 module/page/models.py:711 -msgid "in navigation" -msgstr "in navigatie" - -#: module/page/models.py:264 -msgid "override URL" -msgstr "overschrijf URL" - -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." -msgstr "" -"Overschrijft de URL. Als het een lokale URL betreft moet er aan aan " -"weerszijden een / staan. Dit veld heeft betrekking op zowel de navigatie " -"als de URLs van subpagina's." - -#: module/page/models.py:266 -msgid "redirect to" -msgstr "doorsturen naar" - -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." -msgstr "Eindpunt (URL) voor automatisch doorsturen." - -#: module/page/models.py:268 -msgid "Cached URL" -msgstr "URL uit cache" - -#: module/page/models.py:279 -msgid "page" -msgstr "pagina" - -#: module/page/models.py:280 -msgid "pages" -msgstr "pagina's" - -#: module/page/models.py:297 module/page/models.py:782 -msgid "is active" -msgstr "is actief" - -#: module/page/models.py:631 -msgid "This URL is already taken by an active page." -msgstr "Deze URL is al in gebruik door een actieve pagina." - -#: module/page/models.py:649 -msgid "This URL is already taken by another active page." -msgstr "Deze URL is al in gebruik door een andere actieve pagina." - -#: module/page/models.py:674 -msgid "Other options" -msgstr "Overige opties" - -#: module/page/models.py:721 -msgid "Add child page" -msgstr "Voeg subpagina toe" - -#: module/page/models.py:723 -msgid "View on site" -msgstr "Op de website bekijken" - -#: module/page/models.py:759 -msgid "You don't have the necessary permissions to edit this object" -msgstr "" - -#: module/page/models.py:774 -msgid "inherited" -msgstr "geërfd" - -#: module/page/models.py:778 -msgid "extensions" -msgstr "uitbreidingen" - -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "gepubliceerd op" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "gepubliceerd tot" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "Leeglaten als het item voor altijd actief moet blijven." - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "zichtbaar van - tot" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" -msgstr "Datum-gebaseerde publicering" - -#: module/page/extensions/excerpt.py:9 +#: module/page/extensions/excerpt.py:18 msgid "excerpt" msgstr "samenvatting" -#: module/page/extensions/excerpt.py:10 +#: module/page/extensions/excerpt.py:21 msgid "Add a brief excerpt summarizing the content of this page." msgstr "" "Voeg een korte samenvatting toe die de inhoud van deze pagina beschrijft." -#: module/page/extensions/excerpt.py:12 +#: module/page/extensions/excerpt.py:25 msgid "Excerpt" msgstr "Samenvatting" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 msgid "navigation extension" msgstr "navigatie-uitbreiding" -#: module/page/extensions/navigation.py:99 +#: module/page/extensions/navigation.py:119 msgid "" "Select the module providing subpages for this page if you need to customize " "the navigation." @@ -644,76 +603,182 @@ msgstr "" "Kies de module die subpagina's voor deze pagina definieert als je de " "navigatie wilt uitbreiden." -#: module/page/extensions/navigation.py:112 +#: module/page/extensions/navigation.py:134 msgid "Navigation extension" msgstr "Navigatie-uitbreiding" -#: module/page/extensions/relatedpages.py:13 +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" +msgstr "" + +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" + +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" +msgstr "in navigatie" + +#: module/page/extensions/relatedpages.py:21 msgid "Select pages that should be listed as related content." msgstr "Selecteer pagina's die beschouwd moet worden als gerelateerd aan deze." -#: module/page/extensions/relatedpages.py:20 +#: module/page/extensions/relatedpages.py:26 msgid "Related pages" msgstr "Gerelateerde pagina's" -#: module/page/extensions/sites.py:16 +#: module/page/extensions/sites.py:21 msgid "Site" -msgstr "" +msgstr "Site" -#: module/page/extensions/symlinks.py:15 +#: module/page/extensions/symlinks.py:22 msgid "symlinked page" msgstr "verbonden pagina" -#: module/page/extensions/symlinks.py:16 +#: module/page/extensions/symlinks.py:23 msgid "All content is inherited from this page if given." msgstr "" "Als dit ingesteld is, dan wordt de inhoud geleverd door de aangegeven pagina." -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" -msgstr "Verbonden pagina" - -#: module/page/extensions/titles.py:13 +#: module/page/extensions/titles.py:19 msgid "content title" msgstr "content titel" -#: module/page/extensions/titles.py:14 +#: module/page/extensions/titles.py:22 msgid "The first line is the main title, the following lines are subtitles." msgstr "" "De eerste regel is de hoofd-titel, de overige regels vormen de ondertitel." -#: module/page/extensions/titles.py:15 +#: module/page/extensions/titles.py:26 msgid "page title" msgstr "paginatitel" -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." +#: module/page/extensions/titles.py:30 +#, fuzzy +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." msgstr "paginatitel voor het browservenster. Standaard gelijk aan de titel." -#: module/page/extensions/titles.py:43 +#: module/page/extensions/titles.py:60 msgid "Titles" msgstr "Titels" -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "Wijzig vertaling" +#: module/page/forms.py:187 +msgid "This URL is already taken by an active page." +msgstr "Deze URL is al in gebruik door een actieve pagina." -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "Maak vertaling" +#: module/page/forms.py:206 +msgid "This URL is already taken by another active page." +msgstr "Deze URL is al in gebruik door een andere actieve pagina." -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "vertalingen" +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" -#: templates/admin/filter.html:3 +#: module/page/modeladmins.py:42 +msgid "Other options" +msgstr "Overige opties" + +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "in navigatie" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 +msgid "Add child page" +msgstr "Voeg subpagina toe" + +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 +msgid "View on site" +msgstr "Op de website bekijken" + +#: module/page/modeladmins.py:142 #, python-format -msgid " By %(filter_title)s " -msgstr " Op %(filter_title)s" +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "" +"De inhoud van de originele vertaling is gekopieerd naar de aangemaakte " +"pagina." + +#: module/page/modeladmins.py:197 +msgid "You don't have the necessary permissions to edit this object" +msgstr "Je hebt niet de juiste rechten om dit object aan te passen" + +#: module/page/modeladmins.py:223 +msgid "inherited" +msgstr "geërfd" + +#: module/page/modeladmins.py:229 +msgid "extensions" +msgstr "uitbreidingen" + +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" +msgstr "is actief" + +#: module/page/models.py:169 +msgid "active" +msgstr "actief" + +#: module/page/models.py:173 +#, fuzzy +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "Dit wordt ook gebruikt voor de gegenereerde navigatie." + +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" +msgstr "" + +#: module/page/models.py:184 +msgid "override URL" +msgstr "overschrijf URL" + +#: module/page/models.py:186 +msgid "" +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." +msgstr "" +"Overschrijft de URL. Als het een lokale URL betreft moet er aan aan " +"weerszijden een / staan. Dit veld heeft betrekking op zowel de navigatie " +"als de URLs van subpagina's." + +#: module/page/models.py:190 +msgid "redirect to" +msgstr "doorsturen naar" + +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." +msgstr "" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "Zoeken" +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "URL uit cache" + +#: module/page/models.py:298 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" +msgstr "" + +#: module/page/models.py:426 +msgid "page" +msgstr "pagina" + +#: module/page/models.py:427 +msgid "pages" +msgstr "pagina's" #: templates/admin/feincms/_messages_js.html:4 msgid "Really delete item?" @@ -749,11 +814,11 @@ msgstr "Template echt veranderen?
    Alle wijzigingen zijn opgeslagen." #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s
    is moved to %%(target_region)s." msgstr "" "Template echt veranderen?
    Alle wijzigingen zijn opgeslagen en de " -"inhoud van %(source_regions)sis verplaatst naar " -"%(target_region)s." +"inhoud van %%(source_regions)sis verplaatst naar " +"%%(target_region)s." #: templates/admin/feincms/_messages_js.html:12 msgid "Hide" @@ -775,11 +840,15 @@ msgstr "Voor" msgid "Insert new:" msgstr "Voeg in:" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "Leeg gebied" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." @@ -787,119 +856,136 @@ msgstr "" "Inhoud van de ouder-pagina wordt automatisch geërfd. Voeg zelf inhoud toe om " "dit te veranderen." -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "Nieuw item toevoegen" -#: templates/admin/feincms/fe_editor.html:46 +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "Voeg nog een %(verbose_name)s toe" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "Verwijder" + +#: templates/admin/feincms/fe_editor.html:30 msgid "Save" msgstr "Opslaan" -#: templates/admin/feincms/fe_tools.html:27 +#: templates/admin/feincms/fe_tools.html:28 msgid "Stop Editing" msgstr "Stop met Wijzigen" -#: templates/admin/feincms/fe_tools.html:32 +#: templates/admin/feincms/fe_tools.html:33 msgid "edit" msgstr "wijzigen" -#: templates/admin/feincms/fe_tools.html:34 +#: templates/admin/feincms/fe_tools.html:35 msgid "new" msgstr "nieuw" -#: templates/admin/feincms/fe_tools.html:35 +#: templates/admin/feincms/fe_tools.html:36 msgid "up" msgstr "naar boven" -#: templates/admin/feincms/fe_tools.html:36 +#: templates/admin/feincms/fe_tools.html:37 msgid "down" msgstr "naar onder" -#: templates/admin/feincms/fe_tools.html:37 +#: templates/admin/feincms/fe_tools.html:38 msgid "remove" msgstr "verwijderen" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:16 +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "Op de website wijzigen" + +#: templates/admin/feincms/page/page/item_editor.html:23 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 msgid "Home" msgstr "Startpagina" -#: templates/admin/feincms/recover_form.html:10 +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "Voeg toe" + +#: templates/admin/feincms/recover_form.html:11 #, python-format msgid "Recover deleted %(verbose_name)s" -msgstr "" +msgstr "Herstel verwijderd %(verbose_name)s" #: templates/admin/feincms/recover_form.html:17 msgid "Press the save button below to recover this version of the object." -msgstr "" +msgstr "Druk op de opslaan-knop om deze versie van het object te herstellen." -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:12 msgid "History" -msgstr "" +msgstr "Geschiedenis" -#: templates/admin/feincms/revision_form.html:15 +#: templates/admin/feincms/revision_form.html:13 #, python-format msgid "Revert %(verbose_name)s" -msgstr "" +msgstr "Herstel %(verbose_name)s" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:24 msgid "Press the save button below to revert to this version of the object." -msgstr "" +msgstr "Druk op de save-knop om deze versie van het object te herstellen." -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "Snelkoppelingen" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" msgstr "Alles dichtklappen" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" msgstr "Alles openklappen" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "Filter" -#: templates/admin/feincms/page/page/item_editor.html:9 -msgid "Edit on site" -msgstr "Op de website wijzigen" - -#: templates/admin/feincms/page/page/item_editor.html:20 -msgid "Add" -msgstr "" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr " Op %(filter_title)s" #: templates/admin/medialibrary/add_to_category.html:5 #: templates/admin/medialibrary/add_to_category.html:9 -#, fuzzy msgid "Add media files to category" -msgstr "Mediabestand-vertaling" +msgstr "Voeg mediabestanden toe aan categorie" #: templates/admin/medialibrary/add_to_category.html:11 msgid "Select category to apply:" -msgstr "" +msgstr "Selecteer categorie om toe te passen:" #: templates/admin/medialibrary/add_to_category.html:17 msgid "The following media files will be added to the selected category:" msgstr "" +"De volgende mediabestanden worden toegevoegd aan de geselecteerde categorie:" #: templates/admin/medialibrary/add_to_category.html:22 -#, fuzzy msgid "Add to category" -msgstr "categorie" +msgstr "Voeg toe aan categorie" #: templates/admin/medialibrary/add_to_category.html:23 msgid "Cancel" -msgstr "" +msgstr "Annuleer" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:11 msgid "Bulk upload a ZIP file:" msgstr "Opload meerdere bestanden tegelijk (zip-bestand):" #: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "Overschijven" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 msgid "Send" msgstr "Verstuur" @@ -937,62 +1023,28 @@ msgstr "Verzenden" msgid "Thanks!" msgstr "Bedankt!" -#~ msgid "rich text (ckeditor)" -#~ msgstr "opgemaakte tekst (ckeditor)" - -#~ msgid "rich texts (ckeditor)" -#~ msgstr "opgemaakte teksten (ckeditor)" - -#~ msgid "You may edit the copied page below." -#~ msgstr "Je kunt de gekopieerde pagina hieronder wijzigen." - -#~ msgid "" -#~ "You have replaced %s. You may continue editing the now-active page below." -#~ msgstr "" -#~ "Je hebt %s vervangen. Je kunt de nu actieve pagina hieronder verder " -#~ "wijzigen." - -#~ msgid "Move to" -#~ msgstr "Verplaats naar" - -#~ msgid "Move" -#~ msgstr "Verplaats" +#~ msgid "plain" +#~ msgstr "plat" -#~ msgid "Replace page %(to_replace)s" -#~ msgstr "Vervang pagina %(to_replace)s" +#~ msgid "title row" +#~ msgstr "titel rij" -#~ msgid "Create hidden copy of this page" -#~ msgstr "Maak verborgen kopie" +#~ msgid "title row and column" +#~ msgstr "title rij en kolom" -#~ msgid "Add %(name)s" -#~ msgstr "Voeg %(name)s toe" +#~ msgid "table" +#~ msgstr "tabel" -#~ msgid "Select an item on the left side if you want to edit it." -#~ msgstr "Selecteer een item aan de linkerzijde als je het wilt wijzigen." +#~ msgid "tables" +#~ msgstr "tabellen" -#~ msgid "" -#~ "You can change the structure of the tree by drag-dropping elements. " -#~ "Please note that changes will be saved immediately. " -#~ msgstr "" -#~ "Je kunt de structuur veranderen door de elementen te verslepen. Alle " -#~ "wijzigingen worden direct opgeslagen." +#~ msgid "data" +#~ msgstr "data" -#~ msgid "" -#~ "The context menu on the tree root and tree nodes provide you with " -#~ "additional modes of operation." +#~ msgid "This will be prepended to the default keyword list." #~ msgstr "" -#~ "Het contextmenu in de hoofdtak en takken van de boom voorzien je van " -#~ "extra functionaliteiten." +#~ "Deze begrippen worden voor in de standaard trefwoordenlijst ingevoegd." -#~ msgid "Cannot remove this frame while inside this admin section." +#~ msgid "This will be prepended to the default description." #~ msgstr "" -#~ "Kan dit frame niet verwijderen terwijl we in deze admin sectie zijn." - -#~ msgid "Reload" -#~ msgstr "Vernieuw" - -#~ msgid "Edit" -#~ msgstr "Wijzigen" - -#~ msgid "Delete" -#~ msgstr "Verwijderen" +#~ "Deze beschrijving wordt voor in de standaard beschrijving ingevoegd." diff --git a/feincms/locale/pl/LC_MESSAGES/django.mo b/feincms/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 000000000..ef7824d21 Binary files /dev/null and b/feincms/locale/pl/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/pl/LC_MESSAGES/django.po b/feincms/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 000000000..fff700744 --- /dev/null +++ b/feincms/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,1018 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# areksz , 2013 +msgid "" +msgstr "" +"Project-Id-Version: FeinCMS\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Matthias Kestenholz \n" +"Language-Team: Polish (http://www.transifex.com/projects/p/feincms/language/" +"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" + +#: admin/filterspecs.py:47 admin/filterspecs.py:86 +msgid "All" +msgstr "wszystko" + +#: admin/filterspecs.py:58 module/page/models.py:178 +msgid "Parent" +msgstr "Rodzic" + +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 +msgid "Category" +msgstr "Kategoria" + +#: admin/item_editor.py:190 +#, python-format +msgid "Change %s" +msgstr "Zmień %s" + +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 +msgid "title" +msgstr "tytuł" + +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +#, fuzzy +#| msgid "You don't have the necessary permissions to edit this object" +msgid "You do not have permission to modify this object" +msgstr "Nie masz wystarczających uprawnień aby edytować ten element" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 +#, python-format +msgid "%s has been moved to a new position." +msgstr "%s został przesunięty" + +#: admin/tree_editor.py:512 +msgid "Did not understand moving instruction." +msgstr "Instrukcja przesunięcia nie jest zrozumiała." + +#: admin/tree_editor.py:523 +msgid "actions" +msgstr "akcje" + +#: admin/tree_editor.py:552 +#, python-format +msgid "Successfully deleted %(count)d items." +msgstr "" + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +#: content/application/models.py:147 +msgid "application content" +msgstr "zawartość aplikacji" + +#: content/application/models.py:148 +msgid "application contents" +msgstr "application content" + +#: content/application/models.py:179 +msgid "application" +msgstr "aplikacja" + +#: content/comments/models.py:32 +msgid "enabled" +msgstr "aktywny" + +#: content/comments/models.py:33 +msgid "New comments may be added" +msgstr "Nowe komentarze mogą być dodane" + +#: content/comments/models.py:37 content/comments/models.py:38 +msgid "comments" +msgstr "komentarze" + +#: content/comments/models.py:60 +msgid "public" +msgstr "publiczne" + +#: content/comments/models.py:61 +msgid "not public" +msgstr "nie publiczne" + +#: content/contactform/models.py:20 +msgid "name" +msgstr "nazwa" + +#: content/contactform/models.py:21 +msgid "email" +msgstr "email" + +#: content/contactform/models.py:22 +msgid "subject" +msgstr "temat" + +#: content/contactform/models.py:26 content/raw/models.py:16 +msgid "content" +msgstr "zawartość" + +#: content/contactform/models.py:37 +msgid "contact form" +msgstr "formularz kontaktowy" + +#: content/contactform/models.py:38 +msgid "contact forms" +msgstr "formularze kontaktowe" + +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 +msgid "file" +msgstr "plik" + +#: content/file/models.py:29 +msgid "files" +msgstr "pliki" + +#: content/image/models.py:46 content/image/models.py:55 +msgid "image" +msgstr "zdjęcie" + +#: content/image/models.py:49 +msgid "alternate text" +msgstr "" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "" + +#: content/image/models.py:56 +msgid "images" +msgstr "zdjęcia" + +#: content/image/models.py:82 +msgid "position" +msgstr "pozycja" + +#: content/image/models.py:90 +msgid "format" +msgstr "" + +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 +msgid "media file" +msgstr "plik media" + +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 +msgid "media files" +msgstr "pliki media" + +#: content/medialibrary/models.py:64 content/section/models.py:59 +msgid "type" +msgstr "typ" + +#: content/raw/models.py:20 +msgid "raw content" +msgstr "tekst niesformatowany" + +#: content/raw/models.py:21 +msgid "raw contents" +msgstr "teksty niesformatowane" + +#: content/richtext/models.py:24 +msgid "HTML Tidy" +msgstr "HTML Tidy" + +#: content/richtext/models.py:25 +msgid "Ignore the HTML validation warnings" +msgstr "Ignoruj ostrzeżenia o walidacji HMTL" + +#: content/richtext/models.py:55 +#, python-format +msgid "" +"HTML validation produced %(count)d warnings. Please review the updated " +"content below before continuing: %(messages)s" +msgstr "" + +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "tekst" + +#: content/richtext/models.py:103 +msgid "rich text" +msgstr "Obszar tekstowy (WYSIWYG)" + +#: content/richtext/models.py:104 +msgid "rich texts" +msgstr "Obszary tekstowe (WYSIWYG)" + +#: content/rss/models.py:25 +msgid "" +"The rss field is updated several times a day. A change in the title will " +"only be visible on the home page after the next feed update." +msgstr "" + +#: content/rss/models.py:28 +msgid "link" +msgstr "link" + +#: content/rss/models.py:30 +msgid "pre-rendered content" +msgstr "" + +#: content/rss/models.py:32 +msgid "last updated" +msgstr "ostatnio aktualizowany" + +#: content/rss/models.py:33 +msgid "max. items" +msgstr "maksymalna ilość" + +#: content/rss/models.py:37 +msgid "RSS feed" +msgstr "kanał RSS" + +#: content/rss/models.py:38 +msgid "RSS feeds" +msgstr "kanały RSS" + +#: content/section/models.py:43 +msgid "section" +msgstr "sekcja" + +#: content/section/models.py:44 +msgid "sections" +msgstr "sekcje" + +#: content/template/models.py:53 +msgid "template content" +msgstr "tekst szablonu" + +#: content/template/models.py:54 +msgid "template contents" +msgstr "teksty szablonów" + +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "szablon" + +#: content/video/models.py:34 +msgid "video link" +msgstr "link video" + +#: content/video/models.py:36 +msgid "" +"This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." +"com/watch?v=zmj1rpzDRZ0" +msgstr "" +"Umieść link do youtube.com lub vimeo.com, np. http://www.youtube.com/watch?" +"v=zmj1rpzDRZ0" + +#: content/video/models.py:41 +msgid "video" +msgstr "video" + +#: content/video/models.py:42 +msgid "videos" +msgstr "video" + +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "" + +#: models.py:550 +msgid "ordering" +msgstr "sortowanie" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "tagi" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "język" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "tłumaczenie" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "" + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "dostępne tłumaczenia" + +#: module/blog/models.py:33 +msgid "published" +msgstr "opublikowany" + +#: module/blog/models.py:36 +msgid "This is used for the generated navigation too." +msgstr "" + +#: module/blog/models.py:40 +msgid "published on" +msgstr "opublikowany na" + +#: module/blog/models.py:42 +msgid "Will be set automatically once you tick the `published` checkbox above." +msgstr "" +"Zostanie automatycznie ustawione po kliknięciu \"opublikowany\" powyżej" + +#: module/blog/models.py:48 +msgid "entry" +msgstr "wpis" + +#: module/blog/models.py:49 +msgid "entries" +msgstr "wpisy" + +#: module/extensions/changedate.py:41 +msgid "creation date" +msgstr "data utworzenia" + +#: module/extensions/changedate.py:43 +msgid "modification date" +msgstr "data modyfikacji" + +#: module/extensions/ct_tracker.py:156 +msgid "content types" +msgstr "typ treści" + +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "data publikacji" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "końcowa data publikacji" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "Pozostaw puste jeśli nie chcesz określać końcowej daty publikacji" + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "widoczne od - do" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "Określanie okresu publikacji" + +#: module/extensions/featured.py:15 +msgid "featured" +msgstr "wyróżniony" + +#: module/extensions/featured.py:21 +msgid "Featured" +msgstr "" + +#: module/extensions/seo.py:16 +msgid "meta keywords" +msgstr "tagi meta - keywords" + +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" + +#: module/extensions/seo.py:20 +msgid "meta description" +msgstr "tagi meta - description" + +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" + +#: module/extensions/seo.py:32 +msgid "Search engine optimization" +msgstr "Pola SEO " + +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "Edytuj tłumaczenie" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Stwórz tłumaczenie" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "tłumaczenia" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "Dodaj zaznaczone pliki do kategorii" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "Podgląd" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "rozmiar pliku" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "utworzony" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "typ pliku" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "informacje o pliku" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "zaimportowano %d plików" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "Brak wybranego pliku " + +#: module/medialibrary/models.py:49 +msgid "parent" +msgstr "rodzic" + +#: module/medialibrary/models.py:51 module/page/models.py:175 +msgid "slug" +msgstr "slug (wyświetlany adres)" + +#: module/medialibrary/models.py:55 +msgid "category" +msgstr "kategoria" + +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 +msgid "categories" +msgstr "kategorie" + +#: module/medialibrary/models.py:101 +msgid "copyright" +msgstr "copyright" + +#: module/medialibrary/models.py:219 +msgid "Image" +msgstr "Zdjęcie" + +#: module/medialibrary/models.py:221 +msgid "Video" +msgstr "Video" + +#: module/medialibrary/models.py:224 +msgid "Audio" +msgstr "Audio" + +#: module/medialibrary/models.py:226 +msgid "PDF document" +msgstr "Dokument PDF" + +#: module/medialibrary/models.py:227 +msgid "Flash" +msgstr "Flash" + +#: module/medialibrary/models.py:228 +msgid "Text" +msgstr "Tekst" + +#: module/medialibrary/models.py:229 +msgid "Rich Text" +msgstr "Obszar tekstowy (WYSIWYG)" + +#: module/medialibrary/models.py:230 +msgid "Zip archive" +msgstr "Archiwum ZIP" + +#: module/medialibrary/models.py:231 +msgid "Microsoft Word" +msgstr "Microsoft Word" + +#: module/medialibrary/models.py:233 +msgid "Microsoft Excel" +msgstr "Microsoft Excel" + +#: module/medialibrary/models.py:235 +msgid "Microsoft PowerPoint" +msgstr "Microsoft PowerPoint" + +#: module/medialibrary/models.py:237 +msgid "Binary" +msgstr "Binarny" + +#: module/medialibrary/models.py:261 +msgid "description" +msgstr "opis" + +#: module/medialibrary/models.py:264 +msgid "media file translation" +msgstr "" + +#: module/medialibrary/models.py:265 +msgid "media file translations" +msgstr "" + +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" +msgstr "" + +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." +msgstr "" + +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" +msgstr "" + +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 +msgid "navigation extension" +msgstr "" + +#: module/page/extensions/navigation.py:119 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." +msgstr "" + +#: module/page/extensions/navigation.py:134 +msgid "Navigation extension" +msgstr "" + +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" +msgstr "" + +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" + +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" +msgstr "W menu" + +#: module/page/extensions/relatedpages.py:21 +msgid "Select pages that should be listed as related content." +msgstr "" + +#: module/page/extensions/relatedpages.py:26 +msgid "Related pages" +msgstr "Powiązane strony" + +#: module/page/extensions/sites.py:21 +msgid "Site" +msgstr "Strony" + +#: module/page/extensions/symlinks.py:22 +msgid "symlinked page" +msgstr "dowiązana strona" + +#: module/page/extensions/symlinks.py:23 +msgid "All content is inherited from this page if given." +msgstr "" + +#: module/page/extensions/titles.py:19 +msgid "content title" +msgstr "Tytuł treści" + +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." +msgstr "" + +#: module/page/extensions/titles.py:26 +msgid "page title" +msgstr "Tytuł strony" + +#: module/page/extensions/titles.py:30 +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." +msgstr "" + +#: module/page/extensions/titles.py:60 +msgid "Titles" +msgstr "Tytuły" + +#: module/page/forms.py:187 +msgid "This URL is already taken by an active page." +msgstr "ten URL jest już używany przez aktywną stronę." + +#: module/page/forms.py:206 +msgid "This URL is already taken by another active page." +msgstr "ten URL jest już używany przez inną aktywną stronę." + +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" + +#: module/page/modeladmins.py:42 +msgid "Other options" +msgstr "Pozostałe opcje" + +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "W menu" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 +msgid "Add child page" +msgstr "Dodaj podstronę" + +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 +msgid "View on site" +msgstr "Zobacz podgląd strony" + +#: module/page/modeladmins.py:142 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "" + +#: module/page/modeladmins.py:197 +msgid "You don't have the necessary permissions to edit this object" +msgstr "Nie masz wystarczających uprawnień aby edytować ten element" + +#: module/page/modeladmins.py:223 +msgid "inherited" +msgstr "dziedziczone" + +#: module/page/modeladmins.py:229 +msgid "extensions" +msgstr "rozszerzenia" + +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" +msgstr "czy jest aktywne" + +#: module/page/models.py:169 +msgid "active" +msgstr "aktywny" + +#: module/page/models.py:173 +msgid "This title is also used for navigation menu items." +msgstr "" + +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" +msgstr "" + +#: module/page/models.py:184 +msgid "override URL" +msgstr "nadpisz URL" + +#: module/page/models.py:186 +msgid "" +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." +msgstr "" + +#: module/page/models.py:190 +msgid "redirect to" +msgstr "przekieruj do" + +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." +msgstr "" + +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "" + +#: module/page/models.py:298 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" +msgstr "" + +#: module/page/models.py:426 +msgid "page" +msgstr "strona" + +#: module/page/models.py:427 +msgid "pages" +msgstr "strony" + +#: templates/admin/feincms/_messages_js.html:4 +msgid "Really delete item?" +msgstr "Usunąć?" + +#: templates/admin/feincms/_messages_js.html:4 +msgid "Confirm to delete item" +msgstr "Potwierdź usunięcie" + +#: templates/admin/feincms/_messages_js.html:5 +msgid "Item deleted successfully." +msgstr "Element został usunięty" + +#: templates/admin/feincms/_messages_js.html:5 +msgid "Cannot delete item" +msgstr "Brak możliwości usunięcia elementu" + +#: templates/admin/feincms/_messages_js.html:6 +msgid "Cannot delete item, because it is parent of at least one other item." +msgstr "" +"Element nie może zostać usunięty ponieważ zawiera przynajmniej jeden element " +"podrzędny. Usuń wpierw elementy podrzędne." + +#: templates/admin/feincms/_messages_js.html:7 +msgid "Change template" +msgstr "Zmień szablon" + +#: templates/admin/feincms/_messages_js.html:8 +msgid "Really change template?
    All changes are saved." +msgstr "Zmienić szablon?
    Wszystkie zmiany zostały zachowane." + +#: templates/admin/feincms/_messages_js.html:9 +#, python-format +msgid "" +"Really change template?
    All changes are saved and content from " +"%%(source_regions)s is moved to %%(target_region)s." +msgstr "" + +#: templates/admin/feincms/_messages_js.html:12 +msgid "Hide" +msgstr "Ukryj" + +#: templates/admin/feincms/_messages_js.html:13 +msgid "Show" +msgstr "Pokaż" + +#: templates/admin/feincms/_messages_js.html:14 +msgid "After" +msgstr "Za" + +#: templates/admin/feincms/_messages_js.html:15 +msgid "Before" +msgstr "Przed" + +#: templates/admin/feincms/_messages_js.html:16 +msgid "Insert new:" +msgstr "Wstaw nowy:" + +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 +msgid "Region empty" +msgstr "Pusty region" + +#: templates/admin/feincms/content_editor.html:25 +msgid "" +"Content from the parent site is automatically inherited. To override this " +"behaviour, add some content." +msgstr "" +"Treść ze strony nadrzędnej został automatycznie odziedziczony. Wstaw treść " +"aby nadpisać odziedziczoną wartość." + +#: templates/admin/feincms/content_editor.html:33 +msgid "Add new item" +msgstr "Dodaj nowy element" + +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "Dodaj następny %(verbose_name)s" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "Usuń" + +#: templates/admin/feincms/fe_editor.html:30 +msgid "Save" +msgstr "Zachowaj" + +#: templates/admin/feincms/fe_tools.html:28 +msgid "Stop Editing" +msgstr "Zakończ Edycję" + +#: templates/admin/feincms/fe_tools.html:33 +msgid "edit" +msgstr "edytuj" + +#: templates/admin/feincms/fe_tools.html:35 +msgid "new" +msgstr "nowy" + +#: templates/admin/feincms/fe_tools.html:36 +msgid "up" +msgstr "góra" + +#: templates/admin/feincms/fe_tools.html:37 +msgid "down" +msgstr "dół" + +#: templates/admin/feincms/fe_tools.html:38 +msgid "remove" +msgstr "usuń" + +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "Edytuj na stronie" + +#: templates/admin/feincms/page/page/item_editor.html:23 +#: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 +msgid "Home" +msgstr "Główna" + +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "Dodaj" + +#: templates/admin/feincms/recover_form.html:11 +#, python-format +msgid "Recover deleted %(verbose_name)s" +msgstr "" + +#: templates/admin/feincms/recover_form.html:17 +msgid "Press the save button below to recover this version of the object." +msgstr "" + +#: templates/admin/feincms/revision_form.html:12 +msgid "History" +msgstr "Historia" + +#: templates/admin/feincms/revision_form.html:13 +#, python-format +msgid "Revert %(verbose_name)s" +msgstr "" + +#: templates/admin/feincms/revision_form.html:24 +msgid "Press the save button below to revert to this version of the object." +msgstr "" + +#: templates/admin/feincms/tree_editor.html:22 +msgid "Shortcuts" +msgstr "" + +#: templates/admin/feincms/tree_editor.html:24 +msgid "Collapse tree" +msgstr "Zwiń drzewo" + +#: templates/admin/feincms/tree_editor.html:25 +msgid "Expand tree" +msgstr "Rozwiń drzewo" + +#: templates/admin/feincms/tree_editor.html:29 +msgid "Filter" +msgstr "Filtruj" + +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr "Po %(filter_title)s " + +#: templates/admin/medialibrary/add_to_category.html:5 +#: templates/admin/medialibrary/add_to_category.html:9 +msgid "Add media files to category" +msgstr "Dodaj pliki media do kategorii" + +#: templates/admin/medialibrary/add_to_category.html:11 +msgid "Select category to apply:" +msgstr "Wybierz kategorię:" + +#: templates/admin/medialibrary/add_to_category.html:17 +msgid "The following media files will be added to the selected category:" +msgstr "Następujące pliki media zostaną dodane do wybranej kategorii:" + +#: templates/admin/medialibrary/add_to_category.html:22 +msgid "Add to category" +msgstr "Dodaj do kategorii" + +#: templates/admin/medialibrary/add_to_category.html:23 +msgid "Cancel" +msgstr "Anuluj" + +#: templates/admin/medialibrary/mediafile/change_list.html:11 +msgid "Bulk upload a ZIP file:" +msgstr "" + +#: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "Napisz" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 +msgid "Send" +msgstr "Wyślij" + +#: templates/content/comments/default.html:10 +#, python-format +msgid "%(comment_count)s comments." +msgstr "" + +#: templates/content/comments/default.html:18 +#, python-format +msgid "" +"\n" +" %(comment_username)s said on %(comment_submit_date)s
    \n" +" " +msgstr "" + +#: templates/content/comments/default.html:28 +msgid "No comments." +msgstr "Brak komentarzy." + +#: templates/content/comments/default.html:36 +msgid "Post Comment" +msgstr "" + +#: templates/content/contactform/form.html:9 +msgid "Submit" +msgstr "Wyślij" + +#: templates/content/contactform/thanks.html:3 +msgid "Thanks!" +msgstr "Dziękuję!" + +#~ msgid "plain" +#~ msgstr "zwyczajny" + +#~ msgid "title row" +#~ msgstr "tytuł" + +#~ msgid "table" +#~ msgstr "tablica" + +#~ msgid "tables" +#~ msgstr "tablice" + +#~ msgid "data" +#~ msgstr "data" + +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Zostanie dołączone z przodu listy tagów" + +#~ msgid "This will be prepended to the default description." +#~ msgstr "Zostanie dołączone z przodu listy tagów" diff --git a/feincms/locale/pt/LC_MESSAGES/django.mo b/feincms/locale/pt/LC_MESSAGES/django.mo index bc5942be3..2c8c9dd33 100644 Binary files a/feincms/locale/pt/LC_MESSAGES/django.mo and b/feincms/locale/pt/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/pt/LC_MESSAGES/django.po b/feincms/locale/pt/LC_MESSAGES/django.po index a30612be0..46674dcf0 100644 --- a/feincms/locale/pt/LC_MESSAGES/django.po +++ b/feincms/locale/pt/LC_MESSAGES/django.po @@ -1,208 +1,207 @@ # 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 , YEAR. # +# Translators: +# Vítor Figueiró , 2012 msgid "" msgstr "" -"Project-Id-Version: FienCMS\n" +"Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:19-0500\n" -"PO-Revision-Date: 2010-09-15 23:08+0100\n" -"Last-Translator: Vítor Figueiró \n" -"Language-Team: pt-PT \n" -"Language: \n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Matthias Kestenholz \n" +"Language-Team: Portuguese (http://www.transifex.com/projects/p/feincms/" +"language/pt/)\n" +"Language: pt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Translated-Using: django-rosetta 0.5.1\n" -"X-Poedit-Language: Portuguese\n" -"X-Poedit-SourceCharset: utf-8\n" -"X-Poedit-Country: PORTUGAL\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "modelo" - -#: models.py:553 -msgid "ordering" -msgstr "ordenação" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "idioma" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filterspecs.py:47 admin/filterspecs.py:86 msgid "All" msgstr "Todos" -#: admin/filterspecs.py:57 module/page/models.py:261 +#: admin/filterspecs.py:58 module/page/models.py:178 msgid "Parent" msgstr "Ascendente" -#: admin/filterspecs.py:95 +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 msgid "Category" msgstr "Categoria" -#: admin/item_editor.py:150 +#: admin/item_editor.py:190 #, python-format msgid "Change %s" msgstr "Modificar %s" -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:259 -#: module/page/models.py:338 +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 msgid "title" msgstr "título" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +#, fuzzy +#| msgid "You don't have the necessary permissions to edit this object" +msgid "You do not have permission to modify this object" +msgstr "Não possui as permissões necessárias para editar este objecto" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 #, python-format msgid "%s has been moved to a new position." msgstr "%s foi movido para uma nova posição" -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:512 msgid "Did not understand moving instruction." msgstr "A instrução para mover não foi entendida." -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:523 msgid "actions" msgstr "acções" -#: content/application/models.py:241 +#: admin/tree_editor.py:552 +#, fuzzy, python-format +#| msgid "Successfully added %(count)d media file to %(category)s." +#| msgid_plural "Successfully added %(count)d media files to %(category)s." +msgid "Successfully deleted %(count)d items." +msgstr "%(count)d ficheiro multimédia adicionado com sucesso a %(category)s." + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +#: content/application/models.py:147 msgid "application content" msgstr "conteúdo de aplicação" -#: content/application/models.py:242 +#: content/application/models.py:148 msgid "application contents" msgstr "conteúdos de aplicação" -#: content/application/models.py:273 +#: content/application/models.py:179 msgid "application" msgstr "aplicação" -#: content/comments/models.py:28 +#: content/comments/models.py:32 msgid "enabled" msgstr "habilitado" -#: content/comments/models.py:28 +#: content/comments/models.py:33 msgid "New comments may be added" msgstr "Podem ser adicionados novos comentários" -#: content/comments/models.py:32 content/comments/models.py:33 +#: content/comments/models.py:37 content/comments/models.py:38 msgid "comments" msgstr "comentários" -#: content/comments/models.py:48 +#: content/comments/models.py:60 msgid "public" msgstr "público" -#: content/comments/models.py:48 +#: content/comments/models.py:61 msgid "not public" msgstr "não público" -#: content/contactform/models.py:18 +#: content/contactform/models.py:20 msgid "name" msgstr "nome" -#: content/contactform/models.py:19 +#: content/contactform/models.py:21 msgid "email" msgstr "e-mail" -#: content/contactform/models.py:20 +#: content/contactform/models.py:22 msgid "subject" msgstr "assunto" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:26 content/raw/models.py:16 msgid "content" msgstr "conteúdo" -#: content/contactform/models.py:34 +#: content/contactform/models.py:37 msgid "contact form" msgstr "formulário de contacto" -#: content/contactform/models.py:35 +#: content/contactform/models.py:38 msgid "contact forms" msgstr "formulários de contacto" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 msgid "file" msgstr "ficheiro" -#: content/file/models.py:21 +#: content/file/models.py:29 msgid "files" msgstr "ficheiros" -#: content/image/models.py:24 content/image/models.py:28 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "imagem" -#: content/image/models.py:29 +#: content/image/models.py:49 +msgid "alternate text" +msgstr "texto alternativo" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "Descrição da imagem" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "legenda" + +#: content/image/models.py:56 msgid "images" msgstr "imagens" -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 +#: content/image/models.py:82 msgid "position" msgstr "posição" -#: content/medialibrary/models.py:38 -msgid "(no caption)" -msgstr "(sem legenda)" +#: content/image/models.py:90 +msgid "format" +msgstr "formato" -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 msgid "media file" -msgstr "ficheiro de mídia" +msgstr "ficheiro multimédia" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 msgid "media files" -msgstr "ficheiros de mídia" - -#: content/medialibrary/models.py:131 -msgid "block" -msgstr "bloco" - -#: content/medialibrary/models.py:132 -msgid "left" -msgstr "esquerda" +msgstr "ficheiros multimédia" -#: content/medialibrary/models.py:133 -msgid "right" -msgstr "direita" - -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: content/medialibrary/models.py:64 content/section/models.py:59 msgid "type" msgstr "tipo" -#: content/raw/models.py:18 +#: content/raw/models.py:20 msgid "raw content" msgstr "conteúdo cru" -#: content/raw/models.py:19 +#: content/raw/models.py:21 msgid "raw contents" msgstr "conteúdos crus" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "texto" - -#: content/richtext/models.py:26 +#: content/richtext/models.py:24 msgid "HTML Tidy" msgstr "HTML Tidy" -#: content/richtext/models.py:27 +#: content/richtext/models.py:25 msgid "Ignore the HTML validation warnings" msgstr "Ignorar os avisos de validação do HTML" -#: content/richtext/models.py:51 +#: content/richtext/models.py:55 #, python-format msgid "" "HTML validation produced %(count)d warnings. Please review the updated " @@ -211,15 +210,19 @@ msgstr "" "A validação do HTML produziu %(count)d avisos. Por favor reveja o conteúdo " "actualizado em baixo antes de continuar: %(messages)s " -#: content/richtext/models.py:89 +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "texto" + +#: content/richtext/models.py:103 msgid "rich text" msgstr "texto rico" -#: content/richtext/models.py:90 +#: content/richtext/models.py:104 msgid "rich texts" msgstr "textos ricos" -#: content/rss/models.py:21 +#: content/rss/models.py:25 msgid "" "The rss field is updated several times a day. A change in the title will " "only be visible on the home page after the next feed update." @@ -227,494 +230,556 @@ msgstr "" "O feed RSS é actualizado várias vezes ao dia. Uma alteração do título só " "aparece no site após a actualização do feed RSS seguinte." -#: content/rss/models.py:22 +#: content/rss/models.py:28 msgid "link" msgstr "ligação" -#: content/rss/models.py:23 +#: content/rss/models.py:30 msgid "pre-rendered content" msgstr "conteúdo pré-renderizado" -#: content/rss/models.py:24 +#: content/rss/models.py:32 msgid "last updated" msgstr "última actualização" -#: content/rss/models.py:25 +#: content/rss/models.py:33 msgid "max. items" msgstr "número máximo" -#: content/rss/models.py:29 +#: content/rss/models.py:37 msgid "RSS feed" -msgstr "alimentação RSS" +msgstr "feed RSS" -#: content/rss/models.py:30 +#: content/rss/models.py:38 msgid "RSS feeds" -msgstr "alimentações RSS" +msgstr "feed RSS" -#: content/section/models.py:37 +#: content/section/models.py:43 msgid "section" msgstr "secção" -#: content/section/models.py:38 +#: content/section/models.py:44 msgid "sections" msgstr "secções" -#: content/table/models.py:62 -msgid "plain" -msgstr "simples" - -#: content/table/models.py:63 -msgid "title row" -msgstr "linha de título" - -#: content/table/models.py:65 -msgid "title row and column" -msgstr "linha de título e coluna" - -#: content/table/models.py:71 -msgid "table" -msgstr "tabela" - -#: content/table/models.py:72 -msgid "tables" -msgstr "tabelas" - -#: content/table/models.py:86 -msgid "data" -msgstr "dados" - -#: content/template/models.py:62 +#: content/template/models.py:53 msgid "template content" -msgstr "conteúdo de template" +msgstr "conteúdo do modelo" -#: content/template/models.py:63 +#: content/template/models.py:54 msgid "template contents" msgstr "conteúdos de template" -#: content/video/models.py:23 +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "modelo" + +#: content/video/models.py:34 msgid "video link" msgstr "ligação de vídeo" -#: content/video/models.py:24 +#: content/video/models.py:36 msgid "" "This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." "com/watch?v=zmj1rpzDRZ0" msgstr "" -"Isto deve ser uma ligação par um vídeo do Youtube ou do vimeo, p.ex.: http://" -"www.youtube.com/watch?v=zmj1rpzDRZ0" +"Isto deve ser uma ligação para um vídeo do Youtube ou do vimeo, p.ex.: " +"http://www.youtube.com/watch?v=zmj1rpzDRZ0" -#: content/video/models.py:28 +#: content/video/models.py:41 msgid "video" msgstr "vídeo" -#: content/video/models.py:29 +#: content/video/models.py:42 msgid "videos" msgstr "vídeos" -#: module/blog/models.py:31 +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "Tagging" + +#: models.py:550 +msgid "ordering" +msgstr "ordenação" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "etiquetas" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "idioma" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "tradução de" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "Deixe este campo em branco nas entradas no idioma original." + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "traduções disponíveis" + +#: module/blog/models.py:33 msgid "published" msgstr "publicado" -#: module/blog/models.py:33 +#: module/blog/models.py:36 msgid "This is used for the generated navigation too." msgstr "Isto também é usado para a navegação gerada automaticamente." -#: module/blog/models.py:36 +#: module/blog/models.py:40 msgid "published on" msgstr "publicado em" -#: module/blog/models.py:37 +#: module/blog/models.py:42 msgid "Will be set automatically once you tick the `published` checkbox above." msgstr "" -"É definido automaticamente quando activa a caixa de verificação 'publicado' " -"acima." +"Será definido automaticamente quando activar a caixa de verificação " +"'publicado' acima." -#: module/blog/models.py:42 +#: module/blog/models.py:48 msgid "entry" msgstr "entrada" -#: module/blog/models.py:43 +#: module/blog/models.py:49 msgid "entries" msgstr "entradas" -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "etiquetas" - -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "tradução de" - -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -msgid "Leave this empty for entries in the primary language." -msgstr "Deixe este campo em branco nas entradas do idioma original." - -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" -msgstr "traduções disponíveis" - -#: module/extensions/changedate.py:38 +#: module/extensions/changedate.py:41 msgid "creation date" msgstr "data de criação" -#: module/extensions/changedate.py:39 +#: module/extensions/changedate.py:43 msgid "modification date" msgstr "data de modificação" -#: module/extensions/ct_tracker.py:134 +#: module/extensions/ct_tracker.py:156 msgid "content types" msgstr "tipos de conteúdo" -#: module/extensions/featured.py:9 +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "data de publicação" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "publicar até" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "Deixe vazio se a entrada deve permanecer activa para sempre." + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "visível de - até" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "Publicação baseada em datas" + +#: module/extensions/featured.py:15 msgid "featured" -msgstr "recomendado" +msgstr "em destaque" -#: module/extensions/featured.py:14 +#: module/extensions/featured.py:21 msgid "Featured" -msgstr "Recomendado" +msgstr "Em destaque" -#: module/extensions/seo.py:9 +#: module/extensions/seo.py:16 msgid "meta keywords" msgstr "palavras-chave meta" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "Isto será inserido antes da lista de palavras-chave padrão." +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" -#: module/extensions/seo.py:11 +#: module/extensions/seo.py:20 msgid "meta description" msgstr "descrição meta" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." -msgstr "Isto será inserido antes da descrição padrão." +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" -#: module/extensions/seo.py:18 +#: module/extensions/seo.py:32 msgid "Search engine optimization" msgstr "Optimização para motor de busca" -#: module/medialibrary/models.py:51 +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "Editar a tradução" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Criar tradução" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "traduções" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "Isto criaria um ciclo na hierarquia" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" +"Não é possível sobrescrever com um tipo de ficheiro diferente (tentativa de " +"substituir um %(old_ext)s com um %(new_ext)s)" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "" +"%(count)d ficheiro multimédia adicionado com sucesso a %(category)s." +msgstr[1] "" +"%(count)d ficheiros multimédia adicionados com sucesso a %(category)s." + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "Adicionar os ficheiros multimédia seleccionados à categoria" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "Ficheiro ZIP exportado como %s" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "A exportação do ficheiro ZIP falhou: %s" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "Exportar os ficheiros multimédia seleccionados como ficheiro zip" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "Antevisão" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "tamanho do ficheiro" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "criado em" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "tipo de ficheiro" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "informação do ficheiro" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "%d ficheiros importados" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "A importação do ficheiro ZIP falhou: %s" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "Não indicou o ficheiro de origem" + +#: module/medialibrary/models.py:49 msgid "parent" msgstr "ascendente" -#: module/medialibrary/models.py:53 module/page/models.py:260 +#: module/medialibrary/models.py:51 module/page/models.py:175 msgid "slug" msgstr "slug" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:55 msgid "category" msgstr "categoria" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 msgid "categories" msgstr "categorias" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" -msgstr "tipo de ficheiro" - -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" -msgstr "criado em" - -#: module/medialibrary/models.py:93 +#: module/medialibrary/models.py:101 msgid "copyright" msgstr "copyright" -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 -msgid "file size" -msgstr "tamanho do ficheiro" - -#: module/medialibrary/models.py:203 -msgid "file info" -msgstr "inform. do ficheiro" - -#: module/medialibrary/models.py:280 +#: module/medialibrary/models.py:219 msgid "Image" msgstr "Imagem" -#: module/medialibrary/models.py:281 +#: module/medialibrary/models.py:221 msgid "Video" msgstr "Vídeo" -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:224 msgid "Audio" msgstr "Áudio" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:226 msgid "PDF document" -msgstr "documento PDF" +msgstr "Documento PDF" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:227 msgid "Flash" msgstr "Flash" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:228 msgid "Text" msgstr "Texto" -#: module/medialibrary/models.py:286 +#: module/medialibrary/models.py:229 msgid "Rich Text" msgstr "Texto Rico" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:230 msgid "Zip archive" -msgstr "" +msgstr "Ficheiro zip" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:231 msgid "Microsoft Word" msgstr "Microsoft Word" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:233 msgid "Microsoft Excel" msgstr "Microsoft Excel" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:235 msgid "Microsoft PowerPoint" msgstr "Microsoft PowerPoint" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:237 msgid "Binary" msgstr "Binário" -#: module/medialibrary/models.py:307 -msgid "caption" -msgstr "legenda" - -#: module/medialibrary/models.py:308 +#: module/medialibrary/models.py:261 msgid "description" msgstr "descrição" -#: module/medialibrary/models.py:311 +#: module/medialibrary/models.py:264 msgid "media file translation" -msgstr "tradução do ficheiro de mídia" +msgstr "tradução do ficheiro multimédia" -#: module/medialibrary/models.py:312 +#: module/medialibrary/models.py:265 msgid "media file translations" -msgstr "traduções do ficheiro de mídia" +msgstr "traduções do ficheiro multimédia" -#: module/medialibrary/models.py:335 -msgid "Preview" -msgstr "Pré-visualização" +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" +msgstr "excerto" -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." +msgstr "Adicione um breve excerto que resuma o conteúdo desta página." -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" -msgstr "" +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" +msgstr "Excerto" -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" -msgstr "" +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 +msgid "navigation extension" +msgstr "extensão de navegação" -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" +#: module/page/extensions/navigation.py:119 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." msgstr "" +"Seleccione o módulo que providencia as sub-páginas, caso necessite de " +"personalizar a navegação." -#: module/medialibrary/models.py:448 -msgid "No input file given" +#: module/page/extensions/navigation.py:134 +msgid "Navigation extension" +msgstr "Extensão de navegação" + +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" msgstr "" -#: module/page/models.py:256 -msgid "active" -msgstr "activo" +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" -#: module/page/models.py:263 module/page/models.py:711 -msgid "in navigation" +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" msgstr "na navegação" -#: module/page/models.py:264 -msgid "override URL" -msgstr "URL efectivo" +#: module/page/extensions/relatedpages.py:21 +msgid "Select pages that should be listed as related content." +msgstr "Seleccione páginas a listar como conteúdo relacionado." -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." -msgstr "" -"URL efectivo. Deve conter uma /, caso se trate de um URL local. Este campo " -"determina a navegação e os URL's das sub-páginas." +#: module/page/extensions/relatedpages.py:26 +msgid "Related pages" +msgstr "Páginas relacionadas" -#: module/page/models.py:266 -msgid "redirect to" -msgstr "redireccionar para" +#: module/page/extensions/sites.py:21 +msgid "Site" +msgstr "Site" -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." -msgstr "URL de destino para redireccionamentos automáticos." +#: module/page/extensions/symlinks.py:22 +msgid "symlinked page" +msgstr "página ligada" -#: module/page/models.py:268 -msgid "Cached URL" -msgstr "URL em cache" +#: module/page/extensions/symlinks.py:23 +msgid "All content is inherited from this page if given." +msgstr "O conteúdo é herdado desta página." -#: module/page/models.py:279 -msgid "page" -msgstr "página" +#: module/page/extensions/titles.py:19 +msgid "content title" +msgstr "título do conteúdo" -#: module/page/models.py:280 -msgid "pages" -msgstr "páginas" +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." +msgstr "" +"A primeira linha é o título principal. Outras linhas serão sub-títulos." -#: module/page/models.py:297 module/page/models.py:782 -msgid "is active" -msgstr "activo" +#: module/page/extensions/titles.py:26 +msgid "page title" +msgstr "título da página" -#: module/page/models.py:631 +#: module/page/extensions/titles.py:30 +#, fuzzy +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." +msgstr "" +"Título da página para a janela do navegador. Se omitido assume o título." + +#: module/page/extensions/titles.py:60 +msgid "Titles" +msgstr "Títulos" + +#: module/page/forms.py:187 msgid "This URL is already taken by an active page." -msgstr "Esta URL já está tomada por uma página activa." +msgstr "Este URL já está tomado por uma página activa." -#: module/page/models.py:649 +#: module/page/forms.py:206 msgid "This URL is already taken by another active page." -msgstr "Esta URL já está tomada por uma outra página activa." +msgstr "Este URL já está tomado por uma outra página activa." + +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" -#: module/page/models.py:674 +#: module/page/modeladmins.py:42 msgid "Other options" msgstr "outras opções" -#: module/page/models.py:721 +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "na navegação" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 msgid "Add child page" msgstr "Adicionar página descendente" -#: module/page/models.py:723 +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 msgid "View on site" msgstr "Ver no site" -#: module/page/models.py:759 -msgid "You don't have the necessary permissions to edit this object" +#: module/page/modeladmins.py:142 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" msgstr "" -#: module/page/models.py:774 +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "" +"O conteúdo da tradução original foi copiado para a página recém-criada." + +#: module/page/modeladmins.py:197 +msgid "You don't have the necessary permissions to edit this object" +msgstr "Não possui as permissões necessárias para editar este objecto" + +#: module/page/modeladmins.py:223 msgid "inherited" msgstr "herdado" -#: module/page/models.py:778 +#: module/page/modeladmins.py:229 msgid "extensions" msgstr "extensões" -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "data de publicação" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "publicar até" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "Deixe vazio se a entrada deve permanecer activa para sempre." - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "visível de - até" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" -msgstr "Publicação baseada em datas" +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" +msgstr "activo" -#: module/page/extensions/excerpt.py:9 -msgid "excerpt" -msgstr "excerto" +#: module/page/models.py:169 +msgid "active" +msgstr "activo" -#: module/page/extensions/excerpt.py:10 -msgid "Add a brief excerpt summarizing the content of this page." -msgstr "Adicione um breve excerto que resuma o conteúdo desta página." +#: module/page/models.py:173 +#, fuzzy +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "Isto também é usado para a navegação gerada automaticamente." -#: module/page/extensions/excerpt.py:12 -msgid "Excerpt" -msgstr "Excerto" +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" +msgstr "" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 -msgid "navigation extension" -msgstr "extensão de navegação" +#: module/page/models.py:184 +msgid "override URL" +msgstr "URL efectivo" -#: module/page/extensions/navigation.py:99 +#: module/page/models.py:186 msgid "" -"Select the module providing subpages for this page if you need to customize " -"the navigation." -msgstr "Seleccione o módulo que providencia sub-páginas." - -#: module/page/extensions/navigation.py:112 -msgid "Navigation extension" -msgstr "Extensão de navegação" - -#: module/page/extensions/relatedpages.py:13 -msgid "Select pages that should be listed as related content." -msgstr "Seleccione páginas a listar como conteúdo relacionado." - -#: module/page/extensions/relatedpages.py:20 -msgid "Related pages" -msgstr "Páginas relacionadas" - -#: module/page/extensions/sites.py:16 -msgid "Site" +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." msgstr "" +"URL efectivo. Deve conter uma / no início e no fim, caso se trate de um URL " +"local. Este campo determina a navegação e os URL's das sub-páginas." -#: module/page/extensions/symlinks.py:15 -msgid "symlinked page" -msgstr "página ligada" - -#: module/page/extensions/symlinks.py:16 -msgid "All content is inherited from this page if given." -msgstr "O conteúdo é herdado desta página." - -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" -msgstr "Página ligada" - -#: module/page/extensions/titles.py:13 -msgid "content title" -msgstr "título do conteúdo" +#: module/page/models.py:190 +msgid "redirect to" +msgstr "redireccionar para" -#: module/page/extensions/titles.py:14 -msgid "The first line is the main title, the following lines are subtitles." +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." msgstr "" -"A primeira linha é o título principal. Outras linhas serão sub-títulos." -#: module/page/extensions/titles.py:15 -msgid "page title" -msgstr "título da página" +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "URL em cache" -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." +#: module/page/models.py:298 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" msgstr "" -"Título da página para a janela do navegador. Se omitido assume o título." - -#: module/page/extensions/titles.py:43 -msgid "Titles" -msgstr "Títulos" - -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "Editar a tradução" - -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "Criar tradução" - -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "traduções" -#: templates/admin/filter.html:3 -#, python-format -msgid " By %(filter_title)s " -msgstr "Por %(filter_title)s" +#: module/page/models.py:426 +msgid "page" +msgstr "página" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "Procurar" +#: module/page/models.py:427 +msgid "pages" +msgstr "páginas" #: templates/admin/feincms/_messages_js.html:4 msgid "Really delete item?" @@ -749,11 +814,8 @@ msgstr "" #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s is moved to %%(target_region)s." msgstr "" -"Confirma que deseja alterar o modelo?
    Todas as alterações serão " -"guardadas e o conteúdo de %(source_regions)s será movido " -"para %(target_region)s." #: templates/admin/feincms/_messages_js.html:12 msgid "Hide" @@ -775,129 +837,152 @@ msgstr "Antes" msgid "Insert new:" msgstr "Inserir novo:" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "Região vazia" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." msgstr "O conteúdo é herdado da página ascendente, excepto se o indicar aqui." -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "Adicionar novo item" -#: templates/admin/feincms/fe_editor.html:46 +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "Adicionar outro %(verbose_name)s" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "Remover" + +#: templates/admin/feincms/fe_editor.html:30 msgid "Save" msgstr "Guardar" -#: templates/admin/feincms/fe_tools.html:27 +#: templates/admin/feincms/fe_tools.html:28 msgid "Stop Editing" msgstr "Parar de Editar" -#: templates/admin/feincms/fe_tools.html:32 +#: templates/admin/feincms/fe_tools.html:33 msgid "edit" msgstr "editar" -#: templates/admin/feincms/fe_tools.html:34 +#: templates/admin/feincms/fe_tools.html:35 msgid "new" msgstr "novo" -#: templates/admin/feincms/fe_tools.html:35 +#: templates/admin/feincms/fe_tools.html:36 msgid "up" msgstr "para cima" -#: templates/admin/feincms/fe_tools.html:36 +#: templates/admin/feincms/fe_tools.html:37 msgid "down" msgstr "para baixo" -#: templates/admin/feincms/fe_tools.html:37 +#: templates/admin/feincms/fe_tools.html:38 msgid "remove" msgstr "remover" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:16 +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "Editar no site" + +#: templates/admin/feincms/page/page/item_editor.html:23 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 msgid "Home" msgstr "Página inicial" -#: templates/admin/feincms/recover_form.html:10 +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "Adicionar" + +#: templates/admin/feincms/recover_form.html:11 #, python-format msgid "Recover deleted %(verbose_name)s" -msgstr "" +msgstr "Recuperar %(verbose_name)s excluído" #: templates/admin/feincms/recover_form.html:17 msgid "Press the save button below to recover this version of the object." msgstr "" +"Clique no botão 'Guardar' em baixo para recuperar esta versão do objecto." -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:12 msgid "History" -msgstr "" +msgstr "Historial" -#: templates/admin/feincms/revision_form.html:15 +#: templates/admin/feincms/revision_form.html:13 #, python-format msgid "Revert %(verbose_name)s" -msgstr "" +msgstr "Reverter %(verbose_name)s" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:24 msgid "Press the save button below to revert to this version of the object." msgstr "" +"Clique no botão 'Guardar' em baixo para reverter para esta versão do objecto." -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "Atalhos" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" msgstr "Colapsar a árvore" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" msgstr "Expandir a árvore" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "Filtrar" -#: templates/admin/feincms/page/page/item_editor.html:9 -msgid "Edit on site" -msgstr "Editar no site" - -#: templates/admin/feincms/page/page/item_editor.html:20 -msgid "Add" -msgstr "" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr "Por %(filter_title)s" #: templates/admin/medialibrary/add_to_category.html:5 #: templates/admin/medialibrary/add_to_category.html:9 -#, fuzzy msgid "Add media files to category" -msgstr "tradução do ficheiro de mídia" +msgstr "Adicionar ficheiros multimédia à categoria" #: templates/admin/medialibrary/add_to_category.html:11 msgid "Select category to apply:" -msgstr "" +msgstr "Seleccione a categoria a aplicar:" #: templates/admin/medialibrary/add_to_category.html:17 msgid "The following media files will be added to the selected category:" msgstr "" +"Os seguintes ficheiros multimédia serão adicionados à categoria seleccionada:" #: templates/admin/medialibrary/add_to_category.html:22 -#, fuzzy msgid "Add to category" -msgstr "categoria" +msgstr "Adicionar à categoria" #: templates/admin/medialibrary/add_to_category.html:23 msgid "Cancel" -msgstr "" +msgstr "Cancelar" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:11 msgid "Bulk upload a ZIP file:" msgstr "Enviar um ficheiro ZIP:" #: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "Sobrescrever" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 msgid "Send" msgstr "Enviar" @@ -935,158 +1020,26 @@ msgstr "Enviar" msgid "Thanks!" msgstr "Obrigado!" -#~ msgid "rich text (ckeditor)" -#~ msgstr "texto rico (ckeditor)" - -#~ msgid "rich texts (ckeditor)" -#~ msgstr "textos ricos (ckeditor)" - -#~ msgid "You may edit the copied page below." -#~ msgstr "Pode editar a página copiada em baixo." - -#~ msgid "" -#~ "You have replaced %s. You may continue editing the now-active page below." -#~ msgstr "" -#~ "Substituiu %s. Pode continuar a editar a página agora activa em baixo." - -#~ msgid "Move to" -#~ msgstr "Mover para" - -#~ msgid "Move" -#~ msgstr "Mover" - -#~ msgid "Add %(name)s" -#~ msgstr "Adicionar %(name)s" - -#~ msgid "Select an item on the left side if you want to edit it." -#~ msgstr "Seleccione um item do lado esquerdo, se o quiser editar." - -#~ msgid "" -#~ "You can change the structure of the tree by drag-dropping elements. " -#~ "Please note that changes will be saved immediately. " -#~ msgstr "" -#~ "Pode alterar a estrutura da árvore arrastando elementos. Note que as " -#~ "alterações serão guardadas imediatamente." - -#~ msgid "" -#~ "The context menu on the tree root and tree nodes provide you with " -#~ "additional modes of operation." -#~ msgstr "" -#~ "O menu de contexto na raiz da árvore e nos nós proporciona-lhe modos " -#~ "adicionais de operação." - -#~ msgid "Cannot remove this frame while inside this admin section." -#~ msgstr "" -#~ "Não pode remover esta moldura enquanto estiver dentro desta secção de " -#~ "admin." - -#~ msgid "Reload" -#~ msgstr "Recarregar" - -#~ msgid "Edit" -#~ msgstr "Editar" - -#~ msgid "Delete" -#~ msgstr "Eliminar" - -#~ msgid "Replace page %(to_replace)s" -#~ msgstr "Substituir a página %(to_replace)s" - -#~ msgid "Create hidden copy of this page" -#~ msgstr "Criar uma cópia oculta desta página" - -#~ msgid "Click save to replace the current content with this version" -#~ msgstr "Clique em guardar para substituir o conteúdo actual por esta versão" - -#~ msgid "The %(name)s \"%(obj)s\" was changed successfully." -#~ msgstr "%(name)s \"%(obj)s\" foi modificado com sucesso." - -#~ msgid "You may edit it again below." -#~ msgstr "Pode voltar a editar o elemento." - -#~ msgid "You may add another %s below." -#~ msgstr "Pode adicionar um novo %s." - -#~ msgid "Cut" -#~ msgstr "Cortar" - -#~ msgid "Insert as child" -#~ msgstr "Inserir como descendente" - -#~ msgid "Insert before" -#~ msgstr "Inserir antes" - -#~ msgid "Video from unknown portal" -#~ msgstr "Vídeo de um portal desconhecido" - -#~ msgid "Tree saved successfully." -#~ msgstr "Árvore guardada com sucesso." - -#~ msgid "Cannot make a node a child of itself." -#~ msgstr "Um nó não pode ser dependente de si próprio." - -#~ msgid "No item has been selected." -#~ msgstr "Não seleccionou nenhum item." - -#~ msgid "Add Child Page" -#~ msgstr "Adicionar Página Descendente" - -#~ msgid "Save and add another" -#~ msgstr "Guardar e adicionar outro" - -#~ msgid "Save and continue editing" -#~ msgstr "Guardar e continuar a editar" - -#~ msgid "Please correct the error below." -#~ msgid_plural "Please correct the errors below." -#~ msgstr[0] "Por favor corrija o erro em baixo." -#~ msgstr[1] "Por favor corrija os erros em baixo." - -#~ msgid "Properties" -#~ msgstr "Propriedades" - -#~ msgid "Move selected item to" -#~ msgstr "Mover item seleccionado para" - -#~ msgid "OK" -#~ msgstr "OK" - -#~ msgid "is visible" -#~ msgstr "é visível" - -#~ msgid "Database error" -#~ msgstr "Erro de base de dados" - -#~ msgid "view content" -#~ msgstr "conteúdo da função" - -#~ msgid "" -#~ "Could not parse the view content because the view is excluded from " -#~ "infanta handling." -#~ msgstr "" -#~ "Não foi possível ler o conteúdo da vista, porque esta está excluída do " -#~ "tratamento de infanta." - -#~ msgid "Placeholder for the %(viewname)s calling %(viewfunc)s" -#~ msgstr "Substituto para %(viewname)s, que chama %(viewfunc)s" +#~ msgid "plain" +#~ msgstr "simples" -#~ msgid "Placeholder for calling %(viewfunc)s" -#~ msgstr "Substituto para chamar %(viewfunc)s" +#~ msgid "title row" +#~ msgstr "linha de título" -#~ msgid "not active" -#~ msgstr "inactivo" +#~ msgid "title row and column" +#~ msgstr "linha e coluna de título" -#~ msgid "Save tree" -#~ msgstr "Guardar árvore" +#~ msgid "table" +#~ msgstr "tabela" -#~ msgid "%(icon)s (not active)" -#~ msgstr "%(icon)s (inactivo)" +#~ msgid "tables" +#~ msgstr "tabelas" -#~ msgid "%(icon)s (until %(from)s)" -#~ msgstr "%(icon)s (até %(from)s)" +#~ msgid "data" +#~ msgstr "dados" -#~ msgid "%(icon)s (since %(to)s)" -#~ msgstr "%(icon)s (a partir de %(to)s)" +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Isto será inserido antes da lista de palavras-chave padrão." -#~ msgid "%(icon)s (%(from)s – %(to)s)" -#~ msgstr "%(icon)s (%(from)s – %(to)s)" +#~ msgid "This will be prepended to the default description." +#~ msgstr "Isto será inserido antes da descrição padrão." diff --git a/feincms/locale/pt/LC_MESSAGES/djangojs.po b/feincms/locale/pt/LC_MESSAGES/djangojs.po index 77851f198..f15fdb2b2 100644 --- a/feincms/locale/pt/LC_MESSAGES/djangojs.po +++ b/feincms/locale/pt/LC_MESSAGES/djangojs.po @@ -2,7 +2,7 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" diff --git a/feincms/locale/pt_BR/LC_MESSAGES/django.mo b/feincms/locale/pt_BR/LC_MESSAGES/django.mo index a6129dbd7..f37d29a51 100644 Binary files a/feincms/locale/pt_BR/LC_MESSAGES/django.mo and b/feincms/locale/pt_BR/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/pt_BR/LC_MESSAGES/django.po b/feincms/locale/pt_BR/LC_MESSAGES/django.po index 384bfe3e0..6b2e73258 100644 --- a/feincms/locale/pt_BR/LC_MESSAGES/django.po +++ b/feincms/locale/pt_BR/LC_MESSAGES/django.po @@ -1,740 +1,803 @@ # 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 , YEAR. # +# Translators: +# Guilherme Gondim , 2012 msgid "" msgstr "" -"Project-Id-Version: FienCMS\n" +"Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:19-0500\n" -"PO-Revision-Date: 2010-09-15 23:34+0100\n" -"Last-Translator: Vítor Figueiró \n" -"Language-Team: pt-BR \n" -"Language: \n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Matthias Kestenholz \n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/" +"feincms/language/pt_BR/)\n" +"Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Poedit-Language: Portuguese\n" -"X-Poedit-Country: BRAZIL\n" -"X-Poedit-SourceCharset: utf-8\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "modelo" - -#: models.py:553 -msgid "ordering" -msgstr "ordem" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "idioma" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filterspecs.py:47 admin/filterspecs.py:86 msgid "All" msgstr "Todos" -#: admin/filterspecs.py:57 module/page/models.py:261 +#: admin/filterspecs.py:58 module/page/models.py:178 msgid "Parent" msgstr "Ascendente" -#: admin/filterspecs.py:95 +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 msgid "Category" msgstr "Categoria" -#: admin/item_editor.py:150 +#: admin/item_editor.py:190 #, python-format msgid "Change %s" msgstr "Modificar %s" -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:259 -#: module/page/models.py:338 +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 msgid "title" msgstr "título" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +#, fuzzy +#| msgid "You don't have the necessary permissions to edit this object" +msgid "You do not have permission to modify this object" +msgstr "Você não tem as permissões necessárias para editar este objeto" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 #, python-format msgid "%s has been moved to a new position." -msgstr "%s foi movido para uma nova posição" +msgstr "%s foi movido para uma nova posição." -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:512 msgid "Did not understand moving instruction." msgstr "A instrução para mover não foi entendida." -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:523 msgid "actions" msgstr "ações" -#: content/application/models.py:241 +#: admin/tree_editor.py:552 +#, fuzzy, python-format +#| msgid "Successfully added %(count)d media file to %(category)s." +#| msgid_plural "Successfully added %(count)d media files to %(category)s." +msgid "Successfully deleted %(count)d items." +msgstr "Adicionado com sucesso %(count)d arquivo de mídia em %(category)s." + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +#: content/application/models.py:147 msgid "application content" msgstr "conteúdo de aplicação" -#: content/application/models.py:242 +#: content/application/models.py:148 msgid "application contents" msgstr "conteúdos de aplicação" -#: content/application/models.py:273 +#: content/application/models.py:179 msgid "application" msgstr "aplicação" -#: content/comments/models.py:28 +#: content/comments/models.py:32 msgid "enabled" msgstr "habilitado" -#: content/comments/models.py:28 +#: content/comments/models.py:33 msgid "New comments may be added" msgstr "Podem ser adicionados novos comentários" -#: content/comments/models.py:32 content/comments/models.py:33 +#: content/comments/models.py:37 content/comments/models.py:38 msgid "comments" msgstr "comentários" -#: content/comments/models.py:48 +#: content/comments/models.py:60 msgid "public" msgstr "público" -#: content/comments/models.py:48 +#: content/comments/models.py:61 msgid "not public" msgstr "não público" -#: content/contactform/models.py:18 +#: content/contactform/models.py:20 msgid "name" msgstr "nome" -#: content/contactform/models.py:19 +#: content/contactform/models.py:21 msgid "email" msgstr "e-mail" -#: content/contactform/models.py:20 +#: content/contactform/models.py:22 msgid "subject" msgstr "assunto" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:26 content/raw/models.py:16 msgid "content" msgstr "conteúdo" -#: content/contactform/models.py:34 +#: content/contactform/models.py:37 msgid "contact form" msgstr "formulário de contato" -#: content/contactform/models.py:35 +#: content/contactform/models.py:38 msgid "contact forms" msgstr "formulários de contato" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 msgid "file" msgstr "arquivo" -#: content/file/models.py:21 +#: content/file/models.py:29 msgid "files" msgstr "arquivos" -#: content/image/models.py:24 content/image/models.py:28 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "imagem" -#: content/image/models.py:29 +#: content/image/models.py:49 +msgid "alternate text" +msgstr "" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "legenda" + +#: content/image/models.py:56 msgid "images" msgstr "imagens" -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 +#: content/image/models.py:82 msgid "position" msgstr "posição" -#: content/medialibrary/models.py:38 -msgid "(no caption)" -msgstr "(sem legenda)" +#: content/image/models.py:90 +msgid "format" +msgstr "" -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 msgid "media file" msgstr "arquivo de mídia" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 msgid "media files" msgstr "arquivos de mídia" -#: content/medialibrary/models.py:131 -msgid "block" -msgstr "bloco" - -#: content/medialibrary/models.py:132 -msgid "left" -msgstr "esquerda" - -#: content/medialibrary/models.py:133 -msgid "right" -msgstr "direita" - -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: content/medialibrary/models.py:64 content/section/models.py:59 msgid "type" msgstr "tipo" -#: content/raw/models.py:18 +#: content/raw/models.py:20 msgid "raw content" msgstr "conteúdo cru" -#: content/raw/models.py:19 +#: content/raw/models.py:21 msgid "raw contents" msgstr "conteúdos crus" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "texto" - -#: content/richtext/models.py:26 +#: content/richtext/models.py:24 msgid "HTML Tidy" msgstr "HTML Tidy" -#: content/richtext/models.py:27 +#: content/richtext/models.py:25 msgid "Ignore the HTML validation warnings" msgstr "Ignorar os avisos de validação HTML" -#: content/richtext/models.py:51 +#: content/richtext/models.py:55 #, python-format msgid "" "HTML validation produced %(count)d warnings. Please review the updated " "content below before continuing: %(messages)s" msgstr "" -"A validação HTML produziu %(count)d avisos. Por favor reveja o conteúdo " -"atualizado em baixo antes de continuar: %(messages)s" +"A validação HTML produziu %(count)d avisos. Por favor, revise o conteúdo " +"atualizado abaixo antes de continuar: %(messages)s" + +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "texto" -#: content/richtext/models.py:89 +#: content/richtext/models.py:103 msgid "rich text" msgstr "texto rico" -#: content/richtext/models.py:90 +#: content/richtext/models.py:104 msgid "rich texts" msgstr "textos ricos" -#: content/rss/models.py:21 +#: content/rss/models.py:25 msgid "" "The rss field is updated several times a day. A change in the title will " "only be visible on the home page after the next feed update." msgstr "" -"O feed RSS é atualizado várias vezes por dia. Uma alteração do título só " -"aparece no site após a atualização de feed RSS seguinte." +"O feed RSS é atualizado por várias vezes ao dia. Uma alteração do título só " +"será visível na página após a próxima atualização do feed." -#: content/rss/models.py:22 +#: content/rss/models.py:28 msgid "link" msgstr "ligação" -#: content/rss/models.py:23 +#: content/rss/models.py:30 msgid "pre-rendered content" msgstr "conteúdo pré-renderizado" -#: content/rss/models.py:24 +#: content/rss/models.py:32 msgid "last updated" msgstr "última atualização" -#: content/rss/models.py:25 +#: content/rss/models.py:33 msgid "max. items" msgstr "número máximo" -#: content/rss/models.py:29 +#: content/rss/models.py:37 msgid "RSS feed" msgstr "feed RSS" -#: content/rss/models.py:30 +#: content/rss/models.py:38 msgid "RSS feeds" msgstr "feeds RSS" -#: content/section/models.py:37 +#: content/section/models.py:43 msgid "section" -msgstr "secção" +msgstr "seção" -#: content/section/models.py:38 +#: content/section/models.py:44 msgid "sections" -msgstr "secções" - -#: content/table/models.py:62 -msgid "plain" -msgstr "simples" - -#: content/table/models.py:63 -msgid "title row" -msgstr "título da linha" - -#: content/table/models.py:65 -msgid "title row and column" -msgstr "linha e coluna de título" - -#: content/table/models.py:71 -msgid "table" -msgstr "tabela" +msgstr "seções" -#: content/table/models.py:72 -msgid "tables" -msgstr "tabelas" - -#: content/table/models.py:86 -msgid "data" -msgstr "dados" - -#: content/template/models.py:62 +#: content/template/models.py:53 msgid "template content" msgstr "conteúdo de modelo" -#: content/template/models.py:63 +#: content/template/models.py:54 msgid "template contents" msgstr "conteúdos de modelo" -#: content/video/models.py:23 +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "modelo" + +#: content/video/models.py:34 msgid "video link" msgstr "ligação de vídeo" -#: content/video/models.py:24 +#: content/video/models.py:36 msgid "" "This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." "com/watch?v=zmj1rpzDRZ0" msgstr "" -"Isto deve ser uma ligação para um vídeo do Youtube ou do vimeo, p.ex.: " -"http://www.youtube.com/watch?v=zmj1rpzDRZ0" +"Isto deve ser uma ligação para um vídeo do Youtube ou Vimeo, i.e.: http://" +"www.youtube.com/watch?v=zmj1rpzDRZ0" -#: content/video/models.py:28 +#: content/video/models.py:41 msgid "video" msgstr "vídeo" -#: content/video/models.py:29 +#: content/video/models.py:42 msgid "videos" msgstr "vídeos" -#: module/blog/models.py:31 +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "" + +#: models.py:550 +msgid "ordering" +msgstr "ordenação" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "etiquetas" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "idioma" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "tradução de" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "Deixe este campo em branco para entradas no idioma original." + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "traduções disponíveis" + +#: module/blog/models.py:33 msgid "published" msgstr "publicado" -#: module/blog/models.py:33 +#: module/blog/models.py:36 msgid "This is used for the generated navigation too." msgstr "Isto também é usado para a navegação gerada automaticamente." -#: module/blog/models.py:36 +#: module/blog/models.py:40 msgid "published on" msgstr "publicado em" -#: module/blog/models.py:37 +#: module/blog/models.py:42 msgid "Will be set automatically once you tick the `published` checkbox above." msgstr "" -"É definido automaticamente quando ativa a caixa de verificação 'publicado' " +"Será definido automaticamente assim que você ativar a caixa `publicado` " "acima." -#: module/blog/models.py:42 +#: module/blog/models.py:48 msgid "entry" msgstr "entrada" -#: module/blog/models.py:43 +#: module/blog/models.py:49 msgid "entries" msgstr "entradas" -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "etiquetas" - -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "tradução de" - -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -msgid "Leave this empty for entries in the primary language." -msgstr "Deixe este campo em branco nas entradas do idioma original." - -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" -msgstr "traduções disponíveis" - -#: module/extensions/changedate.py:38 +#: module/extensions/changedate.py:41 msgid "creation date" msgstr "data de criação" -#: module/extensions/changedate.py:39 +#: module/extensions/changedate.py:43 msgid "modification date" msgstr "data de modificação" -#: module/extensions/ct_tracker.py:134 +#: module/extensions/ct_tracker.py:156 msgid "content types" msgstr "tipos de conteúdo" -#: module/extensions/featured.py:9 +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "Otimização para motores de busca" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "publicar até" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "Deixe em branco se a entrada deve ficar ativa para sempre." + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "visível de - até" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "Publicação baseada em datas" + +#: module/extensions/featured.py:15 msgid "featured" msgstr "recomendado" -#: module/extensions/featured.py:14 +#: module/extensions/featured.py:21 msgid "Featured" msgstr "Recomendado" -#: module/extensions/seo.py:9 +#: module/extensions/seo.py:16 msgid "meta keywords" -msgstr "palavras-chave meta" +msgstr "meta palavras-chave" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "Isto será inserido antes da lista de palavras-chave padrão." +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" -#: module/extensions/seo.py:11 +#: module/extensions/seo.py:20 msgid "meta description" -msgstr "descrição meta" +msgstr "meta descrição" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." -msgstr "Isto será inserido antes da descrição padrão." +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" -#: module/extensions/seo.py:18 +#: module/extensions/seo.py:32 msgid "Search engine optimization" -msgstr "Otimização para motor de busca" +msgstr "Otimização para motores de busca" + +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "Editar a tradução" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Criar tradução" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "traduções" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "Adicionado com sucesso %(count)d arquivo de mídia em %(category)s." +msgstr[1] "Adicionado com sucesso %(count)d arquivos de mídia em %(category)s." + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "Adicionar arquivos de mídia selecionados à categoria" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "Pré-visualização" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "tamanho do arquivo" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "criado em" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "tipo de arquivo" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "informação do arquivo" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "%d arquivos importados" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "Nenhum arquivo de entrada fornecido" -#: module/medialibrary/models.py:51 +#: module/medialibrary/models.py:49 msgid "parent" msgstr "ascendente" -#: module/medialibrary/models.py:53 module/page/models.py:260 +#: module/medialibrary/models.py:51 module/page/models.py:175 msgid "slug" msgstr "slug" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:55 msgid "category" msgstr "categoria" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 msgid "categories" msgstr "categorias" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" -msgstr "tipo de arquivo" - -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" -msgstr "criado em" - -#: module/medialibrary/models.py:93 +#: module/medialibrary/models.py:101 msgid "copyright" msgstr "copyright" -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 -msgid "file size" -msgstr "tamanho do arquivo" - -#: module/medialibrary/models.py:203 -msgid "file info" -msgstr "informação do arquivo" - -#: module/medialibrary/models.py:280 +#: module/medialibrary/models.py:219 msgid "Image" msgstr "Imagem" -#: module/medialibrary/models.py:281 +#: module/medialibrary/models.py:221 msgid "Video" msgstr "Vídeo" -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:224 msgid "Audio" msgstr "Áudio" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:226 msgid "PDF document" msgstr "documento PDF" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:227 msgid "Flash" msgstr "Flash" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:228 msgid "Text" msgstr "Texto" -#: module/medialibrary/models.py:286 +#: module/medialibrary/models.py:229 msgid "Rich Text" msgstr "Texto Rico" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:230 msgid "Zip archive" -msgstr "" +msgstr "Arquivo ZIP" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:231 msgid "Microsoft Word" msgstr "Microsoft Word" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:233 msgid "Microsoft Excel" msgstr "Microsoft Excel" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:235 msgid "Microsoft PowerPoint" msgstr "Microsoft PowerPoint" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:237 msgid "Binary" msgstr "Binário" -#: module/medialibrary/models.py:307 -msgid "caption" -msgstr "legenda" - -#: module/medialibrary/models.py:308 +#: module/medialibrary/models.py:261 msgid "description" msgstr "descrição" -#: module/medialibrary/models.py:311 +#: module/medialibrary/models.py:264 msgid "media file translation" msgstr "tradução do arquivo de mídia" -#: module/medialibrary/models.py:312 +#: module/medialibrary/models.py:265 msgid "media file translations" msgstr "traduções do arquivo de mídia" -#: module/medialibrary/models.py:335 -msgid "Preview" -msgstr "Pré-visualização" +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" +msgstr "trecho" -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." +msgstr "Adicione um breve trecho que sumarize o conteúdo desta página." -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" -msgstr "" +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" +msgstr "Trecho" -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" -msgstr "" +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 +msgid "navigation extension" +msgstr "extensão de navegação" -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" +#: module/page/extensions/navigation.py:119 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." msgstr "" +"Selecione o módulo que fornece sub-páginas para esta página se você precisa " +"personalizar a navegação." -#: module/medialibrary/models.py:448 -msgid "No input file given" +#: module/page/extensions/navigation.py:134 +msgid "Navigation extension" +msgstr "Extensão de navegação" + +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" msgstr "" -#: module/page/models.py:256 -msgid "active" -msgstr "ativo" +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" -#: module/page/models.py:263 module/page/models.py:711 -msgid "in navigation" +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" msgstr "na navegação" -#: module/page/models.py:264 -msgid "override URL" -msgstr "URL efetivo" +#: module/page/extensions/relatedpages.py:21 +msgid "Select pages that should be listed as related content." +msgstr "Selecione as páginas que devam ser listadas como conteúdo relacionado." -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." -msgstr "" -"URL efetivo. Deve conter uma /, caso se trate de um URL local. Este campo " -"determina a navegação e os URL's das sub-páginas." +#: module/page/extensions/relatedpages.py:26 +msgid "Related pages" +msgstr "Páginas relacionadas" -#: module/page/models.py:266 -msgid "redirect to" -msgstr "redirecionar para" +#: module/page/extensions/sites.py:21 +msgid "Site" +msgstr "Site" -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." -msgstr "URL de destino para redirecionamentos automáticos." +#: module/page/extensions/symlinks.py:22 +msgid "symlinked page" +msgstr "página de ligação" -#: module/page/models.py:268 -msgid "Cached URL" -msgstr "URL em cache" +#: module/page/extensions/symlinks.py:23 +msgid "All content is inherited from this page if given." +msgstr "O conteúdo é herdado desta página, se fornecida." -#: module/page/models.py:279 -msgid "page" -msgstr "página" +#: module/page/extensions/titles.py:19 +msgid "content title" +msgstr "título do conteúdo" -#: module/page/models.py:280 -msgid "pages" -msgstr "páginas" +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." +msgstr "" +"A primeira linha é o título principal, as linhas subsequentes são sub-" +"títulos." + +#: module/page/extensions/titles.py:26 +msgid "page title" +msgstr "título da página" -#: module/page/models.py:297 module/page/models.py:782 +#: module/page/extensions/titles.py:30 #, fuzzy -msgid "is active" -msgstr "ativo" +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." +msgstr "" +"Título da página para a janela do navegador. Por padrão, o mesmo que o " +"título." + +#: module/page/extensions/titles.py:60 +msgid "Titles" +msgstr "Títulos" -#: module/page/models.py:631 +#: module/page/forms.py:187 msgid "This URL is already taken by an active page." -msgstr "Este URL já está ocupado por uma página ativa." +msgstr "Esta URL já está ocupada por uma página ativa." -#: module/page/models.py:649 +#: module/page/forms.py:206 msgid "This URL is already taken by another active page." -msgstr "Este URL já está ocupado por outra página ativa." +msgstr "Esta URL já está ocupada por outra página ativa." + +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" -#: module/page/models.py:674 +#: module/page/modeladmins.py:42 msgid "Other options" -msgstr "outras opções" +msgstr "Outras opções" + +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "na navegação" -#: module/page/models.py:721 +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 msgid "Add child page" msgstr "Adicionar página descendente" -#: module/page/models.py:723 +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 msgid "View on site" msgstr "Ver no site" -#: module/page/models.py:759 -msgid "You don't have the necessary permissions to edit this object" +#: module/page/modeladmins.py:142 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" msgstr "" -#: module/page/models.py:774 +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "" + +#: module/page/modeladmins.py:197 +msgid "You don't have the necessary permissions to edit this object" +msgstr "Você não tem as permissões necessárias para editar este objeto" + +#: module/page/modeladmins.py:223 msgid "inherited" msgstr "herdado" -#: module/page/models.py:778 +#: module/page/modeladmins.py:229 msgid "extensions" msgstr "extensões" -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "data de publicação" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "publicar até" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "Deixe em branco se a entrada deve ficar ativa para sempre." - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "visível de - até" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" -msgstr "Publicação baseada em datas" +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" +msgstr "ativo" -#: module/page/extensions/excerpt.py:9 -msgid "excerpt" -msgstr "excerto" +#: module/page/models.py:169 +msgid "active" +msgstr "ativo" -#: module/page/extensions/excerpt.py:10 -msgid "Add a brief excerpt summarizing the content of this page." -msgstr "Adicione um breve excerto que sumarize o conteúdo desta página." +#: module/page/models.py:173 +#, fuzzy +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "Isto também é usado para a navegação gerada automaticamente." -#: module/page/extensions/excerpt.py:12 -msgid "Excerpt" -msgstr "Excerto" +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" +msgstr "" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 -msgid "navigation extension" -msgstr "extensão de navegação" +#: module/page/models.py:184 +msgid "override URL" +msgstr "URL efetiva" -#: module/page/extensions/navigation.py:99 +#: module/page/models.py:186 msgid "" -"Select the module providing subpages for this page if you need to customize " -"the navigation." -msgstr "Selecione o módulo que providencia sub-páginas." - -#: module/page/extensions/navigation.py:112 -msgid "Navigation extension" -msgstr "Extensão de navegação" - -#: module/page/extensions/relatedpages.py:13 -msgid "Select pages that should be listed as related content." -msgstr "Selecione páginas a listar como conteúdo relacionado." - -#: module/page/extensions/relatedpages.py:20 -msgid "Related pages" -msgstr "Páginas relacionadas" - -#: module/page/extensions/sites.py:16 -msgid "Site" +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." msgstr "" +"Substitui a URL de destino. Certifique-se de incluir barras no início e no " +"final, se for uma URL local. Isso afeta tanto a navegação e URLs de " +"subpáginas." -#: module/page/extensions/symlinks.py:15 -msgid "symlinked page" -msgstr "página ligada" - -#: module/page/extensions/symlinks.py:16 -msgid "All content is inherited from this page if given." -msgstr "O conteúdo é herdado desta página." - -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" -msgstr "Página ligada" - -#: module/page/extensions/titles.py:13 -msgid "content title" -msgstr "título do conteúdo" +#: module/page/models.py:190 +msgid "redirect to" +msgstr "redirecionar para" -#: module/page/extensions/titles.py:14 -msgid "The first line is the main title, the following lines are subtitles." +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." msgstr "" -"A primeira linha é o título principal. Outras linhas serão sub-títulos." -#: module/page/extensions/titles.py:15 -msgid "page title" -msgstr "título da página" +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "URL em cache" -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." +#: module/page/models.py:298 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" msgstr "" -"Título da página para a janela do navegador. Se omitido assume o título." - -#: module/page/extensions/titles.py:43 -msgid "Titles" -msgstr "Títulos" - -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "Editar a tradução" -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "Criar tradução" - -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "traduções" - -#: templates/admin/filter.html:3 -#, python-format -msgid " By %(filter_title)s " -msgstr " Por %(filter_title)s " +#: module/page/models.py:426 +msgid "page" +msgstr "página" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "Procurar" +#: module/page/models.py:427 +msgid "pages" +msgstr "páginas" #: templates/admin/feincms/_messages_js.html:4 msgid "Really delete item?" -msgstr "Confirma a eliminação do item?" +msgstr "Confirma a remoção do item?" #: templates/admin/feincms/_messages_js.html:4 msgid "Confirm to delete item" -msgstr "Confirme para eliminar o item" +msgstr "Confirme para remover o item" #: templates/admin/feincms/_messages_js.html:5 msgid "Item deleted successfully." -msgstr "Item eliminado com sucesso." +msgstr "Item removido com sucesso." #: templates/admin/feincms/_messages_js.html:5 msgid "Cannot delete item" -msgstr "Impossível eliminar o item" +msgstr "Impossível remover o item" #: templates/admin/feincms/_messages_js.html:6 msgid "Cannot delete item, because it is parent of at least one other item." -msgstr "Impossível eliminar o item, pois possui pelo menos um dependente." +msgstr "Impossível remover o item, pois ele possui pelo menos um dependente." #: templates/admin/feincms/_messages_js.html:7 msgid "Change template" @@ -743,17 +806,14 @@ msgstr "Alterar o modelo" #: templates/admin/feincms/_messages_js.html:8 msgid "Really change template?
    All changes are saved." msgstr "" -"Confirma a alteração do modelo?
    Todas as alterações serão guardadas." +"Confirma a alteração do modelo?
    Todas as alterações serão salvas." #: templates/admin/feincms/_messages_js.html:9 #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s is moved to %%(target_region)s." msgstr "" -"Confirma a alteração do modelo?
    As modificações serão gravadas e o " -"conteúdo de %(source_regions)s será movido para " -"%(target_region)s." #: templates/admin/feincms/_messages_js.html:12 msgid "Hide" @@ -775,129 +835,154 @@ msgstr "Antes" msgid "Insert new:" msgstr "Inserir novo:" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "Região vazia" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." -msgstr "O conteúdo é herdado da página ascendente, excepto se o indicar aqui." +msgstr "" +"O conteúdo é herdado automaticamente da página ascendente. Para mudar este " +"comportamento, adicione algum conteúdo." -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "Adicionar novo item" -#: templates/admin/feincms/fe_editor.html:46 +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "Adicionar mais um %(verbose_name)s" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "Remover" + +#: templates/admin/feincms/fe_editor.html:30 msgid "Save" msgstr "Guardar" -#: templates/admin/feincms/fe_tools.html:27 +#: templates/admin/feincms/fe_tools.html:28 msgid "Stop Editing" -msgstr "Parar de Editar" +msgstr "Parar Edição" -#: templates/admin/feincms/fe_tools.html:32 +#: templates/admin/feincms/fe_tools.html:33 msgid "edit" msgstr "editar" -#: templates/admin/feincms/fe_tools.html:34 +#: templates/admin/feincms/fe_tools.html:35 msgid "new" msgstr "novo" -#: templates/admin/feincms/fe_tools.html:35 +#: templates/admin/feincms/fe_tools.html:36 msgid "up" msgstr "para cima" -#: templates/admin/feincms/fe_tools.html:36 +#: templates/admin/feincms/fe_tools.html:37 msgid "down" msgstr "para baixo" -#: templates/admin/feincms/fe_tools.html:37 +#: templates/admin/feincms/fe_tools.html:38 msgid "remove" msgstr "remover" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:16 +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "Editar no site" + +#: templates/admin/feincms/page/page/item_editor.html:23 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 msgid "Home" msgstr "Página inicial" -#: templates/admin/feincms/recover_form.html:10 +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "Adicionar" + +#: templates/admin/feincms/recover_form.html:11 #, python-format msgid "Recover deleted %(verbose_name)s" -msgstr "" +msgstr "Recuperar %(verbose_name)s removido" #: templates/admin/feincms/recover_form.html:17 msgid "Press the save button below to recover this version of the object." msgstr "" +"Pressione o botão de salvar abaixo para recuperar esta versão do objeto." -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:12 msgid "History" -msgstr "" +msgstr "Histórico" -#: templates/admin/feincms/revision_form.html:15 +#: templates/admin/feincms/revision_form.html:13 #, python-format msgid "Revert %(verbose_name)s" -msgstr "" +msgstr "Reverter %(verbose_name)s" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:24 msgid "Press the save button below to revert to this version of the object." msgstr "" +"Pressione o botão de salvar abaixo para reverter para esta versão do objeto." -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "Atalhos" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" -msgstr "Colapsar a árvore" +msgstr "Contrair árvore" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" -msgstr "Expandir a árvore" +msgstr "Expandir árvore" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "Filtrar" -#: templates/admin/feincms/page/page/item_editor.html:9 -msgid "Edit on site" -msgstr "Editar no sítio" - -#: templates/admin/feincms/page/page/item_editor.html:20 -msgid "Add" -msgstr "" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr " Por %(filter_title)s " #: templates/admin/medialibrary/add_to_category.html:5 #: templates/admin/medialibrary/add_to_category.html:9 -#, fuzzy msgid "Add media files to category" -msgstr "tradução do arquivo de mídia" +msgstr "página de ligação" #: templates/admin/medialibrary/add_to_category.html:11 msgid "Select category to apply:" -msgstr "" +msgstr "Selecione a categoria a aplicar:" #: templates/admin/medialibrary/add_to_category.html:17 msgid "The following media files will be added to the selected category:" msgstr "" +"Os seguintes arquivos de mídia serão adicionados à categoria selecionada:" #: templates/admin/medialibrary/add_to_category.html:22 -#, fuzzy msgid "Add to category" -msgstr "categoria" +msgstr "Adicionar à categoria" #: templates/admin/medialibrary/add_to_category.html:23 msgid "Cancel" -msgstr "" +msgstr "Cancelar" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:11 msgid "Bulk upload a ZIP file:" -msgstr "Enviar um arquivo ZIP:" +msgstr "Enviar em massa um arquivo ZIP:" #: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 msgid "Send" msgstr "Enviar" @@ -935,141 +1020,26 @@ msgstr "Enviar" msgid "Thanks!" msgstr "Obrigado!" -#~ msgid "rich text (ckeditor)" -#~ msgstr "texto rico (ckeditor)" - -#~ msgid "rich texts (ckeditor)" -#~ msgstr "textos ricos (ckeditor)" - -#~ msgid "You may edit the copied page below." -#~ msgstr "Pode voltar a editar a página copiada em baixo." - -#~ msgid "" -#~ "You have replaced %s. You may continue editing the now-active page below." -#~ msgstr "" -#~ "Substituiu %s. Pode continuar a editar a página em baixo, agora ativa." - -#~ msgid "Move to" -#~ msgstr "Mover para" - -#~ msgid "Move" -#~ msgstr "Mover" - -#~ msgid "Add %(name)s" -#~ msgstr "Adicionar %(name)s" - -#~ msgid "Select an item on the left side if you want to edit it." -#~ msgstr "Selecione um item à esquerda para o editar." - -#~ msgid "" -#~ "You can change the structure of the tree by drag-dropping elements. " -#~ "Please note that changes will be saved immediately. " -#~ msgstr "" -#~ "Pode alterar a estrutura da árvore arrastando elementos. Note que as " -#~ "alterações serão guardadas imediatamente." - -#~ msgid "" -#~ "The context menu on the tree root and tree nodes provide you with " -#~ "additional modes of operation." -#~ msgstr "" -#~ "O menu de contexto na raiz e nos ramos da árvore permite modos de " -#~ "operação adicionais." - -#~ msgid "Cannot remove this frame while inside this admin section." -#~ msgstr "" -#~ "É impossível remover esta moldura enquanto estiver dentro desta secção de " -#~ "admin." - -#~ msgid "Reload" -#~ msgstr "Recarregar" - -#~ msgid "Edit" -#~ msgstr "Editar" - -#~ msgid "Delete" -#~ msgstr "Eliminar" - -#~ msgid "Replace page %(to_replace)s" -#~ msgstr "Substituir a página %(to_replace)s" - -#~ msgid "Create hidden copy of this page" -#~ msgstr "Criar uma cópia oculta desta página" - -#~ msgid "The %(name)s \"%(obj)s\" was changed successfully." -#~ msgstr "%(name)s \"%(obj)s\" foi modificado com sucesso." - -#~ msgid "You may edit it again below." -#~ msgstr "Pode voltar a editar o elemento." - -#~ msgid "You may add another %s below." -#~ msgstr "Pode adicionar um novo %s." - -#~ msgid "Insert as child" -#~ msgstr "Inserir como descendente" - -#~ msgid "Insert before" -#~ msgstr "Inserir antes" - -#~ msgid "is visible" -#~ msgstr "é visível" - -#~ msgid "Video from unknown portal" -#~ msgstr "Vídeo de um portal desconhecido" - -#~ msgid "Tree saved successfully." -#~ msgstr "Árvore guardada com sucesso." - -#~ msgid "Cannot make a node a child of itself." -#~ msgstr "Um nó não pode ser dependente de si próprio." - -#~ msgid "Save and add another" -#~ msgstr "Guardar e adicionar outro" - -#~ msgid "Save and continue editing" -#~ msgstr "Guardar e continuar a editar" - -#~ msgid "Properties" -#~ msgstr "Propriedades" - -#~ msgid "Move selected item to" -#~ msgstr "Mover item selecionado para" - -#~ msgid "OK" -#~ msgstr "OK" - -#~ msgid "Database error" -#~ msgstr "Erro de base de dados" - -#~ msgid "view content" -#~ msgstr "conteúdo da função" - -#~ msgid "" -#~ "Could not parse the view content because the view is excluded from " -#~ "infanta handling." -#~ msgstr "" -#~ "Não foi possível ler o conteúdo da vista, porque esta está excluída do " -#~ "tratamento de infanta." - -#~ msgid "Placeholder for the %(viewname)s calling %(viewfunc)s" -#~ msgstr "Substituto para %(viewname)s, que chama %(viewfunc)s" +#~ msgid "plain" +#~ msgstr "simples" -#~ msgid "Placeholder for calling %(viewfunc)s" -#~ msgstr "Substituto para chamar %(viewfunc)s" +#~ msgid "title row" +#~ msgstr "linha de título" -#~ msgid "not active" -#~ msgstr "inativo" +#~ msgid "title row and column" +#~ msgstr "linha e coluna de título" -#~ msgid "Save tree" -#~ msgstr "Guardar árvore" +#~ msgid "table" +#~ msgstr "tabela" -#~ msgid "%(icon)s (not active)" -#~ msgstr "%(icon)s (inactivo)" +#~ msgid "tables" +#~ msgstr "tabelas" -#~ msgid "%(icon)s (until %(from)s)" -#~ msgstr "%(icon)s (até %(from)s)" +#~ msgid "data" +#~ msgstr "dados" -#~ msgid "%(icon)s (since %(to)s)" -#~ msgstr "%(icon)s (a partir de %(to)s)" +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Isto será inserido antes da lista de palavras-chave padrão." -#~ msgid "%(icon)s (%(from)s – %(to)s)" -#~ msgstr "%(icon)s (%(from)s – %(to)s)" +#~ msgid "This will be prepended to the default description." +#~ msgstr "Isto será inserido antes da descrição padrão." diff --git a/feincms/locale/ro/LC_MESSAGES/django.mo b/feincms/locale/ro/LC_MESSAGES/django.mo index cbb4f9c64..3ca2ed362 100644 Binary files a/feincms/locale/ro/LC_MESSAGES/django.mo and b/feincms/locale/ro/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/ro/LC_MESSAGES/django.po b/feincms/locale/ro/LC_MESSAGES/django.po index d7d7ed43c..594d17e0e 100644 --- a/feincms/locale/ro/LC_MESSAGES/django.po +++ b/feincms/locale/ro/LC_MESSAGES/django.po @@ -1,228 +1,223 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# GABRIEL KOVACS , 2010. # +# Translators: +# Gabriel Kovacs , 2010 msgid "" msgstr "" -"Project-Id-Version: FienCMS\n" +"Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:19-0500\n" -"PO-Revision-Date: 2009-07-16 17:21+0100\n" -"Last-Translator: Gabriel Kovacs \n" -"Language-Team: ro \n" -"Language: \n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Matthias Kestenholz \n" +"Language-Team: Romanian (http://www.transifex.com/projects/p/feincms/" +"language/ro/)\n" +"Language: ro\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: Romanian\n" -"X-Poedit-SourceCharset: utf-8\n" -"X-Poedit-Country: ROMANIA\n" +"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?" +"2:1));\n" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "șablon" - -#: models.py:553 -msgid "ordering" -msgstr "ordonare" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "limbă" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filterspecs.py:47 admin/filterspecs.py:86 msgid "All" msgstr "" -#: admin/filterspecs.py:57 module/page/models.py:261 -#, fuzzy +#: admin/filterspecs.py:58 module/page/models.py:178 msgid "Parent" -msgstr "părinte" +msgstr "" -#: admin/filterspecs.py:95 -#, fuzzy +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 msgid "Category" -msgstr "categorie" +msgstr "" -#: admin/item_editor.py:150 +#: admin/item_editor.py:190 #, python-format msgid "Change %s" msgstr "Modifică %s" -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:259 -#: module/page/models.py:338 +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 msgid "title" msgstr "titlu" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +msgid "You do not have permission to modify this object" +msgstr "" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 #, python-format msgid "%s has been moved to a new position." msgstr "" -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:512 msgid "Did not understand moving instruction." msgstr "" -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:523 msgid "actions" msgstr "acțiuni" -#: content/application/models.py:241 +#: admin/tree_editor.py:552 +#, python-format +msgid "Successfully deleted %(count)d items." +msgstr "" + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +#: content/application/models.py:147 msgid "application content" msgstr "conținutul aplicației" -#: content/application/models.py:242 +#: content/application/models.py:148 msgid "application contents" msgstr "conținuturile aplicației" -#: content/application/models.py:273 +#: content/application/models.py:179 msgid "application" msgstr "aplicație" -#: content/comments/models.py:28 -#, fuzzy +#: content/comments/models.py:32 msgid "enabled" -msgstr "tabel" +msgstr "" -#: content/comments/models.py:28 +#: content/comments/models.py:33 msgid "New comments may be added" msgstr "" -#: content/comments/models.py:32 content/comments/models.py:33 -#, fuzzy +#: content/comments/models.py:37 content/comments/models.py:38 msgid "comments" -msgstr "conținut" +msgstr "" -#: content/comments/models.py:48 -#, fuzzy +#: content/comments/models.py:60 msgid "public" -msgstr "publicat" +msgstr "" -#: content/comments/models.py:48 +#: content/comments/models.py:61 msgid "not public" msgstr "" -#: content/contactform/models.py:18 +#: content/contactform/models.py:20 msgid "name" msgstr "nume" -#: content/contactform/models.py:19 +#: content/contactform/models.py:21 msgid "email" msgstr "e-mail" -#: content/contactform/models.py:20 +#: content/contactform/models.py:22 msgid "subject" msgstr "subiect" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:26 content/raw/models.py:16 msgid "content" msgstr "conținut" -#: content/contactform/models.py:34 +#: content/contactform/models.py:37 msgid "contact form" msgstr "formular de contact" -#: content/contactform/models.py:35 +#: content/contactform/models.py:38 msgid "contact forms" msgstr "formulare de contact" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 msgid "file" msgstr "fișier" -#: content/file/models.py:21 +#: content/file/models.py:29 msgid "files" msgstr "fișiere" -#: content/image/models.py:24 content/image/models.py:28 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "imagine" -#: content/image/models.py:29 +#: content/image/models.py:49 +msgid "alternate text" +msgstr "" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "legendă" + +#: content/image/models.py:56 msgid "images" msgstr "imagini" -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 +#: content/image/models.py:82 msgid "position" msgstr "poziție" -#: content/medialibrary/models.py:38 -msgid "(no caption)" -msgstr "(fără legendă)" +#: content/image/models.py:90 +msgid "format" +msgstr "" -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 msgid "media file" msgstr "fișier media" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 msgid "media files" msgstr "fișiere media" -#: content/medialibrary/models.py:131 -msgid "block" -msgstr "blochează" - -#: content/medialibrary/models.py:132 -msgid "left" -msgstr "stânga" - -#: content/medialibrary/models.py:133 -msgid "right" -msgstr "dreapta" - -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: content/medialibrary/models.py:64 content/section/models.py:59 msgid "type" msgstr "tip" -#: content/raw/models.py:18 +#: content/raw/models.py:20 msgid "raw content" msgstr "conținut neformatat" -#: content/raw/models.py:19 +#: content/raw/models.py:21 msgid "raw contents" msgstr "conținuturi neformatate" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "text" - -#: content/richtext/models.py:26 +#: content/richtext/models.py:24 msgid "HTML Tidy" msgstr "" -#: content/richtext/models.py:27 +#: content/richtext/models.py:25 msgid "Ignore the HTML validation warnings" msgstr "" -#: content/richtext/models.py:51 +#: content/richtext/models.py:55 #, python-format msgid "" "HTML validation produced %(count)d warnings. Please review the updated " "content below before continuing: %(messages)s" msgstr "" -#: content/richtext/models.py:89 +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "text" + +#: content/richtext/models.py:103 msgid "rich text" msgstr "text formatabil" -#: content/richtext/models.py:90 +#: content/richtext/models.py:104 msgid "rich texts" msgstr "texte formatabile" -#: content/rss/models.py:21 +#: content/rss/models.py:25 msgid "" "The rss field is updated several times a day. A change in the title will " "only be visible on the home page after the next feed update." @@ -230,79 +225,55 @@ msgstr "" "Fluxul RSS este actualizat de mai multe ori pe zi. O modificare a titlului " "va fi vizibilă in prima pagină după următoarea actualizare a fluxului." -#: content/rss/models.py:22 +#: content/rss/models.py:28 msgid "link" msgstr "legătură" -#: content/rss/models.py:23 +#: content/rss/models.py:30 msgid "pre-rendered content" msgstr "conținut predefinit" -#: content/rss/models.py:24 +#: content/rss/models.py:32 msgid "last updated" msgstr "ultima actualizare" -#: content/rss/models.py:25 +#: content/rss/models.py:33 msgid "max. items" msgstr "nr. de obiecte maxime" -#: content/rss/models.py:29 +#: content/rss/models.py:37 msgid "RSS feed" msgstr "flux RSS" -#: content/rss/models.py:30 +#: content/rss/models.py:38 msgid "RSS feeds" msgstr "fluxuri RSS" -#: content/section/models.py:37 -#, fuzzy +#: content/section/models.py:43 msgid "section" -msgstr "acțiuni" +msgstr "" -#: content/section/models.py:38 -#, fuzzy +#: content/section/models.py:44 msgid "sections" -msgstr "acțiuni" - -#: content/table/models.py:62 -msgid "plain" -msgstr "simplu" - -#: content/table/models.py:63 -msgid "title row" -msgstr "titlu" - -#: content/table/models.py:65 -msgid "title row and column" -msgstr "titlu rând și coloană" - -#: content/table/models.py:71 -msgid "table" -msgstr "tabel" - -#: content/table/models.py:72 -msgid "tables" -msgstr "tabele" - -#: content/table/models.py:86 -msgid "data" -msgstr "data" +msgstr "" -#: content/template/models.py:62 -#, fuzzy +#: content/template/models.py:53 msgid "template content" -msgstr "conținutul aplicației" +msgstr "" -#: content/template/models.py:63 -#, fuzzy +#: content/template/models.py:54 msgid "template contents" -msgstr "conținuturile aplicației" +msgstr "" + +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "șablon" -#: content/video/models.py:23 +#: content/video/models.py:34 msgid "video link" msgstr "legătură film" -#: content/video/models.py:24 +#: content/video/models.py:36 msgid "" "This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." "com/watch?v=zmj1rpzDRZ0" @@ -310,432 +281,496 @@ msgstr "" "Aceasta ar trebui să fie o legătură către un film de pe youtube sau vimeo, " "de ex.: http://www.youtube.com/watch?v=zmj1rpzDRZ0" -#: content/video/models.py:28 +#: content/video/models.py:41 msgid "video" msgstr "film" -#: content/video/models.py:29 +#: content/video/models.py:42 msgid "videos" msgstr "filme" -#: module/blog/models.py:31 +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "" + +#: models.py:550 +msgid "ordering" +msgstr "ordonare" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "etichete" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "limbă" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "tradus de" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "" + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "traduceri disponibile" + +#: module/blog/models.py:33 msgid "published" msgstr "publicat" -#: module/blog/models.py:33 +#: module/blog/models.py:36 msgid "This is used for the generated navigation too." msgstr "Aceasta este deasemenea folosită pentru navigarea generată." -#: module/blog/models.py:36 +#: module/blog/models.py:40 msgid "published on" msgstr "publicat la" -#: module/blog/models.py:37 +#: module/blog/models.py:42 msgid "Will be set automatically once you tick the `published` checkbox above." msgstr "" "Va fi trimis automat în momentul in care bifați butonul `publicat` de mai " "sus." -#: module/blog/models.py:42 +#: module/blog/models.py:48 msgid "entry" msgstr "înregistrare" -#: module/blog/models.py:43 +#: module/blog/models.py:49 msgid "entries" msgstr "înregistrari" -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "etichete" - -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "tradus de" - -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -#, fuzzy -msgid "Leave this empty for entries in the primary language." -msgstr "Lăsați gol pentru inregistrări in limba primară (%s)." - -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" -msgstr "traduceri disponibile" - -#: module/extensions/changedate.py:38 +#: module/extensions/changedate.py:41 msgid "creation date" msgstr "data creerii" -#: module/extensions/changedate.py:39 +#: module/extensions/changedate.py:43 msgid "modification date" msgstr "data modificării" -#: module/extensions/ct_tracker.py:134 -#, fuzzy +#: module/extensions/ct_tracker.py:156 msgid "content types" -msgstr "titlu conținut" +msgstr "" -#: module/extensions/featured.py:9 -#, fuzzy +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "data publicării" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "sfârșitul publicării" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "Lasă gol dacă înregistrarea ar trebui să fie activă întotdeauna" + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "vizibil de la - până la" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "" + +#: module/extensions/featured.py:15 msgid "featured" -msgstr "creat" +msgstr "" -#: module/extensions/featured.py:14 -#, fuzzy +#: module/extensions/featured.py:21 msgid "Featured" -msgstr "creat" +msgstr "" -#: module/extensions/seo.py:9 +#: module/extensions/seo.py:16 msgid "meta keywords" msgstr "cuvinte cheie meta" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "Va fi inserat la lista predefinită de cuvinte cheie." +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" -#: module/extensions/seo.py:11 +#: module/extensions/seo.py:20 msgid "meta description" msgstr "descriere meta" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." -msgstr "Va fi inserată la descrierea predefinită." +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" -#: module/extensions/seo.py:18 +#: module/extensions/seo.py:32 msgid "Search engine optimization" msgstr "" -#: module/medialibrary/models.py:51 +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "Modifică translatarea" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Creează o translatare" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "translații" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "Pre-vizualizare" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "creat" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "tipul fișierului" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "" + +#: module/medialibrary/models.py:49 msgid "parent" msgstr "părinte" -#: module/medialibrary/models.py:53 module/page/models.py:260 +#: module/medialibrary/models.py:51 module/page/models.py:175 msgid "slug" msgstr "slug" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:55 msgid "category" msgstr "categorie" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 msgid "categories" msgstr "categorii" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" -msgstr "tipul fișierului" - -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" -msgstr "creat" - -#: module/medialibrary/models.py:93 +#: module/medialibrary/models.py:101 msgid "copyright" msgstr "copyright" -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 -#, fuzzy -msgid "file size" -msgstr "fișiere" - -#: module/medialibrary/models.py:203 -#, fuzzy -msgid "file info" -msgstr "fișier" - -#: module/medialibrary/models.py:280 +#: module/medialibrary/models.py:219 msgid "Image" msgstr "Imagine" -#: module/medialibrary/models.py:281 -#, fuzzy +#: module/medialibrary/models.py:221 msgid "Video" -msgstr "film" +msgstr "" -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:224 msgid "Audio" msgstr "" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:226 msgid "PDF document" msgstr "document PDF" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:227 msgid "Flash" msgstr "Flash" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:228 msgid "Text" msgstr "Text" -#: module/medialibrary/models.py:286 -#, fuzzy +#: module/medialibrary/models.py:229 msgid "Rich Text" -msgstr "text formatabil" +msgstr "" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:230 msgid "Zip archive" msgstr "" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:231 msgid "Microsoft Word" msgstr "" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:233 msgid "Microsoft Excel" msgstr "" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:235 msgid "Microsoft PowerPoint" msgstr "" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:237 msgid "Binary" msgstr "Binar" -#: module/medialibrary/models.py:307 -msgid "caption" -msgstr "legendă" - -#: module/medialibrary/models.py:308 +#: module/medialibrary/models.py:261 msgid "description" msgstr "descriere" -#: module/medialibrary/models.py:311 +#: module/medialibrary/models.py:264 msgid "media file translation" msgstr "traducere fișier media" -#: module/medialibrary/models.py:312 +#: module/medialibrary/models.py:265 msgid "media file translations" msgstr "traduceri fișier media" -#: module/medialibrary/models.py:335 -msgid "Preview" -msgstr "Pre-vizualizare" +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" +msgstr "" -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." +msgstr "" -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" msgstr "" -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 +msgid "navigation extension" +msgstr "navigare extinsă" + +#: module/page/extensions/navigation.py:119 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." msgstr "" +"Selectați modulul care furnizează subpaginile pentru această pagină dacă " +"doriți să personalizaţi." -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" +#: module/page/extensions/navigation.py:134 +msgid "Navigation extension" msgstr "" -#: module/medialibrary/models.py:448 -msgid "No input file given" +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" msgstr "" -#: module/page/models.py:256 -msgid "active" -msgstr "activ" +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" -#: module/page/models.py:263 module/page/models.py:711 -msgid "in navigation" +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" msgstr "în navigare" -#: module/page/models.py:264 -msgid "override URL" -msgstr "suprascrie URL" +#: module/page/extensions/relatedpages.py:21 +msgid "Select pages that should be listed as related content." +msgstr "" -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." +#: module/page/extensions/relatedpages.py:26 +msgid "Related pages" msgstr "" -"Suprascrie URL-ul țintă. Aveți grijă să scrieti / la început și la șfârșit " -"dacă este un URL local.Aceasta afectează atât navigația cât și subpaginile." -#: module/page/models.py:266 -msgid "redirect to" -msgstr "redirecționare către" +#: module/page/extensions/sites.py:21 +msgid "Site" +msgstr "" -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." -msgstr "URL-ul țintă pentru redirectări automate." +#: module/page/extensions/symlinks.py:22 +msgid "symlinked page" +msgstr "legătură pagină" -#: module/page/models.py:268 -msgid "Cached URL" -msgstr "URL în cache" +#: module/page/extensions/symlinks.py:23 +msgid "All content is inherited from this page if given." +msgstr "Întreg conținutul este moștenit de la această pagină daca se dorește." -#: module/page/models.py:279 -msgid "page" -msgstr "pagină" +#: module/page/extensions/titles.py:19 +msgid "content title" +msgstr "titlu conținut" -#: module/page/models.py:280 -msgid "pages" -msgstr "pagini" +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." +msgstr "Prima linie reprezintă titlul principal, următoarele sunt subtitluri." + +#: module/page/extensions/titles.py:26 +msgid "page title" +msgstr "titlul paginii" -#: module/page/models.py:297 module/page/models.py:782 +#: module/page/extensions/titles.py:30 #, fuzzy -msgid "is active" -msgstr "activ" +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." +msgstr "" +"Titlul paginii pentru fereastra navigatorului. Implicit este la fel cu " +"titlul paginii." -#: module/page/models.py:631 +#: module/page/extensions/titles.py:60 +msgid "Titles" +msgstr "" + +#: module/page/forms.py:187 msgid "This URL is already taken by an active page." msgstr "Acest URL este deja rezervat de către o pagină activă." -#: module/page/models.py:649 +#: module/page/forms.py:206 msgid "This URL is already taken by another active page." msgstr "Acest URL este deja rezervat de către o altă pagină activă." -#: module/page/models.py:674 +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" + +#: module/page/modeladmins.py:42 msgid "Other options" msgstr "Alte opțiuni" -#: module/page/models.py:721 +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "în navigare" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 msgid "Add child page" msgstr "Adaugă o pagină copil" -#: module/page/models.py:723 +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 msgid "View on site" msgstr "Vezi pe site" -#: module/page/models.py:759 +#: module/page/modeladmins.py:142 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "" + +#: module/page/modeladmins.py:197 msgid "You don't have the necessary permissions to edit this object" msgstr "" -#: module/page/models.py:774 +#: module/page/modeladmins.py:223 msgid "inherited" msgstr "moștenit" -#: module/page/models.py:778 +#: module/page/modeladmins.py:229 msgid "extensions" msgstr "extensii" -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "data publicării" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "sfârșitul publicării" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "Lasă gol dacă înregistrarea ar trebui să fie activă întotdeauna" - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "vizibil de la - până la" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" msgstr "" -#: module/page/extensions/excerpt.py:9 -msgid "excerpt" -msgstr "" +#: module/page/models.py:169 +msgid "active" +msgstr "activ" -#: module/page/extensions/excerpt.py:10 -msgid "Add a brief excerpt summarizing the content of this page." -msgstr "" +#: module/page/models.py:173 +#, fuzzy +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "Aceasta este deasemenea folosită pentru navigarea generată." -#: module/page/extensions/excerpt.py:12 -msgid "Excerpt" +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" msgstr "" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 -msgid "navigation extension" -msgstr "navigare extinsă" +#: module/page/models.py:184 +msgid "override URL" +msgstr "suprascrie URL" -#: module/page/extensions/navigation.py:99 +#: module/page/models.py:186 msgid "" -"Select the module providing subpages for this page if you need to customize " -"the navigation." -msgstr "" -"Selectați modulul care furnizează subpaginile pentru această pagină dacă " -"doriți să personalizaţi." - -#: module/page/extensions/navigation.py:112 -#, fuzzy -msgid "Navigation extension" -msgstr "navigare extinsă" - -#: module/page/extensions/relatedpages.py:13 -msgid "Select pages that should be listed as related content." +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." msgstr "" +"Suprascrie URL-ul țintă. Aveți grijă să scrieti / la început și la șfârșit " +"dacă este un URL local.Aceasta afectează atât navigația cât și subpaginile." -#: module/page/extensions/relatedpages.py:20 -msgid "Related pages" -msgstr "" +#: module/page/models.py:190 +msgid "redirect to" +msgstr "redirecționare către" -#: module/page/extensions/sites.py:16 -msgid "Site" +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." msgstr "" -#: module/page/extensions/symlinks.py:15 -msgid "symlinked page" -msgstr "legătură pagină" - -#: module/page/extensions/symlinks.py:16 -msgid "All content is inherited from this page if given." -msgstr "Întreg conținutul este moștenit de la această pagină daca se dorește." - -#: module/page/extensions/symlinks.py:30 -#, fuzzy -msgid "Symlinked page" -msgstr "legătură pagină" - -#: module/page/extensions/titles.py:13 -msgid "content title" -msgstr "titlu conținut" - -#: module/page/extensions/titles.py:14 -msgid "The first line is the main title, the following lines are subtitles." -msgstr "Prima linie reprezintă titlul principal, următoarele sunt subtitluri." - -#: module/page/extensions/titles.py:15 -msgid "page title" -msgstr "titlul paginii" +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "URL în cache" -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." +#: module/page/models.py:298 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" msgstr "" -"Titlul paginii pentru fereastra navigatorului. Implicit este la fel cu " -"titlul paginii." - -#: module/page/extensions/titles.py:43 -#, fuzzy -msgid "Titles" -msgstr "titlu" - -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "Modifică translatarea" - -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "Creează o translatare" - -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "translații" -#: templates/admin/filter.html:3 -#, python-format -msgid " By %(filter_title)s " -msgstr "După %(filter_title)s" +#: module/page/models.py:426 +msgid "page" +msgstr "pagină" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "Căutare" +#: module/page/models.py:427 +msgid "pages" +msgstr "pagini" #: templates/admin/feincms/_messages_js.html:4 msgid "Really delete item?" @@ -771,16 +806,12 @@ msgstr "Doriți ștergerea șablonului?
    Toate modificarile au fost salvate #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s is moved to %%(target_region)s." msgstr "" -"Doriți ștergerea șablonului?
    Toate schimbările au fost salvate și " -"conținutul de la %(source_regions)s a fost mutat la " -"%(target_region)s." #: templates/admin/feincms/_messages_js.html:12 -#, fuzzy msgid "Hide" -msgstr "film" +msgstr "" #: templates/admin/feincms/_messages_js.html:13 msgid "Show" @@ -795,15 +826,18 @@ msgid "Before" msgstr "" #: templates/admin/feincms/_messages_js.html:16 -#, fuzzy msgid "Insert new:" -msgstr "Inssereaza după" +msgstr "" + +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "Regiunea este goală" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." @@ -811,46 +845,63 @@ msgstr "" "Conținutul de la pagina părinte este moștenită automat. Pentru a suprascrie " "acest lucru, adăugați conținut" -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "Adaugă un obiect nou" -#: templates/admin/feincms/fe_editor.html:46 +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "" + +#: templates/admin/feincms/fe_editor.html:30 msgid "Save" msgstr "Salvează" -#: templates/admin/feincms/fe_tools.html:27 +#: templates/admin/feincms/fe_tools.html:28 msgid "Stop Editing" msgstr "" -#: templates/admin/feincms/fe_tools.html:32 +#: templates/admin/feincms/fe_tools.html:33 msgid "edit" msgstr "modifică" -#: templates/admin/feincms/fe_tools.html:34 +#: templates/admin/feincms/fe_tools.html:35 msgid "new" msgstr "nou" -#: templates/admin/feincms/fe_tools.html:35 +#: templates/admin/feincms/fe_tools.html:36 msgid "up" msgstr "sus" -#: templates/admin/feincms/fe_tools.html:36 +#: templates/admin/feincms/fe_tools.html:37 msgid "down" msgstr "jos" -#: templates/admin/feincms/fe_tools.html:37 +#: templates/admin/feincms/fe_tools.html:38 msgid "remove" msgstr "scoate" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:16 +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "" + +#: templates/admin/feincms/page/page/item_editor.html:23 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 msgid "Home" msgstr "Acasă" -#: templates/admin/feincms/recover_form.html:10 +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "" + +#: templates/admin/feincms/recover_form.html:11 #, python-format msgid "Recover deleted %(verbose_name)s" msgstr "" @@ -859,49 +910,44 @@ msgstr "" msgid "Press the save button below to recover this version of the object." msgstr "" -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:12 msgid "History" msgstr "" -#: templates/admin/feincms/revision_form.html:15 +#: templates/admin/feincms/revision_form.html:13 #, python-format msgid "Revert %(verbose_name)s" msgstr "" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:24 msgid "Press the save button below to revert to this version of the object." msgstr "" -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "Scurtături" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" msgstr "Închide arborele" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" msgstr "Deschide arborele" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "Filtrează" -#: templates/admin/feincms/page/page/item_editor.html:9 -#, fuzzy -msgid "Edit on site" -msgstr "Vezi pe site" - -#: templates/admin/feincms/page/page/item_editor.html:20 -msgid "Add" -msgstr "" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr "După %(filter_title)s" #: templates/admin/medialibrary/add_to_category.html:5 #: templates/admin/medialibrary/add_to_category.html:9 -#, fuzzy msgid "Add media files to category" -msgstr "traducere fișier media" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:11 msgid "Select category to apply:" @@ -912,19 +958,22 @@ msgid "The following media files will be added to the selected category:" msgstr "" #: templates/admin/medialibrary/add_to_category.html:22 -#, fuzzy msgid "Add to category" -msgstr "categorie" +msgstr "" #: templates/admin/medialibrary/add_to_category.html:23 msgid "Cancel" msgstr "" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:11 msgid "Bulk upload a ZIP file:" msgstr "" #: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 msgid "Send" msgstr "" @@ -958,151 +1007,26 @@ msgstr "Trimite" msgid "Thanks!" msgstr "Mulțumesc!" -#, fuzzy -#~ msgid "rich text (ckeditor)" -#~ msgstr "text formatabil" - -#, fuzzy -#~ msgid "rich texts (ckeditor)" -#~ msgstr "texte formatabile" - -#~ msgid "You may edit the copied page below." -#~ msgstr "Puteți modifica pagina copiată mai jos." - -#~ msgid "" -#~ "You have replaced %s. You may continue editing the now-active page below." -#~ msgstr "Ai înlocuit %s. Poți continua să modifici actuala pagină activă." - -#, fuzzy -#~ msgid "Move" -#~ msgstr "scoate" - -#~ msgid "Replace page %(to_replace)s" -#~ msgstr "Înlocuieste pagina %(to_replace)s" - -#~ msgid "Create hidden copy of this page" -#~ msgstr "Creează o copie ascunsă a paginii" - -#~ msgid "The %(name)s \"%(obj)s\" was changed successfully." -#~ msgstr "%(name)s \"%(obj)s\" a fost modificat cu succes." - -#~ msgid "You may edit it again below." -#~ msgstr "Poți să-l modifici din nou." - -#~ msgid "You may add another %s below." -#~ msgstr "Poți adăuga %s incă odata mai jos." - -#~ msgid "Cut" -#~ msgstr "Taie" - -#~ msgid "Insert as child" -#~ msgstr "Inserează ca și copil" - -#~ msgid "Insert before" -#~ msgstr "Inserează inainte" - -#~ msgid "is visible" -#~ msgstr "este vizibil" - -#~ msgid "Video from unknown portal" -#~ msgstr "Film de pe un portal necunoscut" - -#~ msgid "Tree saved successfully." -#~ msgstr "Arbore salvat cu succes." - -#~ msgid "Cannot make a node a child of itself." -#~ msgstr "Un nod nu poate fi făcut un copil al său." - -#~ msgid "No item has been selected." -#~ msgstr "Niciun obiect nu a fost selectat." - -#~ msgid "Delete" -#~ msgstr "Șterge" - -#~ msgid "Save and add another" -#~ msgstr "Salvează si adaugă alta" - -#~ msgid "Save and continue editing" -#~ msgstr "Salvează și continuă să modifici" - -#~ msgid "Please correct the error below." -#~ msgid_plural "Please correct the errors below." -#~ msgstr[0] "Vă rugăm să corectați eroarea de mai jos" -#~ msgstr[1] "Vă rugăm să corectați erorile de mai jos" - -#~ msgid "Properties" -#~ msgstr "Proprietăți" - -#~ msgid "Move selected item to" -#~ msgstr "Mută obiectul selectat la" - -#~ msgid "OK" -#~ msgstr "OK" - -#~ msgid "Add %(name)s" -#~ msgstr "Adaugă %(name)s" - -#~ msgid "Select an item on the left side if you want to edit it." -#~ msgstr "Selectați un obiect din partea stângă dacă doriți să-l modificați" - -#~ msgid "" -#~ "You can change the structure of the tree by drag-dropping elements. " -#~ "Please note that changes will be saved immediately. " -#~ msgstr "" -#~ "Se poate schimba structura arborelui prin tragere și lăsare ( mutare ) a " -#~ "unui element. Luați notă faptul că aceste schimbări for fi salvate imediat" - -#~ msgid "" -#~ "The context menu on the tree root and tree nodes provide you with " -#~ "additional modes of operation." -#~ msgstr "" -#~ "Meniul contextual al arborelui rădăcină și ale nodurilor pune la " -#~ "dispoziție moduri de operare adiționale." - -#~ msgid "Cannot remove this frame while inside this admin section." -#~ msgstr "" -#~ "Acestă zonă nu se poate șterge atâta timp cât se află în această secțiune " -#~ "a administrării." - -#~ msgid "Reload" -#~ msgstr "Reîncarcă" - -#~ msgid "Edit" -#~ msgstr "Modifică" - -#~ msgid "Database error" -#~ msgstr "Eroare la baza de date" - -#~ msgid "view content" -#~ msgstr "vizualizează conținutul" - -#~ msgid "" -#~ "Could not parse the view content because the view is excluded from " -#~ "infanta handling." -#~ msgstr "" -#~ "Nu s-a putut parcurge view-ul deoarec view-ul este exclus din " -#~ "manipularea infantei." - -#~ msgid "Placeholder for the %(viewname)s calling %(viewfunc)s" -#~ msgstr "Cadru pentru %(viewname)s, care cheamă %(viewfunc)s" +#~ msgid "plain" +#~ msgstr "simplu" -#~ msgid "Placeholder for calling %(viewfunc)s" -#~ msgstr "Cadru pentru chemarea %(viewfunc)s" +#~ msgid "title row" +#~ msgstr "titlu" -#~ msgid "not active" -#~ msgstr "inactiv" +#~ msgid "title row and column" +#~ msgstr "titlu rând și coloană" -#~ msgid "Save tree" -#~ msgstr "Salvează arborele" +#~ msgid "table" +#~ msgstr "tabel" -#~ msgid "%(icon)s (not active)" -#~ msgstr "%(icon)s (inactiv)" +#~ msgid "tables" +#~ msgstr "tabele" -#~ msgid "%(icon)s (until %(from)s)" -#~ msgstr "%(icon)s (până la %(from)s)" +#~ msgid "data" +#~ msgstr "data" -#~ msgid "%(icon)s (since %(to)s)" -#~ msgstr "%(icon)s (de %(to)s)" +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Va fi inserat la lista predefinită de cuvinte cheie." -#~ msgid "%(icon)s (%(from)s – %(to)s)" -#~ msgstr "%(icon)s (%(de la)s – %(până la)s)" +#~ msgid "This will be prepended to the default description." +#~ msgstr "Va fi inserată la descrierea predefinită." diff --git a/feincms/locale/ru/LC_MESSAGES/django.mo b/feincms/locale/ru/LC_MESSAGES/django.mo index 7f1b8ccf2..2a4485338 100644 Binary files a/feincms/locale/ru/LC_MESSAGES/django.mo and b/feincms/locale/ru/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/ru/LC_MESSAGES/django.po b/feincms/locale/ru/LC_MESSAGES/django.po index 1549e957f..a6dbeea9a 100644 --- a/feincms/locale/ru/LC_MESSAGES/django.po +++ b/feincms/locale/ru/LC_MESSAGES/django.po @@ -1,301 +1,212 @@ -# Feincms Russian translation. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# Feincms Russian translation file +# Copyright (C) 2017 # This file is distributed under the same license as the feincms package. -# Mikhail Korobov , 2009. # +# Translators: +# Mikhail Korobov , 2009 +# Denis Popov , 2014 +# Alexander Paramonov , 2017 msgid "" msgstr "" -"Project-Id-Version: \n" +"Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:19-0500\n" -"PO-Revision-Date: \n" -"Last-Translator: Mikhail Korobov \n" -"Language-Team: RUSSIAN\n" -"Language: \n" +"POT-Creation-Date: 2017-10-24 17:10+0300\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Alexander Paramonov \n" +"Language-Team: Russian (http://www.transifex.com/projects/p/feincms/language/" +"ru/)\n" +"Language: ru\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" +"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" -#: models.py:421 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "шаблон" - -#: models.py:553 -msgid "ordering" -msgstr "сортировка" - -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "язык" - -#: admin/filterspecs.py:46 admin/filterspecs.py:84 +#: admin/filters.py:49 admin/filters.py:100 msgid "All" msgstr "Все" -#: admin/filterspecs.py:57 module/page/models.py:261 +#: admin/filters.py:60 module/page/models.py:165 msgid "Parent" msgstr "Родитель" -#: admin/filterspecs.py:95 +#: admin/filters.py:111 +#: templates/admin/medialibrary/mediafile/change_list.html:13 msgid "Category" msgstr "Категория" -#: admin/item_editor.py:150 -#, python-format -msgid "Change %s" -msgstr "Изменить %s" - -#: admin/tree_editor.py:218 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:32 -#: module/medialibrary/models.py:48 module/page/models.py:259 -#: module/page/models.py:338 +#: admin/tree_editor.py:281 content/section/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:159 +#: module/page/models.py:232 msgid "title" msgstr "заголовок" -#: admin/tree_editor.py:396 +#: admin/tree_editor.py:328 admin/tree_editor.py:345 +msgid "You do not have permission to modify this object" +msgstr "У вас нет прав для изменения этого объекта" + +#: admin/tree_editor.py:461 +msgid "No permission" +msgstr "Нет прав" + +#: admin/tree_editor.py:479 #, python-format msgid "%s has been moved to a new position." msgstr "Узел \"%s\" был перемещен на новое место." -#: admin/tree_editor.py:400 +#: admin/tree_editor.py:482 msgid "Did not understand moving instruction." msgstr "Перемещение не удалось. Непонятная команда." -#: admin/tree_editor.py:409 +#: admin/tree_editor.py:492 msgid "actions" msgstr "действия" -#: content/application/models.py:241 +#: admin/tree_editor.py:521 +#, python-format +msgid "Successfully deleted %(count)d items." +msgstr "Успешно удалено %(count)d объектов" + +#: admin/tree_editor.py:534 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "Удалить выбранные %(verbose_name_plural)s" + +#: content/application/models.py:185 msgid "application content" -msgstr "" +msgstr "Данные приложения" -#: content/application/models.py:242 +#: content/application/models.py:186 msgid "application contents" -msgstr "" +msgstr "Данные приложений" -#: content/application/models.py:273 +#: content/application/models.py:217 msgid "application" msgstr "приложение" -#: content/comments/models.py:28 -msgid "enabled" -msgstr "включены" - -#: content/comments/models.py:28 -msgid "New comments may be added" -msgstr "" - -#: content/comments/models.py:32 content/comments/models.py:33 -#, fuzzy -msgid "comments" -msgstr "содержание" - -#: content/comments/models.py:48 -#, fuzzy -msgid "public" -msgstr "опубликовано" - -#: content/comments/models.py:48 -msgid "not public" -msgstr "" - -#: content/contactform/models.py:18 +#: content/contactform/models.py:21 msgid "name" msgstr "имя" -#: content/contactform/models.py:19 +#: content/contactform/models.py:22 msgid "email" -msgstr "" +msgstr "email" -#: content/contactform/models.py:20 +#: content/contactform/models.py:23 msgid "subject" msgstr "тема" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:27 content/raw/models.py:16 msgid "content" msgstr "содержание" -#: content/contactform/models.py:34 +#: content/contactform/models.py:38 msgid "contact form" msgstr "форма обратной связи" -#: content/contactform/models.py:35 +#: content/contactform/models.py:39 msgid "contact forms" msgstr "формы обратной связи" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:90 +#: content/file/models.py:23 content/file/models.py:28 +#: content/filer/models.py:50 content/filer/models.py:56 +#: module/medialibrary/models.py:96 msgid "file" msgstr "файл" -#: content/file/models.py:21 +#: content/file/models.py:29 content/filer/models.py:57 msgid "files" msgstr "файлы" -#: content/image/models.py:24 content/image/models.py:28 +#: content/filer/models.py:86 content/filer/models.py:102 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "картинка" -#: content/image/models.py:29 -msgid "images" -msgstr "картинки" - -#: content/image/models.py:42 content/medialibrary/models.py:105 -#: content/medialibrary/models.py:113 -msgid "position" -msgstr "расположение" - -#: content/medialibrary/models.py:38 -msgid "(no caption)" -msgstr "(без подписи)" - -#: content/medialibrary/models.py:87 content/medialibrary/models.py:101 -#: content/medialibrary/models.py:111 content/medialibrary/v2.py:44 -#: content/section/models.py:48 module/medialibrary/fields.py:45 -#: module/medialibrary/models.py:102 -msgid "media file" -msgstr "" +#: content/filer/models.py:88 content/image/models.py:51 +#: module/medialibrary/models.py:263 +msgid "caption" +msgstr "подпись" -#: content/medialibrary/models.py:88 content/medialibrary/v2.py:45 -#: module/medialibrary/models.py:103 -msgid "media files" +#: content/filer/models.py:93 +msgid "URL" msgstr "" -#: content/medialibrary/models.py:131 -msgid "block" -msgstr "блок" - -#: content/medialibrary/models.py:132 -msgid "left" -msgstr "слева" - -#: content/medialibrary/models.py:133 -msgid "right" -msgstr "справа" +#: content/filer/models.py:103 content/image/models.py:56 +msgid "images" +msgstr "картинки" -#: content/medialibrary/v2.py:49 content/section/models.py:53 -#: content/section/models.py:61 content/table/models.py:81 +#: content/filer/models.py:115 content/section/models.py:59 +#: module/medialibrary/contents.py:60 msgid "type" msgstr "тип" -#: content/raw/models.py:18 -msgid "raw content" -msgstr "" +#: content/image/models.py:49 +msgid "alternate text" +msgstr "alt текст" -#: content/raw/models.py:19 -msgid "raw contents" -msgstr "" +#: content/image/models.py:50 +msgid "Description of image" +msgstr "Описание изображения" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "текст" +#: content/image/models.py:84 +msgid "position" +msgstr "расположение" -#: content/richtext/models.py:26 -msgid "HTML Tidy" -msgstr "" +#: content/image/models.py:92 +msgid "format" +msgstr "формат" -#: content/richtext/models.py:27 -msgid "Ignore the HTML validation warnings" -msgstr "" +#: content/raw/models.py:20 +msgid "raw content" +msgstr "HTML данные" -#: content/richtext/models.py:51 -#, python-format -msgid "" -"HTML validation produced %(count)d warnings. Please review the updated " -"content below before continuing: %(messages)s" -msgstr "" +#: content/raw/models.py:21 +msgid "raw contents" +msgstr "HTML данные" -#: content/richtext/models.py:89 +#: content/richtext/models.py:33 msgid "rich text" -msgstr "" +msgstr "форматированный текст" -#: content/richtext/models.py:90 +#: content/richtext/models.py:34 msgid "rich texts" -msgstr "" - -#: content/rss/models.py:21 -msgid "" -"The rss field is updated several times a day. A change in the title will " -"only be visible on the home page after the next feed update." -msgstr "" +msgstr "форматированные тексты" -#: content/rss/models.py:22 -msgid "link" -msgstr "ссылка" - -#: content/rss/models.py:23 -msgid "pre-rendered content" -msgstr "" - -#: content/rss/models.py:24 -msgid "last updated" -msgstr "последнее обновление" - -#: content/rss/models.py:25 -msgid "max. items" -msgstr "макс. число элементов" - -#: content/rss/models.py:29 -msgid "RSS feed" -msgstr "" +#: content/richtext/models.py:48 content/section/models.py:36 +msgid "text" +msgstr "текст" -#: content/rss/models.py:30 -msgid "RSS feeds" -msgstr "" +#: content/section/models.py:38 module/medialibrary/contents.py:47 +#: module/medialibrary/fields.py:72 module/medialibrary/models.py:114 +msgid "media file" +msgstr "медиа-файл" -#: content/section/models.py:37 -#, fuzzy +#: content/section/models.py:43 msgid "section" -msgstr "действия" +msgstr "секция" -#: content/section/models.py:38 -#, fuzzy +#: content/section/models.py:44 msgid "sections" -msgstr "действия" - -#: content/table/models.py:62 -msgid "plain" -msgstr "" - -#: content/table/models.py:63 -msgid "title row" -msgstr "" - -#: content/table/models.py:65 -msgid "title row and column" -msgstr "" - -#: content/table/models.py:71 -msgid "table" -msgstr "таблица" - -#: content/table/models.py:72 -msgid "tables" -msgstr "таблицы" - -#: content/table/models.py:86 -msgid "data" -msgstr "данные" +msgstr "секции" -#: content/template/models.py:62 -#, fuzzy +#: content/template/models.py:24 msgid "template content" -msgstr "шаблон" +msgstr "содержимое шаблона" -#: content/template/models.py:63 -#, fuzzy +#: content/template/models.py:25 msgid "template contents" +msgstr "содержимое шаблонов" + +#: content/template/models.py:30 models.py:396 +msgid "template" msgstr "шаблон" -#: content/video/models.py:23 +#: content/video/models.py:35 msgid "video link" msgstr "ссылка на видео" -#: content/video/models.py:24 +#: content/video/models.py:37 msgid "" "This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." "com/watch?v=zmj1rpzDRZ0" @@ -303,632 +214,610 @@ msgstr "" "Это должно быть ссылкой на видео, размещенное на youtube или vimeo, " "например, http://www.youtube.com/watch?v=zmj1rpzDRZ0" -#: content/video/models.py:28 +#: content/video/models.py:42 msgid "video" msgstr "видео" -#: content/video/models.py:29 +#: content/video/models.py:43 msgid "videos" msgstr "видео" -#: module/blog/models.py:31 -msgid "published" -msgstr "опубликовано" - -#: module/blog/models.py:33 -msgid "This is used for the generated navigation too." -msgstr "Используется также в сгенерированно навигации." - -#: module/blog/models.py:36 -msgid "published on" -msgstr "опубликовано" - -#: module/blog/models.py:37 -msgid "Will be set automatically once you tick the `published` checkbox above." -msgstr "" -"Будет установлено автоматически, как только Вы отметите пункт \"опубликовано" -"\" выше." - -#: module/blog/models.py:42 -msgid "entry" -msgstr "запись" - -#: module/blog/models.py:43 -msgid "entries" -msgstr "записи" - -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "теги" - -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "" - -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -msgid "Leave this empty for entries in the primary language." -msgstr "Оставьте поле пустым для записей на основном языке (%s)." - -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:45 -msgid "available translations" -msgstr "доступные переводы" +#: contrib/tagging.py:139 +msgid "Tagging" +msgstr "Тегирование" -#: module/extensions/changedate.py:38 +#: extensions/changedate.py:42 msgid "creation date" msgstr "дата создания" -#: module/extensions/changedate.py:39 +#: extensions/changedate.py:44 msgid "modification date" msgstr "дата изменения" -#: module/extensions/ct_tracker.py:134 -#, fuzzy +#: extensions/ct_tracker.py:155 msgid "content types" -msgstr "содержание" +msgstr "типы данных" + +#: extensions/datepublisher.py:99 +msgid "publication date" +msgstr "дата публикации" + +#: extensions/datepublisher.py:103 +msgid "publication end date" +msgstr "дата окончания публикации" + +#: extensions/datepublisher.py:106 +msgid "Leave empty if the entry should stay active forever." +msgstr "Оставьте поле пустым, если запись должна оставаться активной навсегда." -#: module/extensions/featured.py:9 -#, fuzzy +#: extensions/datepublisher.py:141 +msgid "visible from - to" +msgstr "отображается с - по" + +#: extensions/datepublisher.py:152 +msgid "Date-based publishing" +msgstr "Дато-ориентированная публикация" + +#: extensions/featured.py:18 msgid "featured" -msgstr "создан" +msgstr "избранное" -#: module/extensions/featured.py:14 -#, fuzzy +#: extensions/featured.py:24 msgid "Featured" -msgstr "создан" +msgstr "Избранное" -#: module/extensions/seo.py:9 +#: extensions/seo.py:16 msgid "meta keywords" -msgstr "" +msgstr "Ключевые слова (meta keywords)" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "" +#: extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "Ключевые слова игнорируются большинством поисковиков" -#: module/extensions/seo.py:11 +#: extensions/seo.py:20 msgid "meta description" -msgstr "" +msgstr "Описание (meta description)" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." +#: extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." msgstr "" +"Этот текст отображается в выдаче поисковиков. Но он не используется для SEO " +"ранжирования. Текст длиннее 140 символов усекается." -#: module/extensions/seo.py:18 +#: extensions/seo.py:32 msgid "Search engine optimization" +msgstr "Поисковая оптимизация (SEO)" + +#: extensions/translations.py:162 translations.py:283 +msgid "language" +msgstr "язык" + +#: extensions/translations.py:171 +msgid "translation of" +msgstr "перевод" + +#: extensions/translations.py:175 +msgid "Leave this empty for entries in the primary language." +msgstr "Оставьте поле пустым для записей на основном языке." + +#: extensions/translations.py:284 +msgid "Edit translation" +msgstr "Изменить перевод" + +#: extensions/translations.py:291 +msgid "Create translation" +msgstr "Создать перевод" + +#: extensions/translations.py:298 +msgid "translations" +msgstr "переводы" + +#: models.py:524 +msgid "ordering" +msgstr "сортировка" + +#: module/medialibrary/contents.py:48 module/medialibrary/models.py:115 +msgid "media files" +msgstr "медиа-файлы" + +#: module/medialibrary/forms.py:29 +msgid "This would create a loop in the hierarchy" +msgstr "Это создало бы зацикливание в иерархии" + +#: module/medialibrary/forms.py:74 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" msgstr "" +"Невозможно перезаписать с другим типом файла (попытка перезаписать " +"%(old_ext)s с %(new_ext)s)" + +#: module/medialibrary/modeladmins.py:65 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "%(count)d медиа-файл успешно добавлен в %(category)s." +msgstr[1] "%(count)d медиа-файла успешно добавлены в %(category)s." +msgstr[2] "%(count)d медиа-файлов успешно добавлены в %(category)s." + +#: module/medialibrary/modeladmins.py:86 +msgid "Add selected media files to category" +msgstr "Добавить выбранные медиа-файлы в категорию" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file exported as %s" +msgstr "ZIP файл экспортирован как %s" -#: module/medialibrary/models.py:51 +#: module/medialibrary/modeladmins.py:98 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "Экспорт ZIP файла не удался: %s" + +#: module/medialibrary/modeladmins.py:106 +msgid "Export selected media files as zip file" +msgstr "Экспортировать выбранные медиа-файлы как ZIP архив" + +#: module/medialibrary/modeladmins.py:155 +#: templates/admin/feincms/page/page/item_editor.html:11 +msgid "Preview" +msgstr "Предпросмотр" + +#: module/medialibrary/modeladmins.py:159 module/medialibrary/models.py:105 +msgid "file size" +msgstr "размер файла" + +#: module/medialibrary/modeladmins.py:164 module/medialibrary/models.py:102 +msgid "created" +msgstr "создан" + +#: module/medialibrary/modeladmins.py:184 module/medialibrary/models.py:99 +msgid "file type" +msgstr "тип файла" + +#: module/medialibrary/modeladmins.py:207 +msgid "file info" +msgstr "информация о файле" + +#: module/medialibrary/modeladmins.py:219 +#, python-format +msgid "%d files imported" +msgstr "%d файлов импортировано" + +#: module/medialibrary/modeladmins.py:221 +#, python-format +msgid "ZIP import failed: %s" +msgstr "ZIP импорт не удался: %s" + +#: module/medialibrary/modeladmins.py:223 +msgid "No input file given" +msgstr "Не получен входной файл" + +#: module/medialibrary/models.py:50 msgid "parent" msgstr "родитель" -#: module/medialibrary/models.py:53 module/page/models.py:260 +#: module/medialibrary/models.py:52 module/page/models.py:162 msgid "slug" -msgstr "путь в URL" +msgstr "слаг" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:56 msgid "category" msgstr "категория" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:96 +#: module/medialibrary/models.py:57 module/medialibrary/models.py:108 msgid "categories" msgstr "категории" -#: module/medialibrary/models.py:91 module/medialibrary/models.py:182 -msgid "file type" -msgstr "тип файла" - -#: module/medialibrary/models.py:92 module/medialibrary/models.py:117 -msgid "created" -msgstr "создан" - -#: module/medialibrary/models.py:93 +#: module/medialibrary/models.py:103 msgid "copyright" -msgstr "" - -#: module/medialibrary/models.py:94 module/medialibrary/models.py:112 -msgid "file size" -msgstr "размер файла" +msgstr "копирайт" -#: module/medialibrary/models.py:203 -#, fuzzy -msgid "file info" -msgstr "файл" - -#: module/medialibrary/models.py:280 +#: module/medialibrary/models.py:221 msgid "Image" msgstr "Изображение" -#: module/medialibrary/models.py:281 +#: module/medialibrary/models.py:223 msgid "Video" msgstr "Видео" -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:226 msgid "Audio" msgstr "Аудио" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:228 msgid "PDF document" msgstr "Документ PDF" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:229 msgid "Flash" -msgstr "" +msgstr "Флэш" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:230 msgid "Text" msgstr "Текст" -#: module/medialibrary/models.py:286 -#, fuzzy +#: module/medialibrary/models.py:231 msgid "Rich Text" -msgstr "Текст" +msgstr "Форматтированный текст" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:232 msgid "Zip archive" -msgstr "" +msgstr "Zip архив" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:233 msgid "Microsoft Word" msgstr "" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:235 msgid "Microsoft Excel" msgstr "" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:237 msgid "Microsoft PowerPoint" msgstr "" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:239 msgid "Binary" msgstr "Двоичный" -#: module/medialibrary/models.py:307 -msgid "caption" -msgstr "подпись" - -#: module/medialibrary/models.py:308 +#: module/medialibrary/models.py:264 msgid "description" msgstr "описание" -#: module/medialibrary/models.py:311 +#: module/medialibrary/models.py:267 msgid "media file translation" -msgstr "" +msgstr "перевод медиа-файла" -#: module/medialibrary/models.py:312 +#: module/medialibrary/models.py:268 msgid "media file translations" -msgstr "" +msgstr "переводы медиа-файлов" -#: module/medialibrary/models.py:335 -msgid "Preview" -msgstr "Предпросмотр" +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" +msgstr "отрывок" -#: module/medialibrary/models.py:355 -#, python-format -msgid "Successfully added %(count)d media file to %(category)s." -msgid_plural "Successfully added %(count)d media files to %(category)s." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." +msgstr "Добавьте краткий отрывок, резюмирующий содержание страницы." -#: module/medialibrary/models.py:373 -msgid "Add selected media files to category" -msgstr "" +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" +msgstr "Отрывок" -#: module/medialibrary/models.py:440 -#, python-format -msgid "%d files imported" -msgstr "" +#: module/page/extensions/navigation.py:90 +#: module/page/extensions/navigation.py:152 +msgid "navigation extension" +msgstr "расширение навигации" -#: module/medialibrary/models.py:442 -#, python-format -msgid "ZIP file invalid: %s" +#: module/page/extensions/navigation.py:156 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." msgstr "" +"если вам нужно кастомизировать навигацию, выберите модуль, который снабдит " +"эту страницу подстраницами." -#: module/medialibrary/models.py:448 -msgid "No input file given" -msgstr "" +#: module/page/extensions/navigation.py:179 +msgid "Navigation extension" +msgstr "Расширение навигации" -#: module/page/models.py:256 -msgid "active" -msgstr "активная" +#: module/page/extensions/navigationgroups.py:17 +msgid "Default" +msgstr "По умолчанию" -#: module/page/models.py:263 module/page/models.py:711 -msgid "in navigation" -msgstr "в навигации" +#: module/page/extensions/navigationgroups.py:18 +msgid "Footer" +msgstr "Подвал" -#: module/page/models.py:264 -msgid "override URL" -msgstr "" +#: module/page/extensions/navigationgroups.py:25 +msgid "navigation group" +msgstr "навигационная группа" -#: module/page/models.py:265 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." -msgstr "" +#: module/page/extensions/relatedpages.py:20 +msgid "Select pages that should be listed as related content." +msgstr "Выберите страницы, которые должны быть отображены как похожие." -#: module/page/models.py:266 -msgid "redirect to" -msgstr "редирект на" +#: module/page/extensions/relatedpages.py:25 +msgid "Related pages" +msgstr "Похожие страницы" -#: module/page/models.py:267 -msgid "Target URL for automatic redirects." -msgstr "" +#: module/page/extensions/sites.py:21 +msgid "Site" +msgstr "Сайт" -#: module/page/models.py:268 -msgid "Cached URL" -msgstr "" +#: module/page/extensions/symlinks.py:23 +msgid "symlinked page" +msgstr "страница, привязанная символической ссылкой" -#: module/page/models.py:279 -msgid "page" -msgstr "страница" +#: module/page/extensions/symlinks.py:24 +msgid "All content is inherited from this page if given." +msgstr "Все содержимое наследуется с этой страницы (если указана)." -#: module/page/models.py:280 -msgid "pages" -msgstr "страницы" +#: module/page/extensions/titles.py:19 +msgid "content title" +msgstr "Заголовок содержимого" -#: module/page/models.py:297 module/page/models.py:782 -#, fuzzy -msgid "is active" -msgstr "активная" +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." +msgstr "Первая строка — основной заголовок, следующие строки — подзаголовки." -#: module/page/models.py:631 +#: module/page/extensions/titles.py:26 +msgid "page title" +msgstr "заголовок страницы" + +#: module/page/extensions/titles.py:30 +msgid "" +"Page title for browser window. Same as title by default. Must be 69 " +"characters or fewer." +msgstr "" +"Заголовок страницы для окна браузера. По умолчанию = просто заголовку " +"страницы. Должен быть не длиннее 69 символов" + +#: module/page/extensions/titles.py:60 +msgid "Titles" +msgstr "Заголовки" + +#: module/page/forms.py:186 msgid "This URL is already taken by an active page." msgstr "Этот URL уже занят активной страницей." -#: module/page/models.py:649 +#: module/page/forms.py:205 msgid "This URL is already taken by another active page." msgstr "Этот URL уже занят другой активной страницей." -#: module/page/models.py:674 +#: module/page/forms.py:210 +msgid "This page does not allow attachment of child pages" +msgstr "Эта страница не позволяет создание дочерних страниц" + +#: module/page/modeladmins.py:50 msgid "Other options" msgstr "Другие параметры" -#: module/page/models.py:721 +#: module/page/modeladmins.py:88 module/page/models.py:170 +msgid "in navigation" +msgstr "в навигации" + +#: module/page/modeladmins.py:117 module/page/modeladmins.py:119 msgid "Add child page" -msgstr "" +msgstr "Добавить дочернюю страницу" -#: module/page/models.py:723 +#: module/page/modeladmins.py:128 module/page/modeladmins.py:130 +#: templates/admin/feincms/content_inline.html:10 msgid "View on site" +msgstr "Посмотреть на сайте" + +#: module/page/modeladmins.py:150 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "Добавить %(language)s перевод \"%(page)s\"" + +#: module/page/modeladmins.py:185 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." msgstr "" +"Содержание на языке по умолчанию было скопировано в свежесозданную страницу." -#: module/page/models.py:759 +#: module/page/modeladmins.py:201 msgid "You don't have the necessary permissions to edit this object" -msgstr "" +msgstr "Вы не имеете необходимые права на изменение этого объекта" -#: module/page/models.py:774 +#: module/page/modeladmins.py:228 msgid "inherited" -msgstr "" +msgstr "унаследованный" -#: module/page/models.py:778 +#: module/page/modeladmins.py:234 msgid "extensions" msgstr "расширения" -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "дата публикации" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "дата окончания публикации" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "Оставьте поле пустым, если запись должна оставаться активной навсегда." - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" -msgstr "" +#: module/page/modeladmins.py:237 module/page/models.py:213 +msgid "is active" +msgstr "активная?" -#: module/page/extensions/excerpt.py:9 -msgid "excerpt" -msgstr "" +#: module/page/models.py:156 +msgid "active" +msgstr "активная" -#: module/page/extensions/excerpt.py:10 -msgid "Add a brief excerpt summarizing the content of this page." -msgstr "" +#: module/page/models.py:160 +msgid "This title is also used for navigation menu items." +msgstr "Этот заголовок также используется в навигации." -#: module/page/extensions/excerpt.py:12 -msgid "Excerpt" -msgstr "" +#: module/page/models.py:163 +msgid "This is used to build the URL for this page" +msgstr "Это используется для создания URL этой страницы" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 -msgid "navigation extension" -msgstr "" +#: module/page/models.py:172 +msgid "override URL" +msgstr "переопределение URL" -#: module/page/extensions/navigation.py:99 +#: module/page/models.py:174 msgid "" -"Select the module providing subpages for this page if you need to customize " -"the navigation." -msgstr "" - -#: module/page/extensions/navigation.py:112 -msgid "Navigation extension" -msgstr "" - -#: module/page/extensions/relatedpages.py:13 -msgid "Select pages that should be listed as related content." -msgstr "" - -#: module/page/extensions/relatedpages.py:20 -msgid "Related pages" -msgstr "" - -#: module/page/extensions/sites.py:16 -msgid "Site" -msgstr "" - -#: module/page/extensions/symlinks.py:15 -msgid "symlinked page" -msgstr "" - -#: module/page/extensions/symlinks.py:16 -msgid "All content is inherited from this page if given." -msgstr "" - -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" -msgstr "" - -#: module/page/extensions/titles.py:13 -msgid "content title" -msgstr "" - -#: module/page/extensions/titles.py:14 -msgid "The first line is the main title, the following lines are subtitles." +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." msgstr "" +"Переопределение целевой URL. Не забудьте добавить слэш в начале и в конце, " +"если это локальный URL. Это влияет на навигацию и URL дочерних страниц." -#: module/page/extensions/titles.py:15 -msgid "page title" -msgstr "заголовок страницы" +#: module/page/models.py:178 +msgid "redirect to" +msgstr "редирект на" -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." +#: module/page/models.py:181 +msgid "Target URL for automatic redirects or the primary key of a page." msgstr "" -"Заголовок страницы для окна браузера. По умолчанию = просто заголовку " -"страницы." - -#: module/page/extensions/titles.py:43 -msgid "Titles" -msgstr "Заголовки" +"Целевой URL для автоматической переадресации или первичный ключ страницы." -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "Изменить перевод" - -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "Создать перевод" - -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "переводы" +#: module/page/models.py:184 +msgid "Cached URL" +msgstr "Кешированный URL" -#: templates/admin/filter.html:3 +#: module/page/models.py:286 #, python-format -msgid " By %(filter_title)s " +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" msgstr "" +"Этот %(page_class)s использует шаблон-синглтон, и " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "Искать" - -#: templates/admin/feincms/_messages_js.html:4 -msgid "Really delete item?" -msgstr "Вы уверены, что хотите удалить элемент?" - -#: templates/admin/feincms/_messages_js.html:4 -msgid "Confirm to delete item" -msgstr "Подтвердите удаление элемента" +#: module/page/models.py:372 +msgid "page" +msgstr "страница" -#: templates/admin/feincms/_messages_js.html:5 -msgid "Item deleted successfully." -msgstr "Элемент успешно удален." +#: module/page/models.py:373 +msgid "pages" +msgstr "страницы" #: templates/admin/feincms/_messages_js.html:5 -msgid "Cannot delete item" -msgstr "Не выходит удалить элемент" +msgid "Really delete item?" +msgstr "Вы уверены, что хотите удалить элемент?" #: templates/admin/feincms/_messages_js.html:6 -msgid "Cannot delete item, because it is parent of at least one other item." -msgstr "" -"Не выходит удалить элемент, т.к. он является родителем как минимум для " -"одного другого элемента." +msgid "Really change template? All changes are saved." +msgstr "Точно сменить шаблон? Все изменения сохранены." #: templates/admin/feincms/_messages_js.html:7 -msgid "Change template" -msgstr "Изменить шаблон" - -#: templates/admin/feincms/_messages_js.html:8 -msgid "Really change template?
    All changes are saved." -msgstr "Точно сменить шаблон?
    Все изменения сохранены." - -#: templates/admin/feincms/_messages_js.html:9 #, python-format msgid "" -"Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"Really change template? All changes are saved and content from " +"%%(source_regions)s is moved to %%(target_region)s." msgstr "" +"Действительно изменить шаблон? Все изменения сохранены и контент из " +"%%(source_regions)s перенесён в %%(target_region)s." -#: templates/admin/feincms/_messages_js.html:12 -msgid "Hide" -msgstr "Скрыть" - -#: templates/admin/feincms/_messages_js.html:13 -msgid "Show" -msgstr "Покзать" - -#: templates/admin/feincms/_messages_js.html:14 -msgid "After" -msgstr "После" - -#: templates/admin/feincms/_messages_js.html:15 -msgid "Before" -msgstr "До" +#: templates/admin/feincms/_messages_js.html:8 +msgid "Move to region:" +msgstr "Перенести в регион:" -#: templates/admin/feincms/_messages_js.html:16 -msgid "Insert new:" -msgstr "Вставить перед этим элементом" +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "Скопировать оригинальное содержимое" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "Регион пуст" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." msgstr "" +"Контент автоматически унаследован от родительского узла. Чтобы " +"переопределить это поведение, добавьте содержимое." -#: templates/admin/feincms/content_editor.html:23 -msgid "Add new item" -msgstr "Добавить элемент" - -#: templates/admin/feincms/fe_editor.html:46 -msgid "Save" -msgstr "Сохранить" - -#: templates/admin/feincms/fe_tools.html:27 -msgid "Stop Editing" -msgstr "Закончить редактирование" - -#: templates/admin/feincms/fe_tools.html:32 -msgid "edit" -msgstr "редактировать" +#: templates/admin/feincms/content_inline.html:8 +msgid "Change" +msgstr "Изменить" -#: templates/admin/feincms/fe_tools.html:34 -msgid "new" -msgstr "новый" +#: templates/admin/feincms/content_inline.html:27 +msgid "Remove" +msgstr "Удалить" -#: templates/admin/feincms/fe_tools.html:35 -msgid "up" -msgstr "вверх" +#: templates/admin/feincms/content_inline.html:28 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "Добавить еще %(verbose_name)s" -#: templates/admin/feincms/fe_tools.html:36 -msgid "down" -msgstr "вниз" +#: templates/admin/feincms/content_type_selection_widget.html:3 +msgid "Insert new content:" +msgstr "Вставить новый контент:" -#: templates/admin/feincms/fe_tools.html:37 -msgid "remove" -msgstr "удалить" +#: templates/admin/feincms/item_editor.html:32 +msgid "available translations" +msgstr "доступные переводы" -#: templates/admin/feincms/recover_form.html:7 -#: templates/admin/feincms/revision_form.html:10 -#: templates/admin/feincms/page/page/item_editor.html:16 +#: templates/admin/feincms/page/page/item_editor.html:19 #: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:7 +#: templates/admin/feincms/revision_form.html:7 msgid "Home" msgstr "Начало" +#: templates/admin/feincms/page/page/item_editor.html:23 +msgid "Add" +msgstr "Добавить" + #: templates/admin/feincms/recover_form.html:10 #, python-format msgid "Recover deleted %(verbose_name)s" -msgstr "" +msgstr "Восстановить удаленные %(verbose_name)s" -#: templates/admin/feincms/recover_form.html:17 +#: templates/admin/feincms/recover_form.html:16 msgid "Press the save button below to recover this version of the object." -msgstr "" +msgstr "Нажмите кнопку \"Сохранить\" ниже, чтобы восстановить эту версию объекта." -#: templates/admin/feincms/revision_form.html:14 +#: templates/admin/feincms/revision_form.html:11 msgid "History" -msgstr "" +msgstr "История" -#: templates/admin/feincms/revision_form.html:15 +#: templates/admin/feincms/revision_form.html:12 #, python-format msgid "Revert %(verbose_name)s" -msgstr "" +msgstr "Восстановить %(verbose_name)s" -#: templates/admin/feincms/revision_form.html:28 +#: templates/admin/feincms/revision_form.html:23 msgid "Press the save button below to revert to this version of the object." -msgstr "" +msgstr "Нажмите кнопку \"Сохранить\" ниже, чтобы восстановить эту версию объекта." -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/tree_editor.html:21 msgid "Shortcuts" -msgstr "" +msgstr "Управление деревом" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:23 msgid "Collapse tree" -msgstr "Свернуть дерево" +msgstr "Свернуть" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:24 msgid "Expand tree" -msgstr "Развернуть дерево" +msgstr "Развернуть" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:28 msgid "Filter" -msgstr "" +msgstr "Фильтр" -#: templates/admin/feincms/page/page/item_editor.html:9 -msgid "Edit on site" -msgstr "Редактировать на сайте" - -#: templates/admin/feincms/page/page/item_editor.html:20 -msgid "Add" -msgstr "" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr " По %(filter_title)s " #: templates/admin/medialibrary/add_to_category.html:5 #: templates/admin/medialibrary/add_to_category.html:9 msgid "Add media files to category" -msgstr "" +msgstr "Добавить медиа-файлы в категорию" #: templates/admin/medialibrary/add_to_category.html:11 msgid "Select category to apply:" -msgstr "" +msgstr "Выберите категорию для применения:" #: templates/admin/medialibrary/add_to_category.html:17 msgid "The following media files will be added to the selected category:" -msgstr "" +msgstr "Следующие медиа-файлы будут добавлены в выбранную категорию:" #: templates/admin/medialibrary/add_to_category.html:22 -#, fuzzy msgid "Add to category" -msgstr "категория" +msgstr "Добавить в категорию" #: templates/admin/medialibrary/add_to_category.html:23 msgid "Cancel" -msgstr "" +msgstr "Отмена" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/mediafile/change_list.html:10 msgid "Bulk upload a ZIP file:" msgstr "Загрузить несколько файлов в ZIP-архиве:" -#: templates/admin/medialibrary/mediafile/change_list.html:21 +#: templates/admin/medialibrary/mediafile/change_list.html:20 +msgid "Overwrite" +msgstr "Перезаписать" + +#: templates/admin/medialibrary/mediafile/change_list.html:23 msgid "Send" msgstr "Отправить" -#: templates/content/comments/default.html:10 -#, python-format -msgid "%(comment_count)s comments." -msgstr "%(comment_count)s комментариев." - -#: templates/content/comments/default.html:18 -#, python-format -msgid "" -"\n" -" %(comment_username)s said on %(comment_submit_date)s
    \n" -" " -msgstr "" -"\n" -" Пользователь %(comment_username)s сказал " -"(%(comment_submit_date)s)
    \n" -" " - -#: templates/content/comments/default.html:28 -msgid "No comments." -msgstr "Комментариев нет." - -#: templates/content/comments/default.html:36 -msgid "Post Comment" -msgstr "Отправить комментарий" - #: templates/content/contactform/form.html:9 msgid "Submit" msgstr "Отправить" @@ -937,14 +826,164 @@ msgstr "Отправить" msgid "Thanks!" msgstr "Спасибо!" -#~ msgid "You may edit the copied page below." -#~ msgstr "Вы можете отредактировать скопированную страницу ниже." +#~ msgid "enabled" +#~ msgstr "включены" + +#~ msgid "New comments may be added" +#~ msgstr "Новые комментарии могут быть добавлены" + +#~ msgid "comments" +#~ msgstr "комментарии" + +#~ msgid "public" +#~ msgstr "Опубликовано" + +#~ msgid "not public" +#~ msgstr "Не опубликовано" + +#~ msgid "HTML Tidy" +#~ msgstr "HTML Tidy" + +#~ msgid "Ignore the HTML validation warnings" +#~ msgstr "Игнорировать предупреждения при валидации HTML" + +#~ msgid "" +#~ "HTML validation produced %(count)d warnings. Please review the updated " +#~ "content below before continuing: %(messages)s" +#~ msgstr "" +#~ "HTML валидация вызвала %(count)d предупреждений. Пожалуйста просмотрите " +#~ "ниже обновленное содержимое прежде чем продолжить: %(messages)s" + +#~ msgid "" +#~ "The rss field is updated several times a day. A change in the title will " +#~ "only be visible on the home page after the next feed update." +#~ msgstr "" +#~ "RSS поле обновляется несколько раз в день. Изменение в названии будет " +#~ "видно на главной странице только после следующего обновления фида" + +#~ msgid "link" +#~ msgstr "ссылка" + +#~ msgid "pre-rendered content" +#~ msgstr "предподготовленное содержимое" + +#~ msgid "last updated" +#~ msgstr "последнее обновление" + +#~ msgid "max. items" +#~ msgstr "макс. число элементов" + +#~ msgid "RSS feed" +#~ msgstr "RSS фид" + +#~ msgid "RSS feeds" +#~ msgstr "RSS фиды" + +#~ msgid "tags" +#~ msgstr "теги" + +#~ msgid "published" +#~ msgstr "опубликовано" + +#~ msgid "This is used for the generated navigation too." +#~ msgstr "Используется также в сгенерированно навигации." + +#~ msgid "published on" +#~ msgstr "опубликовано" + +#~ msgid "" +#~ "Will be set automatically once you tick the `published` checkbox above." +#~ msgstr "" +#~ "Будет установлено автоматически, как только Вы отметите пункт " +#~ "\"опубликовано\" выше." + +#~ msgid "entry" +#~ msgstr "запись" + +#~ msgid "entries" +#~ msgstr "записи" + +#~ msgid "Confirm to delete item" +#~ msgstr "Подтвердите удаление элемента" + +#~ msgid "Item deleted successfully." +#~ msgstr "Элемент успешно удален." + +#~ msgid "Cannot delete item" +#~ msgstr "Не выходит удалить элемент" + +#~ msgid "Cannot delete item, because it is parent of at least one other item." +#~ msgstr "" +#~ "Не выходит удалить элемент, т.к. он является родителем как минимум для " +#~ "одного другого элемента." + +#~ msgid "Change template" +#~ msgstr "Изменить шаблон" + +#~ msgid "Hide" +#~ msgstr "Скрыть" + +#~ msgid "Show" +#~ msgstr "Покзать" + +#~ msgid "After" +#~ msgstr "После" + +#~ msgid "Before" +#~ msgstr "До" + +#~ msgid "Add new item" +#~ msgstr "Добавить элемент" + +#~ msgid "Save" +#~ msgstr "Сохранить" + +#~ msgid "Stop Editing" +#~ msgstr "Закончить редактирование" + +#~ msgid "edit" +#~ msgstr "редактировать" + +#~ msgid "new" +#~ msgstr "новый" + +#~ msgid "up" +#~ msgstr "вверх" + +#~ msgid "down" +#~ msgstr "вниз" + +#~ msgid "remove" +#~ msgstr "удалить" + +#~ msgid "Edit on site" +#~ msgstr "Редактировать на сайте" + +#~ msgid "%(comment_count)s comments." +#~ msgstr "%(comment_count)s комментариев." + +#~ msgid "" +#~ "\n" +#~ " %(comment_username)s said on " +#~ "%(comment_submit_date)s
    \n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ " Пользователь %(comment_username)s сказал " +#~ "(%(comment_submit_date)s)
    \n" +#~ " " + +#~ msgid "No comments." +#~ msgstr "Комментариев нет." + +#~ msgid "Post Comment" +#~ msgstr "Отправить комментарий" -#~ msgid "Remove" -#~ msgstr "Удалить" +#~ msgid "table" +#~ msgstr "таблица" -#~ msgid "Replace page %(to_replace)s" -#~ msgstr "Заменить страницу %(to_replace)s" +#~ msgid "tables" +#~ msgstr "таблицы" -#~ msgid "Create hidden copy of this page" -#~ msgstr "Создать скрытую копию этой страницы" +#~ msgid "data" +#~ msgstr "данные" diff --git a/feincms/locale/tr/LC_MESSAGES/django.mo b/feincms/locale/tr/LC_MESSAGES/django.mo new file mode 100644 index 000000000..878620bbb Binary files /dev/null and b/feincms/locale/tr/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/tr/LC_MESSAGES/django.po b/feincms/locale/tr/LC_MESSAGES/django.po new file mode 100644 index 000000000..eb8a8e7c1 --- /dev/null +++ b/feincms/locale/tr/LC_MESSAGES/django.po @@ -0,0 +1,1042 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# byzgl , 2013 +# byzgl , 2013 +msgid "" +msgstr "" +"Project-Id-Version: FeinCMS\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Matthias Kestenholz \n" +"Language-Team: Turkish (http://www.transifex.com/projects/p/feincms/language/" +"tr/)\n" +"Language: tr\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/filterspecs.py:47 admin/filterspecs.py:86 +msgid "All" +msgstr "Tümü" + +#: admin/filterspecs.py:58 module/page/models.py:178 +msgid "Parent" +msgstr "Üst Öğe" + +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 +msgid "Category" +msgstr "Kategori" + +#: admin/item_editor.py:190 +#, python-format +msgid "Change %s" +msgstr "Değişiklik %s" + +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 +msgid "title" +msgstr "başlık" + +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +#, fuzzy +#| msgid "You don't have the necessary permissions to edit this object" +msgid "You do not have permission to modify this object" +msgstr "Bu nesneye düzenlemek için gerekli izinleriniz yok" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 +#, python-format +msgid "%s has been moved to a new position." +msgstr "%s yeni bir pozisyona taşındı." + +#: admin/tree_editor.py:512 +msgid "Did not understand moving instruction." +msgstr "Taşıma yönergeleri anlaşılamadı." + +#: admin/tree_editor.py:523 +msgid "actions" +msgstr "olaylar" + +#: admin/tree_editor.py:552 +#, fuzzy, python-format +#| msgid "Successfully deleted %s items." +msgid "Successfully deleted %(count)d items." +msgstr "%s öğeleri başarıyla silindi." + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "%(verbose_name_plural)s seçilenleri sil" + +#: content/application/models.py:147 +msgid "application content" +msgstr "uygulama içeriği" + +#: content/application/models.py:148 +msgid "application contents" +msgstr "uygulama içerikleri" + +#: content/application/models.py:179 +msgid "application" +msgstr "uygulama" + +#: content/comments/models.py:32 +msgid "enabled" +msgstr "etkin" + +#: content/comments/models.py:33 +msgid "New comments may be added" +msgstr "Yeni yorumlar eklenebilir" + +#: content/comments/models.py:37 content/comments/models.py:38 +msgid "comments" +msgstr "yorumlar" + +#: content/comments/models.py:60 +msgid "public" +msgstr "genel" + +#: content/comments/models.py:61 +msgid "not public" +msgstr "genel değil" + +#: content/contactform/models.py:20 +msgid "name" +msgstr "ad" + +#: content/contactform/models.py:21 +msgid "email" +msgstr "email" + +#: content/contactform/models.py:22 +msgid "subject" +msgstr "konu" + +#: content/contactform/models.py:26 content/raw/models.py:16 +msgid "content" +msgstr "içerik" + +#: content/contactform/models.py:37 +msgid "contact form" +msgstr "iletişim formu" + +#: content/contactform/models.py:38 +msgid "contact forms" +msgstr "iletişim formları" + +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 +msgid "file" +msgstr "dosya" + +#: content/file/models.py:29 +msgid "files" +msgstr "dosyalar" + +#: content/image/models.py:46 content/image/models.py:55 +msgid "image" +msgstr "resim" + +#: content/image/models.py:49 +msgid "alternate text" +msgstr "alternatif yazı" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "Resim açıklaması" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "altyazı" + +#: content/image/models.py:56 +msgid "images" +msgstr "resimler" + +#: content/image/models.py:82 +msgid "position" +msgstr "pozisyon" + +#: content/image/models.py:90 +msgid "format" +msgstr "format" + +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 +msgid "media file" +msgstr "medya dosyası" + +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 +msgid "media files" +msgstr "medya dosyaları" + +#: content/medialibrary/models.py:64 content/section/models.py:59 +msgid "type" +msgstr "tür" + +#: content/raw/models.py:20 +msgid "raw content" +msgstr "ham içerik" + +#: content/raw/models.py:21 +msgid "raw contents" +msgstr "ham içerikler" + +#: content/richtext/models.py:24 +msgid "HTML Tidy" +msgstr "HTML Toparla" + +#: content/richtext/models.py:25 +msgid "Ignore the HTML validation warnings" +msgstr "HTML doğrulama uyarılarını yoksay" + +#: content/richtext/models.py:55 +#, python-format +msgid "" +"HTML validation produced %(count)d warnings. Please review the updated " +"content below before continuing: %(messages)s" +msgstr "" +"HTML doğrulama işlemi %(count)d uyarı. Devam etmeden önce aşağıdaki " +"güncellenen içeriği gözden geçirin: %(messages)s" + +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "yazı" + +#: content/richtext/models.py:103 +msgid "rich text" +msgstr "zengin yazı" + +#: content/richtext/models.py:104 +msgid "rich texts" +msgstr "zengin yazı" + +#: content/rss/models.py:25 +msgid "" +"The rss field is updated several times a day. A change in the title will " +"only be visible on the home page after the next feed update." +msgstr "" +"Rss alanı günde birkaç kez güncellenir. Başlıktaki bir değişiklik sadece bir " +"sonraki besleme güncellemesinden sonra ana sayfasında görülebilir." + +#: content/rss/models.py:28 +msgid "link" +msgstr "link" + +#: content/rss/models.py:30 +msgid "pre-rendered content" +msgstr "önden görüntülenen içerik" + +#: content/rss/models.py:32 +msgid "last updated" +msgstr "son güncelleme" + +#: content/rss/models.py:33 +msgid "max. items" +msgstr "max. öğe" + +#: content/rss/models.py:37 +msgid "RSS feed" +msgstr "RSS besleme" + +#: content/rss/models.py:38 +msgid "RSS feeds" +msgstr "RSS beslemeler" + +#: content/section/models.py:43 +msgid "section" +msgstr "bölüm" + +#: content/section/models.py:44 +msgid "sections" +msgstr "bölümler" + +#: content/template/models.py:53 +msgid "template content" +msgstr "tema içeriği" + +#: content/template/models.py:54 +msgid "template contents" +msgstr "tema içerikleri" + +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "tema" + +#: content/video/models.py:34 +msgid "video link" +msgstr "video linki" + +#: content/video/models.py:36 +msgid "" +"This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." +"com/watch?v=zmj1rpzDRZ0" +msgstr "" +"Bu, bir youtube veya vimeo video bir bağlantı olmalıdır, ör: http://www." +"youtube.com/watch?v=zmj1rpzDRZ0" + +#: content/video/models.py:41 +msgid "video" +msgstr "video" + +#: content/video/models.py:42 +msgid "videos" +msgstr "vidyolar" + +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "Etiketleme" + +#: models.py:550 +msgid "ordering" +msgstr "sıralama" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "etiketler" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "dil" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "tercümesi" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "Ana dilde girişler için boş bırakın." + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "kullanılabilir çeviriler" + +#: module/blog/models.py:33 +msgid "published" +msgstr "yayınlandı" + +#: module/blog/models.py:36 +msgid "This is used for the generated navigation too." +msgstr "Bu da oluşturulan navigasyon için kullanılır." + +#: module/blog/models.py:40 +msgid "published on" +msgstr "yayınlanan tarih" + +#: module/blog/models.py:42 +msgid "Will be set automatically once you tick the `published` checkbox above." +msgstr "" +"Yukarıdaki `yayınlanan` onay kutusunu bir kez işaretleyin otomatik olarak " +"ayarlanır." + +#: module/blog/models.py:48 +msgid "entry" +msgstr "giriş" + +#: module/blog/models.py:49 +msgid "entries" +msgstr "girişler" + +#: module/extensions/changedate.py:41 +msgid "creation date" +msgstr "oluşturulma tarihi" + +#: module/extensions/changedate.py:43 +msgid "modification date" +msgstr "düzenlenme tarihi" + +#: module/extensions/ct_tracker.py:156 +msgid "content types" +msgstr "içerik tipleri" + +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "yayın tarihi" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "yayın bitiş tarihi" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "Giriş sonsuza kadar aktif kalmalı ise boş bırakın." + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "için - görünür " + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "Tarih-bazlı yayınlama" + +#: module/extensions/featured.py:15 +msgid "featured" +msgstr "öne çıkan" + +#: module/extensions/featured.py:21 +msgid "Featured" +msgstr "Öne çıkan" + +#: module/extensions/seo.py:16 +msgid "meta keywords" +msgstr "meta anahtar kelimeler" + +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" + +#: module/extensions/seo.py:20 +msgid "meta description" +msgstr "meta açıklaması" + +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" + +#: module/extensions/seo.py:32 +msgid "Search engine optimization" +msgstr "Arama motoru optimizasyonu" + +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "Çeviriyi düzemle" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "Çeviri oluştur" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "çeviriler" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "Bu hiyerarşi içinde bir döngü yaratacak" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" +"Farklı dosya türü (%(old_ext)s ile bir %(new_ext)s üzerine yazma girişimi) " +"ile üzerine yazamazsınız" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "%(count)d medya dosyaları %(category)s e başarıyla eklendi." +msgstr[1] "%(count)d medya dosyaları %(category)s e başarıyla eklendi." + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "Seçilen medya dosyalarını kategoriye ekle" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "ZIP dosyası %s olarak dışa aktarıldı" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "ZIP dosyasını %s olarak dışa aktarmada hata" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "Seçilen medya dosyalarını ZIP dosyası olarak dışa aktar" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "Önizleme" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "dosya boyutu" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "oluşturuldu" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "dosya tipi" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "dosya bilgisi" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "%d dosya içe aktarıldı" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "ZIP dosyasını %s olarak içe aktarmada hata" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "Dosya girdisi yok" + +#: module/medialibrary/models.py:49 +msgid "parent" +msgstr "üst öğe" + +#: module/medialibrary/models.py:51 module/page/models.py:175 +msgid "slug" +msgstr "kurşun" + +#: module/medialibrary/models.py:55 +msgid "category" +msgstr "kategori" + +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 +msgid "categories" +msgstr "kategoriler" + +#: module/medialibrary/models.py:101 +msgid "copyright" +msgstr "telif hakkı" + +#: module/medialibrary/models.py:219 +msgid "Image" +msgstr "resim" + +#: module/medialibrary/models.py:221 +msgid "Video" +msgstr "Video" + +#: module/medialibrary/models.py:224 +msgid "Audio" +msgstr "Ses" + +#: module/medialibrary/models.py:226 +msgid "PDF document" +msgstr "PDF dökümanı" + +#: module/medialibrary/models.py:227 +msgid "Flash" +msgstr "Flash" + +#: module/medialibrary/models.py:228 +msgid "Text" +msgstr "Yazı" + +#: module/medialibrary/models.py:229 +msgid "Rich Text" +msgstr "Zengin Yazı" + +#: module/medialibrary/models.py:230 +msgid "Zip archive" +msgstr "Zip arşivi" + +#: module/medialibrary/models.py:231 +msgid "Microsoft Word" +msgstr "Microsoft Word" + +#: module/medialibrary/models.py:233 +msgid "Microsoft Excel" +msgstr "Microsoft Excel" + +#: module/medialibrary/models.py:235 +msgid "Microsoft PowerPoint" +msgstr "Microsoft PowerPoint" + +#: module/medialibrary/models.py:237 +msgid "Binary" +msgstr "Binary" + +#: module/medialibrary/models.py:261 +msgid "description" +msgstr "açıklama" + +#: module/medialibrary/models.py:264 +msgid "media file translation" +msgstr "medya dosyası çevirisi" + +#: module/medialibrary/models.py:265 +msgid "media file translations" +msgstr "medya dosyası çevirileri" + +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" +msgstr "alıntı" + +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." +msgstr "Bu sayfanın içeriğini özetleyen kısa bir alıntı ekleyin." + +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" +msgstr "Alıntı" + +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 +msgid "navigation extension" +msgstr "navigasyon bileşeni" + +#: module/page/extensions/navigation.py:119 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." +msgstr "" +"Navigasyonu özelleştirmeniz gerekiyorsa, bu altsayfaları sağlayan modülü " +"seçiniz." + +#: module/page/extensions/navigation.py:134 +msgid "Navigation extension" +msgstr "Navigasyon bileşeni" + +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" +msgstr "" + +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" + +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" +msgstr "navigasyonda" + +#: module/page/extensions/relatedpages.py:21 +msgid "Select pages that should be listed as related content." +msgstr "İlişkili içerik olarak listelenecek sayfaları seçiniz" + +#: module/page/extensions/relatedpages.py:26 +msgid "Related pages" +msgstr "İlgili sayfalar" + +#: module/page/extensions/sites.py:21 +msgid "Site" +msgstr "Site" + +#: module/page/extensions/symlinks.py:22 +msgid "symlinked page" +msgstr "Sembolik olarak bağlanmış sayfa" + +#: module/page/extensions/symlinks.py:23 +msgid "All content is inherited from this page if given." +msgstr "Verilirse tüm içerik bu sayfadan devralınır." + +#: module/page/extensions/titles.py:19 +msgid "content title" +msgstr "içerik başlığı" + +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." +msgstr "İlk satır ana başlık, aşağıdaki satırlar ise alt başlıktır." + +#: module/page/extensions/titles.py:26 +msgid "page title" +msgstr "sayfa başlığı" + +#: module/page/extensions/titles.py:30 +#, fuzzy +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." +msgstr "" +"Tarayıcı penceresi için sayfa başlığı. Varsayılan olarak başlık ile aynı." + +#: module/page/extensions/titles.py:60 +msgid "Titles" +msgstr "Başlıklar" + +#: module/page/forms.py:187 +msgid "This URL is already taken by an active page." +msgstr "Bu URL aktif bir sayfa tarafından alınmış." + +#: module/page/forms.py:206 +msgid "This URL is already taken by another active page." +msgstr "Bu URL zaten başka aktif bir sayfa tarafından alınmış." + +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" + +#: module/page/modeladmins.py:42 +msgid "Other options" +msgstr "Diğer seçenekler" + +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "navigasyonda" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 +msgid "Add child page" +msgstr "Alt sayfa ekle" + +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 +msgid "View on site" +msgstr "Sitede göster" + +#: module/page/modeladmins.py:142 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "Orijinal çeviri içeriği yeni oluşturulan sayfaya kopyalandı." + +#: module/page/modeladmins.py:197 +msgid "You don't have the necessary permissions to edit this object" +msgstr "Bu nesneye düzenlemek için gerekli izinleriniz yok" + +#: module/page/modeladmins.py:223 +msgid "inherited" +msgstr "devralındı" + +#: module/page/modeladmins.py:229 +msgid "extensions" +msgstr "bileşenler" + +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" +msgstr "aktif" + +#: module/page/models.py:169 +msgid "active" +msgstr "aktif" + +#: module/page/models.py:173 +#, fuzzy +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "Bu da oluşturulan navigasyon için kullanılır." + +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" +msgstr "" + +#: module/page/models.py:184 +msgid "override URL" +msgstr "URL geçersiz" + +#: module/page/models.py:186 +msgid "" +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." +msgstr "" +"Hedef URL geçersiz Yerel bir URL ise başında ve sonunda bölü eklemeyi " +"unutmayın. Bu navigasyon ve alt sayfaları etkiler." + +#: module/page/models.py:190 +msgid "redirect to" +msgstr "buraya yönlendiriliyor" + +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." +msgstr "" + +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "Önbelleklenmiş URL" + +#: module/page/models.py:298 +#, python-format +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" +msgstr "" + +#: module/page/models.py:426 +msgid "page" +msgstr "sayfa" + +#: module/page/models.py:427 +msgid "pages" +msgstr "sayfalar" + +#: templates/admin/feincms/_messages_js.html:4 +msgid "Really delete item?" +msgstr "Gerçekten öğe silisin mi?" + +#: templates/admin/feincms/_messages_js.html:4 +msgid "Confirm to delete item" +msgstr "Öğeyi silmeyi onaylayın" + +#: templates/admin/feincms/_messages_js.html:5 +msgid "Item deleted successfully." +msgstr "öğe başarılı bir şekilde silindi." + +#: templates/admin/feincms/_messages_js.html:5 +msgid "Cannot delete item" +msgstr "Öğe silinemiyor" + +#: templates/admin/feincms/_messages_js.html:6 +msgid "Cannot delete item, because it is parent of at least one other item." +msgstr "öğe silinemiyor, çünkü en az bir alt öğeye sahip." + +#: templates/admin/feincms/_messages_js.html:7 +msgid "Change template" +msgstr "Temayı değiştir" + +#: templates/admin/feincms/_messages_js.html:8 +msgid "Really change template?
    All changes are saved." +msgstr "Tema değiştirilsin mi?
    Tüm değişiklikler kaydedildi." + +#: templates/admin/feincms/_messages_js.html:9 +#, python-format +msgid "" +"Really change template?
    All changes are saved and content from " +"%%(source_regions)s is moved to %%(target_region)s." +msgstr "" +"Gerçekten tema değişsin mi?
    Tüm değişiklikler kaydedildi ve " +"%%(source_regions)s teki içerikler %%(target_region)s e taşındı." + +#: templates/admin/feincms/_messages_js.html:12 +msgid "Hide" +msgstr "Gizle" + +#: templates/admin/feincms/_messages_js.html:13 +msgid "Show" +msgstr "Göster" + +#: templates/admin/feincms/_messages_js.html:14 +msgid "After" +msgstr "Sonra" + +#: templates/admin/feincms/_messages_js.html:15 +msgid "Before" +msgstr "Önce" + +#: templates/admin/feincms/_messages_js.html:16 +msgid "Insert new:" +msgstr "Yeni ekle:" + +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 +msgid "Region empty" +msgstr "Bölge boş" + +#: templates/admin/feincms/content_editor.html:25 +msgid "" +"Content from the parent site is automatically inherited. To override this " +"behaviour, add some content." +msgstr "" +"Üst siteden içerik otomatik devralınır. Bu davranışı geçersiz kılmak için, " +"bazı içerikler ekleyin." + +#: templates/admin/feincms/content_editor.html:33 +msgid "Add new item" +msgstr "Yeni öğe ekle" + +#: templates/admin/feincms/content_inline.html:93 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "Başka ekle %(verbose_name)s" + +#: templates/admin/feincms/content_inline.html:96 +msgid "Remove" +msgstr "Kaldır" + +#: templates/admin/feincms/fe_editor.html:30 +msgid "Save" +msgstr "Kaydet" + +#: templates/admin/feincms/fe_tools.html:28 +msgid "Stop Editing" +msgstr "Düzenlemeyi Durdur" + +#: templates/admin/feincms/fe_tools.html:33 +msgid "edit" +msgstr "düzenle" + +#: templates/admin/feincms/fe_tools.html:35 +msgid "new" +msgstr "yeni" + +#: templates/admin/feincms/fe_tools.html:36 +msgid "up" +msgstr "yukarı" + +#: templates/admin/feincms/fe_tools.html:37 +msgid "down" +msgstr "aşağı" + +#: templates/admin/feincms/fe_tools.html:38 +msgid "remove" +msgstr "kaldır" + +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "Sitede düzenle" + +#: templates/admin/feincms/page/page/item_editor.html:23 +#: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 +msgid "Home" +msgstr "Anasayfa" + +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "Ekle" + +#: templates/admin/feincms/recover_form.html:11 +#, python-format +msgid "Recover deleted %(verbose_name)s" +msgstr "Sşilineni kurtar %(verbose_name)s" + +#: templates/admin/feincms/recover_form.html:17 +msgid "Press the save button below to recover this version of the object." +msgstr "Nesnenin bu sürümünü kurtarmak için aşağıdaki kaydet düğmesine basın." + +#: templates/admin/feincms/revision_form.html:12 +msgid "History" +msgstr "Geçmiş" + +#: templates/admin/feincms/revision_form.html:13 +#, python-format +msgid "Revert %(verbose_name)s" +msgstr "Dön %(verbose_name)s" + +#: templates/admin/feincms/revision_form.html:24 +msgid "Press the save button below to revert to this version of the object." +msgstr "Nesnenin bu sürümüne dönmek için aşağıdaki kaydet düğmesine basın." + +#: templates/admin/feincms/tree_editor.html:22 +msgid "Shortcuts" +msgstr "Kısayollar" + +#: templates/admin/feincms/tree_editor.html:24 +msgid "Collapse tree" +msgstr "Ağacı daralt" + +#: templates/admin/feincms/tree_editor.html:25 +msgid "Expand tree" +msgstr "Ağacı genişlet" + +#: templates/admin/feincms/tree_editor.html:29 +msgid "Filter" +msgstr "Filtre" + +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr "%(filter_title)s ile" + +#: templates/admin/medialibrary/add_to_category.html:5 +#: templates/admin/medialibrary/add_to_category.html:9 +msgid "Add media files to category" +msgstr "Kategoriye medya dosyaları ekle" + +#: templates/admin/medialibrary/add_to_category.html:11 +msgid "Select category to apply:" +msgstr "Uygulamak için kategori seçin:" + +#: templates/admin/medialibrary/add_to_category.html:17 +msgid "The following media files will be added to the selected category:" +msgstr "Aşağıdaki ortam dosyaları seçilen kategoriye eklenecektir:" + +#: templates/admin/medialibrary/add_to_category.html:22 +msgid "Add to category" +msgstr "Kategoriye ekle" + +#: templates/admin/medialibrary/add_to_category.html:23 +msgid "Cancel" +msgstr "İptal" + +#: templates/admin/medialibrary/mediafile/change_list.html:11 +msgid "Bulk upload a ZIP file:" +msgstr "Toplu upload ZIP dosyası:" + +#: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "Üzerine yaz" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 +msgid "Send" +msgstr "Gönder" + +#: templates/content/comments/default.html:10 +#, python-format +msgid "%(comment_count)s comments." +msgstr "%(comment_count)s yorum" + +#: templates/content/comments/default.html:18 +#, python-format +msgid "" +"\n" +" %(comment_username)s said on %(comment_submit_date)s
    \n" +" " +msgstr "" +"\n" +" %(comment_username)s dedi buna " +"%(comment_submit_date)s
    \n" +" " + +#: templates/content/comments/default.html:28 +msgid "No comments." +msgstr "Yorum yok." + +#: templates/content/comments/default.html:36 +msgid "Post Comment" +msgstr "Yorum gönder" + +#: templates/content/contactform/form.html:9 +msgid "Submit" +msgstr "Gönder" + +#: templates/content/contactform/thanks.html:3 +msgid "Thanks!" +msgstr "Teşekkürler!" + +#~ msgid "plain" +#~ msgstr "düz" + +#~ msgid "title row" +#~ msgstr "başlık satırı" + +#~ msgid "title row and column" +#~ msgstr "başlık satır ve sütunu" + +#~ msgid "table" +#~ msgstr "tablo" + +#~ msgid "tables" +#~ msgstr "tablolar" + +#~ msgid "data" +#~ msgstr "veri" + +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "Bu, varsayılan anahtar kelime listesine başına eklenecektir." + +#~ msgid "This will be prepended to the default description." +#~ msgstr "Bu varsayılan açıklamanın başına eklenecektir." diff --git a/feincms/locale/zh_CN/LC_MESSAGES/django.mo b/feincms/locale/zh_CN/LC_MESSAGES/django.mo index 06e7264da..55ed1a58e 100644 Binary files a/feincms/locale/zh_CN/LC_MESSAGES/django.mo and b/feincms/locale/zh_CN/LC_MESSAGES/django.mo differ diff --git a/feincms/locale/zh_CN/LC_MESSAGES/django.po b/feincms/locale/zh_CN/LC_MESSAGES/django.po index dda27d321..7ad192d53 100644 --- a/feincms/locale/zh_CN/LC_MESSAGES/django.po +++ b/feincms/locale/zh_CN/LC_MESSAGES/django.po @@ -1,698 +1,771 @@ -# Simplified Chinese Translation For FeinCMS. -# Copyright (C) 2011 THE FeinCMS'S COPYRIGHT HOLDER -# This file is distributed under the same license as the FeinCMS package. -# Mark Renton , 2011. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. # -#, fuzzy +# Translators: +# aaffdd11 , 2011 +# indexofire , 2011 msgid "" msgstr "" "Project-Id-Version: FeinCMS\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-08-05 06:32+0800\n" -"PO-Revision-Date: 2011-08-05 10:20+0800\n" -"Last-Translator: Mark Renton \n" -"Language-Team: zh_CN \n" -"Language: Simplified Chinese\n" +"POT-Creation-Date: 2014-07-20 14:59+0200\n" +"PO-Revision-Date: 2013-10-25 09:15+0000\n" +"Last-Translator: Matthias Kestenholz \n" +"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/feincms/" +"language/zh_CN/)\n" +"Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0\n" - -#: models.py:343 content/template/models.py:39 content/template/models.py:57 -msgid "template" -msgstr "模板" - -#: models.py:522 -msgid "ordering" -msgstr "排序" +"Plural-Forms: nplurals=1; plural=0;\n" -#: translations.py:171 module/blog/extensions/translations.py:17 -#: module/page/extensions/translations.py:102 -msgid "language" -msgstr "语言" - -#: admin/filterspecs.py:35 admin/filterspecs.py:70 +#: admin/filterspecs.py:47 admin/filterspecs.py:86 msgid "All" msgstr "全部" -#: admin/filterspecs.py:46 module/page/models.py:254 +#: admin/filterspecs.py:58 module/page/models.py:178 msgid "Parent" msgstr "父级" -#: admin/filterspecs.py:81 +#: admin/filterspecs.py:97 +#: templates/admin/medialibrary/mediafile/change_list.html:14 msgid "Category" msgstr "分类" -#: admin/item_editor.py:140 +#: admin/item_editor.py:190 #, python-format msgid "Change %s" msgstr "修改 %s" -#: admin/tree_editor.py:219 content/rss/models.py:20 -#: content/section/models.py:32 module/blog/models.py:31 -#: module/medialibrary/models.py:48 module/page/models.py:251 -#: module/page/models.py:327 +#: admin/tree_editor.py:294 content/rss/models.py:23 +#: content/section/models.py:35 module/blog/models.py:35 +#: module/medialibrary/models.py:45 module/page/models.py:172 +#: module/page/models.py:240 msgid "title" msgstr "标题" -#: admin/tree_editor.py:397 +#: admin/tree_editor.py:353 admin/tree_editor.py:370 +#, fuzzy +#| msgid "You don't have the necessary permissions to edit this object" +msgid "You do not have permission to modify this object" +msgstr "你无权编辑该对象" + +#: admin/tree_editor.py:491 +msgid "No permission" +msgstr "" + +#: admin/tree_editor.py:509 #, python-format msgid "%s has been moved to a new position." msgstr "%s 已被移至新的位置" -#: admin/tree_editor.py:401 +#: admin/tree_editor.py:512 msgid "Did not understand moving instruction." msgstr "无法接受的移动指令" -#: admin/tree_editor.py:410 +#: admin/tree_editor.py:523 msgid "actions" msgstr "动作" -#: content/application/models.py:186 +#: admin/tree_editor.py:552 +#, fuzzy, python-format +#| msgid "Successfully added %(count)d media file to %(category)s." +#| msgid_plural "Successfully added %(count)d media files to %(category)s." +msgid "Successfully deleted %(count)d items." +msgstr "Successfully added %(count)d media files to %(category)s." + +#: admin/tree_editor.py:565 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "" + +#: content/application/models.py:147 msgid "application content" msgstr "应用程序内容" -#: content/application/models.py:187 +#: content/application/models.py:148 msgid "application contents" msgstr "应用程序内容" -#: content/application/models.py:218 +#: content/application/models.py:179 msgid "application" msgstr "应用程序" -#: content/comments/models.py:28 +#: content/comments/models.py:32 msgid "enabled" msgstr "激活" -#: content/comments/models.py:28 +#: content/comments/models.py:33 msgid "New comments may be added" msgstr "新的评论" -#: content/comments/models.py:32 content/comments/models.py:33 +#: content/comments/models.py:37 content/comments/models.py:38 msgid "comments" msgstr "评论" -#: content/comments/models.py:48 +#: content/comments/models.py:60 msgid "public" msgstr "公开" -#: content/comments/models.py:48 +#: content/comments/models.py:61 msgid "not public" msgstr "不公开" -#: content/contactform/models.py:18 +#: content/contactform/models.py:20 msgid "name" msgstr "名称" -#: content/contactform/models.py:19 +#: content/contactform/models.py:21 msgid "email" msgstr "电子邮件" -#: content/contactform/models.py:20 +#: content/contactform/models.py:22 msgid "subject" msgstr "主题" -#: content/contactform/models.py:23 content/raw/models.py:14 +#: content/contactform/models.py:26 content/raw/models.py:16 msgid "content" msgstr "内容" -#: content/contactform/models.py:34 +#: content/contactform/models.py:37 msgid "contact form" msgstr "联系表单" -#: content/contactform/models.py:35 +#: content/contactform/models.py:38 msgid "contact forms" msgstr "联系表单" -#: content/file/models.py:16 content/file/models.py:20 -#: module/medialibrary/models.py:98 +#: content/file/models.py:23 content/file/models.py:28 +#: module/medialibrary/models.py:94 msgid "file" msgstr "文件" -#: content/file/models.py:21 +#: content/file/models.py:29 msgid "files" msgstr "文件" -#: content/image/models.py:24 content/image/models.py:28 +#: content/image/models.py:46 content/image/models.py:55 msgid "image" msgstr "图片" -#: content/image/models.py:29 +#: content/image/models.py:49 +msgid "alternate text" +msgstr "" + +#: content/image/models.py:50 +msgid "Description of image" +msgstr "" + +#: content/image/models.py:51 module/medialibrary/models.py:260 +msgid "caption" +msgstr "题目" + +#: content/image/models.py:56 msgid "images" msgstr "图片" -#: content/image/models.py:42 content/medialibrary/models.py:100 -#: content/medialibrary/models.py:108 +#: content/image/models.py:82 msgid "position" msgstr "位置" -#: content/medialibrary/models.py:36 -msgid "(no caption)" -msgstr "(无标题)" +#: content/image/models.py:90 +msgid "format" +msgstr "" -#: content/medialibrary/models.py:85 content/medialibrary/models.py:96 -#: content/medialibrary/models.py:106 content/section/models.py:48 -#: module/medialibrary/models.py:110 +#: content/medialibrary/models.py:51 content/section/models.py:38 +#: module/medialibrary/fields.py:66 module/medialibrary/models.py:112 msgid "media file" msgstr "多媒体文件" -#: content/medialibrary/models.py:86 module/medialibrary/models.py:111 +#: content/medialibrary/models.py:52 module/medialibrary/models.py:113 msgid "media files" msgstr "多媒体文件" -#: content/medialibrary/models.py:126 -msgid "block" -msgstr "区块" - -#: content/medialibrary/models.py:127 -msgid "left" -msgstr "左" - -#: content/medialibrary/models.py:128 -msgid "right" -msgstr "右" +#: content/medialibrary/models.py:64 content/section/models.py:59 +msgid "type" +msgstr "类型" -#: content/raw/models.py:18 +#: content/raw/models.py:20 msgid "raw content" msgstr "raw content" -#: content/raw/models.py:19 +#: content/raw/models.py:21 msgid "raw contents" msgstr "raw contents" -#: content/richtext/models.py:15 content/richtext/models.py:85 -#: content/section/models.py:33 -msgid "text" -msgstr "纯文本" - -#: content/richtext/models.py:26 +#: content/richtext/models.py:24 msgid "HTML Tidy" -msgstr "HTML" +msgstr "HTML Tidy" -#: content/richtext/models.py:27 +#: content/richtext/models.py:25 msgid "Ignore the HTML validation warnings" msgstr "忽略HTML验证错误警告" -#: content/richtext/models.py:51 +#: content/richtext/models.py:55 #, python-format msgid "" "HTML validation produced %(count)d warnings. Please review the updated " "content below before continuing: %(messages)s" -msgstr "HTML验证产生 %(count)d 个警告。请在继续前查看下面更新的内容:" -" %(messages)s" +msgstr "" +"HTML验证产生 %(count)d 个警告。请在继续前查看下面更新的内容: %(messages)s" -#: content/richtext/models.py:89 +#: content/richtext/models.py:99 content/section/models.py:36 +msgid "text" +msgstr "纯文本" + +#: content/richtext/models.py:103 msgid "rich text" msgstr "富文本" -#: content/richtext/models.py:90 +#: content/richtext/models.py:104 msgid "rich texts" msgstr "富文本" -#: content/rss/models.py:21 +#: content/rss/models.py:25 msgid "" "The rss field is updated several times a day. A change in the title will " "only be visible on the home page after the next feed update." msgstr "RSS字段一天内更新多次。标题改动将在feed更新后只在主页显示。" -#: content/rss/models.py:22 +#: content/rss/models.py:28 msgid "link" msgstr "链接" -#: content/rss/models.py:23 +#: content/rss/models.py:30 msgid "pre-rendered content" msgstr "预渲染的内容" -#: content/rss/models.py:24 +#: content/rss/models.py:32 msgid "last updated" msgstr "最后更新" -#: content/rss/models.py:25 +#: content/rss/models.py:33 msgid "max. items" msgstr "最大条目数" -#: content/rss/models.py:29 +#: content/rss/models.py:37 msgid "RSS feed" msgstr "RSS 提要" -#: content/rss/models.py:30 +#: content/rss/models.py:38 msgid "RSS feeds" msgstr "RSS 提要" -#: content/section/models.py:37 +#: content/section/models.py:43 msgid "section" msgstr "版块" -#: content/section/models.py:38 +#: content/section/models.py:44 msgid "sections" msgstr "版块" -#: content/section/models.py:53 content/section/models.py:61 -#: content/table/models.py:81 -msgid "type" -msgstr "类型" - -#: content/table/models.py:62 -msgid "plain" -msgstr "无格式" - -#: content/table/models.py:63 -msgid "title row" -msgstr "标题行" - -#: content/table/models.py:65 -msgid "title row and column" -msgstr "标题行和列" - -#: content/table/models.py:71 -msgid "table" -msgstr "表格" - -#: content/table/models.py:72 -msgid "tables" -msgstr "表格" - -#: content/table/models.py:86 -msgid "data" -msgstr "数据" - -#: content/template/models.py:62 +#: content/template/models.py:53 msgid "template content" msgstr "模板内容" -#: content/template/models.py:63 +#: content/template/models.py:54 msgid "template contents" msgstr "模板内容" -#: content/video/models.py:23 +#: content/template/models.py:63 models.py:400 +msgid "template" +msgstr "模板" + +#: content/video/models.py:34 msgid "video link" msgstr "视频链接" -#: content/video/models.py:24 +#: content/video/models.py:36 msgid "" "This should be a link to a youtube or vimeo video, i.e.: http://www.youtube." "com/watch?v=zmj1rpzDRZ0" -msgstr "请输入youtube或vimeo视频链接,比如:http://www.youtube.com/watch?" +msgstr "" +"请输入youtube或vimeo视频链接,比如:http://www.youtube.com/watch?" "v=zmj1rpzDRZ0" -#: content/video/models.py:28 +#: content/video/models.py:41 msgid "video" msgstr "视频" -#: content/video/models.py:29 +#: content/video/models.py:42 msgid "videos" msgstr "视频" -#: module/blog/models.py:30 +#: contrib/tagging.py:132 +msgid "Tagging" +msgstr "" + +#: models.py:550 +msgid "ordering" +msgstr "排序" + +#: module/blog/extensions/tags.py:17 +msgid "tags" +msgstr "标签" + +#: module/blog/extensions/translations.py:25 +#: module/extensions/translations.py:140 translations.py:282 +msgid "language" +msgstr "语言" + +#: module/blog/extensions/translations.py:35 +#: module/extensions/translations.py:148 +msgid "translation of" +msgstr "翻译" + +#: module/blog/extensions/translations.py:39 +#: module/extensions/translations.py:152 +msgid "Leave this empty for entries in the primary language." +msgstr "在主要语言项中对这些条目留空。" + +#: module/blog/extensions/translations.py:67 +#: templates/admin/feincms/item_editor.html:33 +msgid "available translations" +msgstr "已有翻译" + +#: module/blog/models.py:33 msgid "published" msgstr "已发布" -#: module/blog/models.py:32 module/page/models.py:252 +#: module/blog/models.py:36 msgid "This is used for the generated navigation too." msgstr "也被用于产生导航条" -#: module/blog/models.py:35 +#: module/blog/models.py:40 msgid "published on" msgstr "发布于" -#: module/blog/models.py:36 +#: module/blog/models.py:42 msgid "Will be set automatically once you tick the `published` checkbox above." msgstr "一旦你将`发表`复选框以上打勾选中,日志将设置成自动发布。" -#: module/blog/models.py:41 +#: module/blog/models.py:48 msgid "entry" msgstr "条目" -#: module/blog/models.py:42 +#: module/blog/models.py:49 msgid "entries" msgstr "条目" -#: module/blog/extensions/tags.py:12 -msgid "tags" -msgstr "标签" - -#: module/blog/extensions/translations.py:20 -#: module/page/extensions/translations.py:105 -msgid "translation of" -msgstr "翻译" - -#: module/blog/extensions/translations.py:23 -#: module/page/extensions/translations.py:108 -msgid "Leave this empty for entries in the primary language." -msgstr "在主要语言项中对这些条目留空。" - -#: module/blog/extensions/translations.py:44 -#: templates/admin/feincms/item_editor.html:69 -msgid "available translations" -msgstr "已有翻译" - -#: module/extensions/changedate.py:38 +#: module/extensions/changedate.py:41 msgid "creation date" msgstr "创建日期" -#: module/extensions/changedate.py:39 +#: module/extensions/changedate.py:43 msgid "modification date" msgstr "修改日期" -#: module/extensions/ct_tracker.py:117 +#: module/extensions/ct_tracker.py:156 msgid "content types" msgstr "内容类型" -#: module/extensions/featured.py:9 +#: module/extensions/datepublisher.py:86 +msgid "publication date" +msgstr "发布起始日期" + +#: module/extensions/datepublisher.py:90 +msgid "publication end date" +msgstr "发布结束日期" + +#: module/extensions/datepublisher.py:93 +msgid "Leave empty if the entry should stay active forever." +msgstr "如果条目要永久显示,请留空" + +#: module/extensions/datepublisher.py:128 +msgid "visible from - to" +msgstr "可查看日期" + +#: module/extensions/datepublisher.py:139 +msgid "Date-based publishing" +msgstr "发布基于日期控制" + +#: module/extensions/featured.py:15 msgid "featured" msgstr "特性" -#: module/extensions/featured.py:14 +#: module/extensions/featured.py:21 msgid "Featured" msgstr "特性" -#: module/extensions/seo.py:9 +#: module/extensions/seo.py:16 msgid "meta keywords" msgstr "meta 关键词" -#: module/extensions/seo.py:10 -msgid "This will be prepended to the default keyword list." -msgstr "这将作为默认的关键字列表被添加。" +#: module/extensions/seo.py:18 +msgid "Keywords are ignored by most search engines." +msgstr "" -#: module/extensions/seo.py:11 +#: module/extensions/seo.py:20 msgid "meta description" msgstr "meta 描述" -#: module/extensions/seo.py:12 -msgid "This will be prepended to the default description." -msgstr "这将作为默认的描述被添加" +#: module/extensions/seo.py:22 +msgid "" +"This text is displayed on the search results page. It is however not used " +"for the SEO ranking. Text longer than 140 characters is truncated." +msgstr "" -#: module/extensions/seo.py:18 +#: module/extensions/seo.py:32 msgid "Search engine optimization" msgstr "搜索引擎优化" -#: module/medialibrary/models.py:51 +#: module/extensions/translations.py:249 +msgid "Edit translation" +msgstr "编辑翻译" + +#: module/extensions/translations.py:256 +msgid "Create translation" +msgstr "创建翻译" + +#: module/extensions/translations.py:264 +msgid "translations" +msgstr "翻译" + +#: module/medialibrary/forms.py:31 +msgid "This would create a loop in the hierarchy" +msgstr "" + +#: module/medialibrary/forms.py:77 +#, python-format +msgid "" +"Cannot overwrite with different file type (attempt to overwrite a " +"%(old_ext)s with a %(new_ext)s)" +msgstr "" + +#: module/medialibrary/modeladmins.py:63 +#, python-format +msgid "Successfully added %(count)d media file to %(category)s." +msgid_plural "Successfully added %(count)d media files to %(category)s." +msgstr[0] "Successfully added %(count)d media files to %(category)s." + +#: module/medialibrary/modeladmins.py:84 +msgid "Add selected media files to category" +msgstr "所选的媒体文件添加到类" + +#: module/medialibrary/modeladmins.py:94 +#, python-format +msgid "ZIP file exported as %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:96 +#, python-format +msgid "ZIP file export failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:104 +msgid "Export selected media files as zip file" +msgstr "" + +#: module/medialibrary/modeladmins.py:157 +#: templates/admin/feincms/page/page/item_editor.html:15 +msgid "Preview" +msgstr "预览" + +#: module/medialibrary/modeladmins.py:162 module/medialibrary/models.py:103 +msgid "file size" +msgstr "文件大小" + +#: module/medialibrary/modeladmins.py:167 module/medialibrary/models.py:100 +msgid "created" +msgstr "创建" + +#: module/medialibrary/modeladmins.py:187 module/medialibrary/models.py:97 +msgid "file type" +msgstr "文件类型" + +#: module/medialibrary/modeladmins.py:211 +msgid "file info" +msgstr "文件信息" + +#: module/medialibrary/modeladmins.py:224 +#, python-format +msgid "%d files imported" +msgstr "导入 %d 个文件" + +#: module/medialibrary/modeladmins.py:226 +#, python-format +msgid "ZIP import failed: %s" +msgstr "" + +#: module/medialibrary/modeladmins.py:228 +msgid "No input file given" +msgstr "没有选择文件" + +#: module/medialibrary/models.py:49 msgid "parent" msgstr "父" -#: module/medialibrary/models.py:53 module/page/models.py:253 +#: module/medialibrary/models.py:51 module/page/models.py:175 msgid "slug" msgstr "slug" -#: module/medialibrary/models.py:57 +#: module/medialibrary/models.py:55 msgid "category" msgstr "类别" -#: module/medialibrary/models.py:58 module/medialibrary/models.py:104 +#: module/medialibrary/models.py:56 module/medialibrary/models.py:106 msgid "categories" msgstr "类别" -#: module/medialibrary/models.py:99 module/medialibrary/models.py:185 -msgid "file type" -msgstr "文件类型" - -#: module/medialibrary/models.py:100 module/medialibrary/models.py:125 -msgid "created" -msgstr "创建" - #: module/medialibrary/models.py:101 msgid "copyright" msgstr "版权" -#: module/medialibrary/models.py:102 module/medialibrary/models.py:120 -msgid "file size" -msgstr "文件大小" - -#: module/medialibrary/models.py:202 -msgid "file info" -msgstr "文件信息" - -#: module/medialibrary/models.py:282 +#: module/medialibrary/models.py:219 msgid "Image" msgstr "图片" -#: module/medialibrary/models.py:283 +#: module/medialibrary/models.py:221 msgid "Video" msgstr "视频" -#: module/medialibrary/models.py:284 +#: module/medialibrary/models.py:224 msgid "Audio" msgstr "音频" -#: module/medialibrary/models.py:285 +#: module/medialibrary/models.py:226 msgid "PDF document" msgstr "PDF文档" -#: module/medialibrary/models.py:286 +#: module/medialibrary/models.py:227 msgid "Flash" msgstr "Flash" -#: module/medialibrary/models.py:287 +#: module/medialibrary/models.py:228 msgid "Text" msgstr "纯文本" -#: module/medialibrary/models.py:288 +#: module/medialibrary/models.py:229 msgid "Rich Text" msgstr "富文本" -#: module/medialibrary/models.py:289 +#: module/medialibrary/models.py:230 msgid "Zip archive" msgstr "zip压缩包" -#: module/medialibrary/models.py:290 +#: module/medialibrary/models.py:231 msgid "Microsoft Word" msgstr "Microsoft Word" -#: module/medialibrary/models.py:291 +#: module/medialibrary/models.py:233 msgid "Microsoft Excel" msgstr "Microsoft Excel" -#: module/medialibrary/models.py:292 +#: module/medialibrary/models.py:235 msgid "Microsoft PowerPoint" msgstr "Microsoft PowerPoint" -#: module/medialibrary/models.py:293 +#: module/medialibrary/models.py:237 msgid "Binary" msgstr "二进制文件" -#: module/medialibrary/models.py:309 -msgid "caption" -msgstr "题目" - -#: module/medialibrary/models.py:310 +#: module/medialibrary/models.py:261 msgid "description" msgstr "描述" -#: module/medialibrary/models.py:313 +#: module/medialibrary/models.py:264 msgid "media file translation" msgstr "媒体文件翻译" -#: module/medialibrary/models.py:314 +#: module/medialibrary/models.py:265 msgid "media file translations" msgstr "媒体文件翻译" -#: module/medialibrary/models.py:341 -msgid "Preview" -msgstr "预览" +#: module/page/extensions/excerpt.py:18 +msgid "excerpt" +msgstr "摘抄" -#: module/medialibrary/models.py:390 -msgid "Could not access storage" -msgstr "无法访问储存" +#: module/page/extensions/excerpt.py:21 +msgid "Add a brief excerpt summarizing the content of this page." +msgstr "添加一条简要形容页面内容" -#: module/medialibrary/models.py:411 -#, python-format -msgid "%d files imported" -msgstr "导入 %d 个文件" +#: module/page/extensions/excerpt.py:25 +msgid "Excerpt" +msgstr "摘抄" -#: module/medialibrary/models.py:413 -#, python-format -msgid "ZIP file invalid: %s" -msgstr "zip文件无效: %s" +#: module/page/extensions/navigation.py:86 +#: module/page/extensions/navigation.py:115 +msgid "navigation extension" +msgstr "导航扩展" -#: module/medialibrary/models.py:419 -msgid "No input file given" -msgstr "没有选择文件" +#: module/page/extensions/navigation.py:119 +msgid "" +"Select the module providing subpages for this page if you need to customize " +"the navigation." +msgstr "如果您需要定制的导航,选择提供此页面的子页面模块。" -#: module/page/models.py:248 -msgid "active" -msgstr "激活" +#: module/page/extensions/navigation.py:134 +msgid "Navigation extension" +msgstr "导航扩展" -#: module/page/models.py:256 module/page/models.py:795 -msgid "in navigation" +#: module/page/extensions/navigationgroups.py:20 +msgid "Default" +msgstr "" + +#: module/page/extensions/navigationgroups.py:21 +msgid "Footer" +msgstr "" + +#: module/page/extensions/navigationgroups.py:28 +#, fuzzy +#| msgid "in navigation" +msgid "navigation group" msgstr "导航中" -#: module/page/models.py:257 -msgid "override URL" -msgstr "URL覆盖" +#: module/page/extensions/relatedpages.py:21 +msgid "Select pages that should be listed as related content." +msgstr "选择页面作为相关页面在列表中显示" -#: module/page/models.py:258 -msgid "" -"Override the target URL. Be sure to include slashes at the beginning and at " -"the end if it is a local URL. This affects both the navigation and subpages' " -"URLs." -msgstr "" -"覆盖目标URL,如果它是一个本地的URL,一定要包括在开始和结束时的斜线。这会" -"影响导航和子页面的URL" +#: module/page/extensions/relatedpages.py:26 +msgid "Related pages" +msgstr "相关页面" -#: module/page/models.py:259 -msgid "redirect to" -msgstr "转向" +#: module/page/extensions/sites.py:21 +msgid "Site" +msgstr "网站" + +#: module/page/extensions/symlinks.py:22 +msgid "symlinked page" +msgstr "链接页面" -#: module/page/models.py:260 -msgid "Target URL for automatic redirects." -msgstr "转向目标URL" +#: module/page/extensions/symlinks.py:23 +msgid "All content is inherited from this page if given." +msgstr "所有内容继承于给出的页面" -#: module/page/models.py:261 -msgid "Cached URL" -msgstr "URL缓存" +#: module/page/extensions/titles.py:19 +msgid "content title" +msgstr "内容标题" -#: module/page/models.py:272 -msgid "page" -msgstr "页面" +#: module/page/extensions/titles.py:22 +msgid "The first line is the main title, the following lines are subtitles." +msgstr "正文第一行作为主标题,第二行作为副标题" -#: module/page/models.py:273 -msgid "pages" -msgstr "页面" +#: module/page/extensions/titles.py:26 +msgid "page title" +msgstr "页面标题" -#: module/page/models.py:290 module/page/models.py:886 -msgid "is active" -msgstr "激活" +#: module/page/extensions/titles.py:30 +#, fuzzy +#| msgid "Page title for browser window. Same as title by default." +msgid "" +"Page title for browser window. Same as title bydefault. Must not be longer " +"than 70 characters." +msgstr "浏览器窗口显示标题默认为页面标题" + +#: module/page/extensions/titles.py:60 +msgid "Titles" +msgstr "标题" -#: module/page/models.py:715 +#: module/page/forms.py:187 msgid "This URL is already taken by an active page." msgstr "此URL已被其他页面使用" -#: module/page/models.py:733 +#: module/page/forms.py:206 msgid "This URL is already taken by another active page." msgstr "此URL已被另一个页面使用" -#: module/page/models.py:758 +#: module/page/forms.py:211 +msgid "This page does not allow attachment of child pages" +msgstr "" + +#: module/page/modeladmins.py:42 msgid "Other options" msgstr "其他选项" -#: module/page/models.py:802 templates/admin/feincms/item_editor.html:54 +#: module/page/modeladmins.py:80 module/page/models.py:182 +msgid "in navigation" +msgstr "导航中" + +#: module/page/modeladmins.py:109 module/page/modeladmins.py:111 msgid "Add child page" msgstr "增加子页面" -#: module/page/models.py:804 templates/admin/feincms/content_inline.html:9 +#: module/page/modeladmins.py:120 module/page/modeladmins.py:122 +#: templates/admin/feincms/content_inline.html:9 msgid "View on site" msgstr "站内查看" -#: module/page/models.py:863 +#: module/page/modeladmins.py:142 +#, python-format +msgid "Add %(language)s translation of \"%(page)s\"" +msgstr "" + +#: module/page/modeladmins.py:177 +msgid "" +"The content from the original translation has been copied to the newly " +"created page." +msgstr "" + +#: module/page/modeladmins.py:197 msgid "You don't have the necessary permissions to edit this object" msgstr "你无权编辑该对象" -#: module/page/models.py:878 +#: module/page/modeladmins.py:223 msgid "inherited" msgstr "继承" -#: module/page/models.py:882 +#: module/page/modeladmins.py:229 msgid "extensions" msgstr "扩展" -#: module/page/extensions/datepublisher.py:48 -msgid "publication date" -msgstr "发布起始日期" - -#: module/page/extensions/datepublisher.py:50 -msgid "publication end date" -msgstr "发布结束日期" - -#: module/page/extensions/datepublisher.py:52 -msgid "Leave empty if the entry should stay active forever." -msgstr "如果条目要永久显示,请留空" - -#: module/page/extensions/datepublisher.py:78 -msgid "visible from - to" -msgstr "可查看日期" - -#: module/page/extensions/datepublisher.py:88 -msgid "Date-based publishing" -msgstr "发布基于日期控制" +#: module/page/modeladmins.py:233 module/page/models.py:221 +msgid "is active" +msgstr "激活" -#: module/page/extensions/excerpt.py:9 -msgid "excerpt" -msgstr "摘抄" +#: module/page/models.py:169 +msgid "active" +msgstr "激活" -#: module/page/extensions/excerpt.py:10 -msgid "Add a brief excerpt summarizing the content of this page." -msgstr "添加一条简要形容页面内容" +#: module/page/models.py:173 +#, fuzzy +#| msgid "This is used for the generated navigation too." +msgid "This title is also used for navigation menu items." +msgstr "也被用于产生导航条" -#: module/page/extensions/excerpt.py:12 -msgid "Excerpt" -msgstr "摘抄" +#: module/page/models.py:176 +msgid "This is used to build the URL for this page" +msgstr "" -#: module/page/extensions/navigation.py:77 -#: module/page/extensions/navigation.py:97 -msgid "navigation extension" -msgstr "导航扩展" +#: module/page/models.py:184 +msgid "override URL" +msgstr "URL覆盖" -#: module/page/extensions/navigation.py:99 +#: module/page/models.py:186 msgid "" -"Select the module providing subpages for this page if you need to customize " -"the navigation." +"Override the target URL. Be sure to include slashes at the beginning and at " +"the end if it is a local URL. This affects both the navigation and subpages' " +"URLs." msgstr "" -"如果您需要定制的导航,选择提供此页面的子页面模块。" - -#: module/page/extensions/navigation.py:112 -msgid "Navigation extension" -msgstr "导航扩展" +"覆盖目标URL,如果它是一个本地的URL,一定要包括在开始和结束时的斜线。这会影响" +"导航和子页面的URL" -#: module/page/extensions/relatedpages.py:13 -msgid "Select pages that should be listed as related content." -msgstr "选择页面作为相关页面在列表中显示" - -#: module/page/extensions/relatedpages.py:20 -msgid "Related pages" -msgstr "相关页面" - -#: module/page/extensions/symlinks.py:15 -msgid "symlinked page" -msgstr "链接页面" - -#: module/page/extensions/symlinks.py:16 -msgid "All content is inherited from this page if given." -msgstr "所有内容继承于给出的页面" - -#: module/page/extensions/symlinks.py:30 -msgid "Symlinked page" -msgstr "链接页面" - -#: module/page/extensions/titles.py:13 -msgid "content title" -msgstr "内容标题" - -#: module/page/extensions/titles.py:14 -msgid "The first line is the main title, the following lines are subtitles." -msgstr "正文第一行作为主标题,第二行作为副标题" - -#: module/page/extensions/titles.py:15 -msgid "page title" -msgstr "页面标题" - -#: module/page/extensions/titles.py:16 -msgid "Page title for browser window. Same as title by default." -msgstr "浏览器窗口显示标题默认为页面标题" - -#: module/page/extensions/titles.py:43 -msgid "Titles" -msgstr "标题" - -#: module/page/extensions/translations.py:171 -msgid "Edit translation" -msgstr "编辑翻译" +#: module/page/models.py:190 +msgid "redirect to" +msgstr "转向" -#: module/page/extensions/translations.py:174 -msgid "Create translation" -msgstr "创建翻译" +#: module/page/models.py:193 +msgid "Target URL for automatic redirects or the primary key of a page." +msgstr "" -#: module/page/extensions/translations.py:179 -msgid "translations" -msgstr "翻译" +#: module/page/models.py:196 +msgid "Cached URL" +msgstr "URL缓存" -#: templates/admin/filter.html:3 +#: module/page/models.py:298 #, python-format -msgid " By %(filter_title)s " -msgstr "由 %(filter_title)s" +msgid "" +"This %(page_class)s uses a singleton template, and " +"FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" +msgstr "" + +#: module/page/models.py:426 +msgid "page" +msgstr "页面" -#: templates/admin/content/mediafile/init.html:9 -msgid "Search" -msgstr "搜索" +#: module/page/models.py:427 +msgid "pages" +msgstr "页面" #: templates/admin/feincms/_messages_js.html:4 msgid "Really delete item?" @@ -726,10 +799,8 @@ msgstr "真的要修改模板么?
    全部修改已被保存。" #, python-format msgid "" "Really change template?
    All changes are saved and content from " -"%(source_regions)s is moved to %(target_region)s." +"%%(source_regions)s
    is moved to %%(target_region)s." msgstr "" -"真的修改模板?
    所有修改已保存,%(source_regions)s的内容" -"已被移到%(target_region)s" #: templates/admin/feincms/_messages_js.html:12 msgid "Hide" @@ -751,88 +822,149 @@ msgstr "之前" msgid "Insert new:" msgstr "插入新的" -#: templates/admin/feincms/content_editor.html:11 +#: templates/admin/feincms/content_editor.html:15 +msgid "Copy content from the original" +msgstr "" + +#: templates/admin/feincms/content_editor.html:19 msgid "Region empty" msgstr "空区域" -#: templates/admin/feincms/content_editor.html:15 +#: templates/admin/feincms/content_editor.html:25 msgid "" "Content from the parent site is automatically inherited. To override this " "behaviour, add some content." -msgstr "" -"父站的内容已自动继承。添加新内容会覆盖。" +msgstr "父站的内容已自动继承。添加新内容会覆盖。" -#: templates/admin/feincms/content_editor.html:23 +#: templates/admin/feincms/content_editor.html:33 msgid "Add new item" msgstr "新建" -#: templates/admin/feincms/content_inline.html:91 +#: templates/admin/feincms/content_inline.html:93 #, python-format msgid "Add another %(verbose_name)s" msgstr "添加另一个 %(verbose_name)s" -#: templates/admin/feincms/content_inline.html:94 +#: templates/admin/feincms/content_inline.html:96 msgid "Remove" msgstr "移除" -#: templates/admin/feincms/fe_editor.html:46 +#: templates/admin/feincms/fe_editor.html:30 msgid "Save" msgstr "保存" -#: templates/admin/feincms/fe_tools.html:27 +#: templates/admin/feincms/fe_tools.html:28 msgid "Stop Editing" msgstr "暂停编辑" -#: templates/admin/feincms/fe_tools.html:32 +#: templates/admin/feincms/fe_tools.html:33 msgid "edit" msgstr "编辑" -#: templates/admin/feincms/fe_tools.html:34 +#: templates/admin/feincms/fe_tools.html:35 msgid "new" msgstr "新建" -#: templates/admin/feincms/fe_tools.html:35 +#: templates/admin/feincms/fe_tools.html:36 msgid "up" msgstr "上" -#: templates/admin/feincms/fe_tools.html:36 +#: templates/admin/feincms/fe_tools.html:37 msgid "down" msgstr "下" -#: templates/admin/feincms/fe_tools.html:37 +#: templates/admin/feincms/fe_tools.html:38 msgid "remove" msgstr "移除" -#: templates/admin/feincms/tree_editor.html:37 +#: templates/admin/feincms/page/page/item_editor.html:10 +msgid "Edit on site" +msgstr "站内编辑" + +#: templates/admin/feincms/page/page/item_editor.html:23 +#: templates/admin/feincms/page/page/tree_editor.html:7 +#: templates/admin/feincms/recover_form.html:8 +#: templates/admin/feincms/revision_form.html:8 +msgid "Home" +msgstr "首页" + +#: templates/admin/feincms/page/page/item_editor.html:27 +msgid "Add" +msgstr "添加" + +#: templates/admin/feincms/recover_form.html:11 +#, python-format +msgid "Recover deleted %(verbose_name)s" +msgstr "Recover deleted %(verbose_name)s" + +#: templates/admin/feincms/recover_form.html:17 +msgid "Press the save button below to recover this version of the object." +msgstr "按下面的“保存”按钮恢复此对象的版本。" + +#: templates/admin/feincms/revision_form.html:12 +msgid "History" +msgstr "历史" + +#: templates/admin/feincms/revision_form.html:13 +#, python-format +msgid "Revert %(verbose_name)s" +msgstr "Revert %(verbose_name)s" + +#: templates/admin/feincms/revision_form.html:24 +msgid "Press the save button below to revert to this version of the object." +msgstr "按下面的“保存”按钮,恢复到这个版本的对象。" + +#: templates/admin/feincms/tree_editor.html:22 msgid "Shortcuts" msgstr "快捷方式" -#: templates/admin/feincms/tree_editor.html:39 +#: templates/admin/feincms/tree_editor.html:24 msgid "Collapse tree" msgstr "折叠树型" -#: templates/admin/feincms/tree_editor.html:40 +#: templates/admin/feincms/tree_editor.html:25 msgid "Expand tree" msgstr "展开树型" -#: templates/admin/feincms/tree_editor.html:43 +#: templates/admin/feincms/tree_editor.html:29 msgid "Filter" msgstr "过滤器" -#: templates/admin/feincms/page/page/item_editor.html:9 -msgid "Edit on site" -msgstr "站内编辑" +#: templates/admin/filter.html:3 +#, python-format +msgid " By %(filter_title)s " +msgstr "由 %(filter_title)s" -#: templates/admin/feincms/page/page/item_editor.html:16 -#: templates/admin/feincms/page/page/tree_editor.html:7 -msgid "Home" -msgstr "首页" +#: templates/admin/medialibrary/add_to_category.html:5 +#: templates/admin/medialibrary/add_to_category.html:9 +msgid "Add media files to category" +msgstr "添加媒体文件到分类" + +#: templates/admin/medialibrary/add_to_category.html:11 +msgid "Select category to apply:" +msgstr "选择申请类别:" + +#: templates/admin/medialibrary/add_to_category.html:17 +msgid "The following media files will be added to the selected category:" +msgstr "以下的媒体文件将被添加到选定的类别:" -#: templates/admin/medialibrary/mediafile/change_list.html:12 +#: templates/admin/medialibrary/add_to_category.html:22 +msgid "Add to category" +msgstr "添加到分类" + +#: templates/admin/medialibrary/add_to_category.html:23 +msgid "Cancel" +msgstr "取消" + +#: templates/admin/medialibrary/mediafile/change_list.html:11 msgid "Bulk upload a ZIP file:" msgstr "批量上传zip文件" #: templates/admin/medialibrary/mediafile/change_list.html:21 +msgid "Overwrite" +msgstr "" + +#: templates/admin/medialibrary/mediafile/change_list.html:24 msgid "Send" msgstr "发送" @@ -869,3 +1001,27 @@ msgstr "提交" #: templates/content/contactform/thanks.html:3 msgid "Thanks!" msgstr "感谢" + +#~ msgid "plain" +#~ msgstr "无格式" + +#~ msgid "title row" +#~ msgstr "标题行" + +#~ msgid "title row and column" +#~ msgstr "标题行和列" + +#~ msgid "table" +#~ msgstr "表格" + +#~ msgid "tables" +#~ msgstr "表格" + +#~ msgid "data" +#~ msgstr "数据" + +#~ msgid "This will be prepended to the default keyword list." +#~ msgstr "这将作为默认的关键字列表被添加。" + +#~ msgid "This will be prepended to the default description." +#~ msgstr "这将作为默认的描述被添加" diff --git a/feincms/management/checker.py b/feincms/management/checker.py deleted file mode 100644 index 344bd20e8..000000000 --- a/feincms/management/checker.py +++ /dev/null @@ -1,53 +0,0 @@ -from django.core.management.color import color_style -from django.db import connection - - -def check_database_schema(cls, module_name): - """ - Returns a function which inspects the database table of the passed class. - It checks whether all fields in the model are available on the database - too. This is especially helpful for models with an extension mechanism, - where the extension might be activated after syncdb has been run for the - first time. - - Please note that you have to connect the return value using strong - references. Here's an example how to do this:: - - signals.post_syncdb.connect(check_database_schema(Page, __name__), weak=False) - - (Yes, this is a weak attempt at a substitute for South until we find - a way to make South work with FeinCMS' dynamic model creation.) - """ - - def _fn(sender, **kwargs): - if sender.__name__ != module_name: - return - - cursor = connection.cursor() - - existing_columns = [row[0] for row in \ - connection.introspection.get_table_description(cursor, cls._meta.db_table)] - - missing_columns = [] - - for field in cls._meta.fields: - if field.column not in existing_columns: - missing_columns.append(field) - - if not missing_columns: - return - - style = color_style() - - print style.ERROR('The following columns seem to be missing in the database table %s:' % cls._meta.db_table) - for field in missing_columns: - print u'%s:%s%s' % ( - style.SQL_KEYWORD(field.column), - ' ' * (25 - len(field.column)), - u'%s.%s' % (field.__class__.__module__, field.__class__.__name__), - ) - - print style.NOTICE('\nPlease consult the output of `python manage.py sql %s` to find out what the correct column types are.\n' % ( - cls._meta.app_label, - )) - return _fn diff --git a/feincms/management/commands/feincms_validate.py b/feincms/management/commands/feincms_validate.py deleted file mode 100644 index 4eaefe7e9..000000000 --- a/feincms/management/commands/feincms_validate.py +++ /dev/null @@ -1,51 +0,0 @@ -# ------------------------------------------------------------------------ -# coding=utf-8 -# $Id$ -# ------------------------------------------------------------------------ -""" -``feincms_validate`` --------------------- - -``feincms_validate`` checks your models for common pitfalls. -""" - -from django.core.management.base import NoArgsCommand -from django.core.management.color import color_style -from django.db.models import loading - - -class Command(NoArgsCommand): - help = "Check models for common pitfalls." - - requires_model_validation = False - - def handle_noargs(self, **options): - self.style = color_style() - - print "Running Django's own validation:" - self.validate(display_num_errors=True) - - for model in loading.get_models(): - if hasattr(model, '_create_content_base'): - self.validate_base_model(model) - - if hasattr(model, '_feincms_content_models'): - self.validate_content_type(model) - - def validate_base_model(self, model): - """ - Validate a subclass of ``feincms.models.Base`` or anything else created - by ``feincms.models.create_base_model`` - """ - - if not hasattr(model, 'template'): - print self.style.NOTICE('%s has no template attribute; did you forget register_templates or register_regions?' % model) - - def validate_content_type(self, model): - """ - Validate a dynamically created concrete content type - """ - - for base in model.__bases__: - if not base._meta.abstract: - print self.style.NOTICE('One of %s bases, %s, is not abstract' % (model, base)) \ No newline at end of file diff --git a/feincms/management/commands/medialibrary_orphans.py b/feincms/management/commands/medialibrary_orphans.py new file mode 100644 index 000000000..208d47213 --- /dev/null +++ b/feincms/management/commands/medialibrary_orphans.py @@ -0,0 +1,24 @@ +import os + +from django.conf import settings +from django.core.management.base import BaseCommand + +from feincms.module.medialibrary.models import MediaFile + + +class Command(BaseCommand): + help = "Prints all orphaned files in the `media/medialibrary` folder" + + def handle(self, **options): + mediafiles = list(MediaFile.objects.values_list("file", flat=True)) + + root_len = len(settings.MEDIA_ROOT) + medialib_path = os.path.join(settings.MEDIA_ROOT, "medialibrary") + + for base, dirs, files in os.walk(medialib_path): + for f in files: + if base.startswith(settings.MEDIA_ROOT): + base = base[root_len:] + full = os.path.join(base, f) + if full not in mediafiles: + self.stdout.write(full) diff --git a/feincms/management/commands/medialibrary_to_filer.py b/feincms/management/commands/medialibrary_to_filer.py new file mode 100644 index 000000000..de0961e01 --- /dev/null +++ b/feincms/management/commands/medialibrary_to_filer.py @@ -0,0 +1,60 @@ +from django.contrib.auth.models import User +from django.core.files import File as DjangoFile +from django.core.management.base import BaseCommand +from filer.models import File, Image + +from feincms.contents import FilerFileContent, FilerImageContent +from feincms.module.medialibrary.contents import MediaFileContent +from feincms.module.medialibrary.models import MediaFile +from feincms.module.page.models import Page + + +PageMediaFileContent = Page.content_type_for(MediaFileContent) +PageFilerFileContent = Page.content_type_for(FilerFileContent) +PageFilerImageContent = Page.content_type_for(FilerImageContent) + + +assert all((PageMediaFileContent, PageFilerFileContent, PageFilerImageContent)), ( + "Not all required models available" +) + + +class Command(BaseCommand): + help = "Migrate the medialibrary and contents to django-filer" + + def handle(self, **options): + user = User.objects.order_by("pk")[0] + + count = MediaFile.objects.count() + + for i, mediafile in enumerate(MediaFile.objects.order_by("pk")): + model = Image if mediafile.type == "image" else File + content_model = ( + PageFilerImageContent + if mediafile.type == "image" + else PageFilerFileContent + ) # noqa + + filerfile = model.objects.create( + owner=user, + original_filename=mediafile.file.name, + file=DjangoFile(mediafile.file.file, name=mediafile.file.name), + ) + + contents = PageMediaFileContent.objects.filter(mediafile=mediafile) + + for content in contents: + content_model.objects.create( + parent=content.parent, + region=content.region, + ordering=content.ordering, + type=content.type, + mediafile=filerfile, + ) + + content.delete() + + if not i % 10: + self.stdout.write(f"{i} / {count} files\n") + + self.stdout.write(f"{count} / {count} files\n") diff --git a/feincms/management/commands/rebuild_mptt.py b/feincms/management/commands/rebuild_mptt.py index 7dc656cfd..7c292758f 100644 --- a/feincms/management/commands/rebuild_mptt.py +++ b/feincms/management/commands/rebuild_mptt.py @@ -1,6 +1,4 @@ # ------------------------------------------------------------------------ -# coding=utf-8 -# $Id$ # ------------------------------------------------------------------------ """ ``rebuild_mptt`` @@ -9,43 +7,17 @@ ``rebuild_mptt`` rebuilds your mptt pointers. Only use in emergencies. """ -from django.core.management.base import NoArgsCommand -from django.db import transaction +from django.core.management.base import BaseCommand from feincms.module.page.models import Page -class Command(NoArgsCommand): - help = "Run this manually to rebuild your mptt pointers. Only use in emergencies." - - @staticmethod - def seq(start = 1): - """ - Returns an ever-increasing stream of numbers. The starting point can - be freely defined. - """ - while True: - yield start - start += 1 +class Command(BaseCommand): + help = "Run this manually to rebuild your mptt pointers. Only use in emergencies." - @transaction.commit_manually def handle_noargs(self, **options): - print "Rebuilding MPTT pointers for Page" - root = 1 - changes = set() - for page in Page.objects.filter(parent__isnull=True).order_by('tree_id'): - print " Processing subtree %d at %s" % ( page.tree_id, page.slug ) - - page.tree_id = root # Renumber tree_id for good measure - - self.renumber_mptt_tree(page, self.seq(1)) - - root += 1 - transaction.commit() + self.handle(**options) - def renumber_mptt_tree(self, obj, edge_count): - obj.lft = edge_count.next() - for c in obj.children.order_by('lft', 'rght').all(): - self.renumber_mptt_tree(c, edge_count) - obj.rght = edge_count.next() - obj.save() \ No newline at end of file + def handle(self, **options): + self.stdout.write("Rebuilding MPTT pointers for Page") + Page._tree_manager.rebuild() diff --git a/feincms/management/commands/rebuild_mptt_direct.py b/feincms/management/commands/rebuild_mptt_direct.py deleted file mode 100644 index 98f40045b..000000000 --- a/feincms/management/commands/rebuild_mptt_direct.py +++ /dev/null @@ -1,102 +0,0 @@ -# encoding: utf-8 -""" -``rebuild_mptt_direct`` ------------------------ - -``rebuild_mptt_direct`` rebuilds your mptt pointers, bypassing the ORM. -Only use in emergencies. -""" - -import logging - -from django.core.management.base import NoArgsCommand -from django.db import transaction, connection, backend - -from feincms.module.page.models import Page - -class Command(NoArgsCommand): - help = "Manually rebuild MPTT hierarchy - should only be used to repair damaged databases" - - @transaction.commit_manually - def handle_noargs(self, **options): - logging.info("Rebuilding all MPTT trees") - try: - rebuild() - transaction.commit() - except backend.DatabaseError: - logging.exception("Unable to rebuild MPTT tree due to exception: rolling back all changes") - transaction.rollback() - -# TODO: Move this into utils and add a post-migrate/syncdb signal handler -# which can automatically rebuild after a fixture load? - -# Based heavily on the code from http://code.google.com/p/django-mptt/issues/detail?id=13 -qn = connection.ops.quote_name - -def rebuild(): - opts = Page._meta - tree = Page.tree - - cursor = connection.cursor() - cursor.execute('UPDATE %(table)s SET %(left)s = 0, %(right)s = 0, %(level)s = 0, %(tree_id)s = 0' % { - 'table': qn(opts.db_table), - 'left': qn(opts.get_field(tree.left_attr).column), - 'right': qn(opts.get_field(tree.right_attr).column), - 'level': qn(opts.get_field(tree.level_attr).column), - 'tree_id': qn(opts.get_field(tree.tree_id_attr).column) - }) - - cursor.execute('SELECT %(id_col)s FROM %(table)s WHERE %(parent_col)s is NULL %(orderby)s' % { - 'id_col': qn(opts.pk.column), - 'table': qn(opts.db_table), - 'parent_col': qn(opts.get_field(tree.parent_attr).column), - 'orderby': 'ORDER BY ' + ', '.join([qn(field) for field in opts.order_insertion_by]) if opts.order_insertion_by else '' - }) - - idx = 0 - for (pk, ) in cursor.fetchall(): - idx += 1 - _rebuild_helper(pk, 1, idx) - transaction.commit_unless_managed() - -def _rebuild_helper(pk, left, tree_id, level=0): - opts = Page._meta - tree = Page.tree - right = left + 1 - - cursor = connection.cursor() - cursor.execute('SELECT %(id_col)s FROM %(table)s WHERE %(parent_col)s = %(parent)d %(orderby)s' % { - 'id_col': qn(opts.pk.column), - 'table': qn(opts.db_table), - 'parent_col': qn(opts.get_field(tree.parent_attr).column), - 'parent': pk, - 'orderby': 'ORDER BY ' + ', '.join([qn(field) for field in opts.order_insertion_by]) if opts.order_insertion_by else '' - }) - - for (child_id, ) in cursor.fetchall(): - right = _rebuild_helper(child_id, right, tree_id, level+1) - - cursor.execute(""" - UPDATE %(table)s - SET - %(left_col)s = %(left)d, - %(right_col)s = %(right)d, - %(level_col)s = %(level)d, - %(tree_id_col)s = %(tree_id)d - WHERE - %(pk_col)s = %(pk)s - """ % { - 'table': qn(opts.db_table), - 'pk_col': qn(opts.pk.column), - 'left_col': qn(opts.get_field(tree.left_attr).column), - 'right_col': qn(opts.get_field(tree.right_attr).column), - 'level_col': qn(opts.get_field(tree.level_attr).column), - 'tree_id_col': qn(opts.get_field(tree.tree_id_attr).column), - 'pk': pk, - 'left': left, - 'right': right, - 'level': level, - 'tree_id': tree_id - }) - - return right + 1 diff --git a/feincms/management/commands/update_rsscontent.py b/feincms/management/commands/update_rsscontent.py deleted file mode 100644 index 35f209876..000000000 --- a/feincms/management/commands/update_rsscontent.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -``update_rsscontent`` ---------------------- - -``update_rsscontent`` should be run as a cronjob when the ``RSSContent`` -content type is used -- the content type itself does not update the -feed by itself. -""" - -from django.core.management.base import BaseCommand - -from feincms.content.rss.models import RSSContent - -class Command(BaseCommand): - help = "Run this as a cronjob." - - def handle(self, date_format='', *args, **options): - # find all concrete content types of RSSContent - for cls in RSSContent._feincms_content_models: - for content in cls.objects.all(): - if date_format: - content.cache_content(date_format=date_format) - else: - content.cache_content() - diff --git a/feincms/models.py b/feincms/models.py index 0a2d2bf46..5e65b2475 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -2,41 +2,28 @@ This is the core of FeinCMS All models defined here are abstract, which means no tables are created in -the feincms\_ namespace. +the feincms namespace. """ -import itertools import operator +import sys import warnings +from collections import OrderedDict +from functools import reduce from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ImproperlyConfigured -from django.db import connection, models +from django.db import connections, models from django.db.models import Q -from django.db.models.fields import FieldDoesNotExist -from django.db.models.loading import get_model from django.forms.widgets import Media -from django.template.loader import render_to_string -from django.utils.datastructures import SortedDict -from django.utils.encoding import force_unicode -from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import force_str +from django.utils.translation import gettext_lazy as _ -from feincms import settings, ensure_completely_loaded -from feincms.utils import get_object, copy_model_instance +from feincms.extensions import ExtensionsMixin +from feincms.utils import ChoicesCharField, copy_model_instance -try: - import reversion -except ImportError: - reversion = None -try: - any -except NameError: - # For Python 2.4 - from feincms.compat import c_any as any - - -class Region(object): +class Region: """ This class represents a region inside a template. Example regions might be 'main' and 'sidebar'. @@ -45,11 +32,11 @@ class Region(object): def __init__(self, key, title, *args): self.key = key self.title = title - self.inherited = args and args[0] == 'inherited' or False + self.inherited = args and args[0] == "inherited" or False self._content_types = [] - def __unicode__(self): - return force_unicode(self.title) + def __str__(self): + return force_str(self.title) @property def content_types(self): @@ -58,16 +45,18 @@ def content_types(self): of (content type key, beautified content type name) tuples """ - return [(ct.__name__.lower(), ct._meta.verbose_name) for ct in self._content_types] + return [ + (ct.__name__.lower(), ct._meta.verbose_name) for ct in self._content_types + ] -class Template(object): +class Template: """ A template is a standard Django template which is used to render a CMS object, most commonly a page. """ - def __init__(self, title, path, regions, key=None, preview_image=None): + def __init__(self, title, path, regions, key=None, preview_image=None, **kwargs): # The key is what will be stored in the database. If key is undefined # use the template path as fallback. if not key: @@ -77,6 +66,10 @@ def __init__(self, title, path, regions, key=None, preview_image=None): self.title = title self.path = path self.preview_image = preview_image + self.singleton = kwargs.get("singleton", False) + self.child_template = kwargs.get("child_template", None) + self.enforce_leaf = kwargs.get("enforce_leaf", False) + self.urlconf = kwargs.get("urlconf", None) def _make_region(data): if isinstance(data, Region): @@ -84,17 +77,17 @@ def _make_region(data): return Region(*data) self.regions = [_make_region(row) for row in regions] - self.regions_dict = dict((r.key, r) for r in self.regions) + self.regions_dict = {r.key: r for r in self.regions} - def __unicode__(self): - return force_unicode(self.title) + def __str__(self): + return force_str(self.title) -class ContentProxy(object): +class ContentProxy: """ The ``ContentProxy`` is responsible for loading the content blocks for all - regions (including content blocks in inherited regions) and assembling media - definitions. + regions (including content blocks in inherited regions) and assembling + media definitions. The content inside a region can be fetched using attribute access with the region key. This is achieved through a custom ``__getattr__`` @@ -104,9 +97,8 @@ class ContentProxy(object): def __init__(self, item): item._needs_content_types() self.item = item - self._cache = { - 'cts': {}, - } + self.db = item._state.db + self._cache = {"cts": {}} def _inherit_from(self): """ @@ -117,7 +109,7 @@ def _inherit_from(self): is good enough (tm) for pages. """ - return self.item.get_ancestors(ascending=True).values_list('pk', flat=True) + return self.item.get_ancestors(ascending=True).values_list("pk", flat=True) def _fetch_content_type_counts(self): """ @@ -136,7 +128,7 @@ def _fetch_content_type_counts(self): } """ - if 'counts' not in self._cache: + if "counts" not in self._cache: counts = self._fetch_content_type_count_helper(self.item.pk) empty_inherited_regions = set() @@ -147,7 +139,8 @@ def _fetch_content_type_counts(self): if empty_inherited_regions: for parent in self._inherit_from(): parent_counts = self._fetch_content_type_count_helper( - parent, regions=tuple(empty_inherited_regions)) + parent, regions=tuple(empty_inherited_regions) + ) counts.update(parent_counts) for key in parent_counts.keys(): @@ -156,35 +149,38 @@ def _fetch_content_type_counts(self): if not empty_inherited_regions: break - self._cache['counts'] = counts - return self._cache['counts'] + self._cache["counts"] = counts + return self._cache["counts"] def _fetch_content_type_count_helper(self, pk, regions=None): - tmpl = ['SELECT %d AS ct_idx, region, COUNT(id) FROM %s WHERE parent_id=%s'] + tmpl = ["SELECT %d AS ct_idx, region, COUNT(id) FROM %s WHERE parent_id=%s"] args = [] if regions: - tmpl.append('AND region IN (' + ','.join(['%%s'] * len(regions)) + ')') + tmpl.append("AND region IN (" + ",".join(["%%s"] * len(regions)) + ")") args.extend(regions * len(self.item._feincms_content_types)) - tmpl.append('GROUP BY region') - tmpl = u' '.join(tmpl) - - sql = ' UNION '.join([tmpl % (idx, cls._meta.db_table, pk)\ - for idx, cls in enumerate(self.item._feincms_content_types)]) - sql = 'SELECT * FROM ( ' + sql + ' ) AS ct ORDER BY ct_idx' + tmpl.append("GROUP BY region") + tmpl = " ".join(tmpl) - cursor = connection.cursor() - cursor.execute(sql, args) + sql = " UNION ".join( + [ + tmpl % (idx, cls._meta.db_table, pk) + for idx, cls in enumerate(self.item._feincms_content_types) + ] + ) + sql = "SELECT * FROM ( " + sql + " ) AS ct ORDER BY ct_idx" _c = {} - for ct_idx, region, count in cursor.fetchall(): - if count: - _c.setdefault(region, []).append((pk, ct_idx)) + with connections[self.db].cursor() as cursor: + cursor.execute(sql, args) + for ct_idx, region, count in cursor.fetchall(): + if count: + _c.setdefault(region, []).append((pk, ct_idx)) return _c - def _popuplate_content_type_caches(self, types): + def _populate_content_type_caches(self, types): """ Populate internal caches for all content types passed """ @@ -192,52 +188,83 @@ def _popuplate_content_type_caches(self, types): counts_by_type = {} for region, counts in self._fetch_content_type_counts().items(): for pk, ct_idx in counts: - counts_by_type.setdefault(self.item._feincms_content_types[ct_idx], []).append((region, pk)) + counts_by_type.setdefault( + self.item._feincms_content_types[ct_idx], [] + ).append((region, pk)) # Resolve abstract to concrete content types - types = tuple(types) - - for type in (type for type in self.item._feincms_content_types if issubclass(type, types)): - counts = counts_by_type.get(type) - if type not in self._cache['cts']: + if types is self.item._feincms_content_types: + # If we come from _fetch_regions, we don't need to do + # any type resolving + content_types = self.item._feincms_content_types + else: + content_types = ( + cls + for cls in self.item._feincms_content_types + if issubclass(cls, tuple(types)) + ) + + for cls in content_types: + counts = counts_by_type.get(cls) + if cls not in self._cache["cts"]: if counts: - self._cache['cts'][type] = list(type.get_queryset( - reduce(operator.or_, (Q(region=r[0], parent=r[1]) for r in counts)))) + self._cache["cts"][cls] = list( + cls.get_queryset().filter( + reduce( + operator.or_, + (Q(region=r[0], parent=r[1]) for r in counts), + ) + ) + ) else: - self._cache['cts'][type] = [] + self._cache["cts"][cls] = [] + + # share this content proxy object between all content items + # so that each can use obj.parent.content to determine its + # relationship to its siblings, etc. + for objects in self._cache["cts"].values(): + for obj in objects: + setattr(obj.parent, "_content_proxy", self) def _fetch_regions(self): """ + Fetches all content types and group content types into regions """ - if 'regions' not in self._cache: - self._popuplate_content_type_caches(self.item._feincms_content_types) + if "regions" not in self._cache: + self._populate_content_type_caches(self.item._feincms_content_types) contents = {} - for type, content_list in self._cache['cts'].items(): + for content_list in self._cache["cts"].values(): for instance in content_list: contents.setdefault(instance.region, []).append(instance) - self._cache['regions'] = dict((region, sorted(instances, key=lambda c: c.ordering))\ - for region, instances in contents.iteritems()) - return self._cache['regions'] + self._cache["regions"] = { + region: sorted(instances, key=lambda c: c.ordering) + for region, instances in contents.items() + } + + return self._cache["regions"] def all_of_type(self, type_or_tuple): """ - Return all content type instances belonging to the type or types passed. - If you want to filter for several types at the same time, type must be - a tuple. + Returns all content type instances belonging to the type or types + passed. If you want to filter for several types at the same time, type + must be a tuple. + + The content type instances are sorted by their ``ordering`` value, + but that isn't necessarily meaningful if the same content type exists + in different regions. """ content_list = [] - if not hasattr(type_or_tuple, '__iter__'): + if not hasattr(type_or_tuple, "__iter__"): type_or_tuple = (type_or_tuple,) - self._popuplate_content_type_caches(type_or_tuple) + self._populate_content_type_caches(type_or_tuple) - for type, contents in self._cache['cts'].items(): + for type, contents in self._cache["cts"].items(): if any(issubclass(type, t) for t in type_or_tuple): content_list.extend(contents) - # TODO: Sort content types by region? return sorted(content_list, key=lambda c: c.ordering) def _get_media(self): @@ -245,16 +272,17 @@ def _get_media(self): Collect the media files of all content types of the current object """ - if 'media' not in self._cache: + if "media" not in self._cache: media = Media() for contents in self._fetch_regions().values(): for content in contents: - if hasattr(content, 'media'): + if hasattr(content, "media"): media = media + content.media - self._cache['media'] = media - return self._cache['media'] + self._cache["media"] = media + return self._cache["media"] + media = property(_get_media) def __getattr__(self, attr): @@ -265,7 +293,7 @@ def __getattr__(self, attr): has the inherited flag set, this method will go up the ancestor chain until either some item contents have found or no ancestors are left. """ - if (attr.startswith('__')): + if attr.startswith("__"): raise AttributeError # Do not trigger loading of real content type models if not necessary @@ -275,73 +303,11 @@ def __getattr__(self, attr): return self._fetch_regions().get(attr, []) -class ExtensionsMixin(object): - @classmethod - def register_extension(cls, register_fn): - """ - Call the register function of an extension. You must override this - if you provide a custom ModelAdmin class and want your extensions to - be able to patch stuff in. - """ - register_fn(cls, None) - - @classmethod - def register_extensions(cls, *extensions): - """ - Register all extensions passed as arguments. - - Extensions should be specified as a string to the python module - containing the extension. If it is a bundled extension of FeinCMS, - you do not need to specify the full python module path -- only - specifying the last part (f.e. ``'seo'`` or ``'translations'``) is - sufficient. - """ - - if not hasattr(cls, '_feincms_extensions'): - cls._feincms_extensions = set() - - here = cls.__module__.split('.')[:-1] - - paths = [ - '.'.join(here + ['extensions']), - '.'.join(here[:-1] + ['extensions']), - 'feincms.module.extensions', - ] - - for ext in extensions: - if ext in cls._feincms_extensions: - continue - - fn = None - if isinstance(ext, basestring): - try: - fn = get_object(ext + '.register') - except ImportError: - for path in paths: - try: - fn = get_object('%s.%s.register' % (path, ext)) - if fn: - break - except ImportError, e: - pass - - if not fn: - raise ImproperlyConfigured, '%s is not a valid extension for %s' % ( - ext, cls.__name__) - - # Not a string, so take our chances and just try to access "register" - else: - fn = ext.register - - cls.register_extension(fn) - cls._feincms_extensions.add(ext) - - def create_base_model(inherit_from=models.Model): """ - This method can be used to create a FeinCMS base model inheriting from your - own custom subclass (f.e. extend ``MPTTModel``). The default is to extend - :class:`django.db.models.Model`. + This method can be used to create a FeinCMS base model inheriting from + your own custom subclass (f.e. extend ``MPTTModel``). The default is to + extend :class:`django.db.models.Model`. """ class Base(inherit_from, ExtensionsMixin): @@ -359,29 +325,30 @@ class Meta: def register_regions(cls, *regions): """ Register a list of regions. Only use this if you do not want to use - multiple templates with this model (read: not use ``register_templates``):: + multiple templates with this model (read: not use + ``register_templates``):: BlogEntry.register_regions( ('main', _('Main content area')), ) """ - if hasattr(cls, 'template'): + if hasattr(cls, "template"): warnings.warn( - 'Ignoring second call to register_regions.', - RuntimeWarning) + "Ignoring second call to register_regions.", RuntimeWarning + ) return # implicitly creates a dummy template object -- the item editor # depends on the presence of a template. - cls.template = Template('', '', regions) + cls.template = Template("", "", regions) cls._feincms_all_regions = cls.template.regions @classmethod def register_templates(cls, *templates): """ - Register templates and add a ``template_key`` field to the model for - saving the selected template:: + Register templates and add a ``template_key`` field to the model + for saving the selected template:: Page.register_templates({ 'key': 'base', @@ -403,11 +370,11 @@ def register_templates(cls, *templates): }) """ - if not hasattr(cls, '_feincms_templates'): - cls._feincms_templates = SortedDict() + if not hasattr(cls, "_feincms_templates"): + cls._feincms_templates = OrderedDict() cls.TEMPLATES_CHOICES = [] - instances = getattr(cls, '_feincms_templates', SortedDict()) + instances = cls._feincms_templates for template in templates: if not isinstance(template, Template): @@ -416,46 +383,65 @@ def register_templates(cls, *templates): instances[template.key] = template try: - field = cls._meta.get_field_by_name('template_key')[0] - except (FieldDoesNotExist, IndexError): - cls.add_to_class('template_key', models.CharField(_('template'), max_length=255, choices=())) - field = cls._meta.get_field_by_name('template_key')[0] + field = next( + iter( + field + for field in cls._meta.local_fields + if field.name == "template_key" + ) + ) + except (StopIteration,): + cls.add_to_class( + "template_key", + ChoicesCharField( + _("template"), + max_length=255, + ), + ) + field = next( + iter( + field + for field in cls._meta.local_fields + if field.name == "template_key" + ) + ) def _template(self): - ensure_completely_loaded() - try: return self._feincms_templates[self.template_key] except KeyError: # return first template as a fallback if the template # has changed in-between return self._feincms_templates[ - self._feincms_templates.keys()[0]] + list(self._feincms_templates.keys())[0] + ] cls.template = property(_template) - cls.TEMPLATE_CHOICES = field._choices = [(template.key, template.title) - for template in cls._feincms_templates.values()] - field.default = field.choices[0][0] + cls.TEMPLATE_CHOICES = [ + (template_.key, template_.title) + for template_ in cls._feincms_templates.values() + ] + field.choices = cls.TEMPLATE_CHOICES + field.default = cls.TEMPLATE_CHOICES[0][0] # Build a set of all regions used anywhere cls._feincms_all_regions = set() for template in cls._feincms_templates.values(): cls._feincms_all_regions.update(template.regions) - #: ``ContentProxy`` class this object uses to collect content blocks content_proxy_class = ContentProxy @property def content(self): """ - Instantiate and return a ``ContentProxy``. You can use your own custom - ``ContentProxy`` by assigning a different class to the + Instantiate and return a ``ContentProxy``. You can use your own + custom ``ContentProxy`` by assigning a different class to the ``content_proxy_class`` member variable. """ - if not hasattr(self, '_content_proxy'): + if not hasattr(self, "_content_proxy"): self._content_proxy = self.content_proxy_class(self) return self._content_proxy @@ -463,99 +449,92 @@ def content(self): @classmethod def _create_content_base(cls): """ - This is purely an internal method. Here, we create a base class for the - concrete content types, which are built in ``create_content_type``. + This is purely an internal method. Here, we create a base class for + the concrete content types, which are built in + ``create_content_type``. - The three fields added to build a concrete content type class/model are - ``parent``, ``region`` and ``ordering``. + The three fields added to build a concrete content type class/model + are ``parent``, ``region`` and ``ordering``. """ - # We need a template, because of the possibility of restricting content types - # to a subset of all available regions. Each region object carries a list of - # all allowed content types around. Content types created before a region is - # initialized would not be available in the respective region; we avoid this - # problem by raising an ImproperlyConfigured exception early. + # We need a template, because of the possibility of restricting + # content types to a subset of all available regions. Each region + # object carries a list of all allowed content types around. + # Content types created before a region is initialized would not be + # available in the respective region; we avoid this problem by + # raising an ImproperlyConfigured exception early. cls._needs_templates() class Meta: abstract = True app_label = cls._meta.app_label - ordering = ['ordering'] - - def __unicode__(self): - return u'%s on %s, ordering %s' % (self.region, self.parent, self.ordering) + ordering = ["ordering"] + + def __str__(self): + return ("%s, region=%s, ordering=%d>") % ( + self.__class__.__name__, + self.pk, + self.parent.__class__.__name__, + self.parent.pk, + self.parent, + self.region, + self.ordering, + ) def render(self, **kwargs): """ - Default render implementation, tries to call a method named after the - region key before giving up. + Default render implementation, tries to call a method named + after the region key before giving up. - You'll probably override the render method itself most of the time - instead of adding region-specific render methods. + You'll probably override the render method itself most of the + time instead of adding region-specific render methods. """ - render_fn = getattr(self, 'render_%s' % self.region, None) + render_fn = getattr(self, "render_%s" % self.region, None) if render_fn: return render_fn(**kwargs) raise NotImplementedError - def fe_render(self, **kwargs): - """ - Frontend Editing enabled renderer - """ - - if 'request' in kwargs: - request = kwargs['request'] - - if request.session and request.session.get('frontend_editing'): - return render_to_string('admin/feincms/fe_box.html', { - 'content': self.render(**kwargs), - 'identifier': self.fe_identifier(), - }) - - return self.render(**kwargs) - - def fe_identifier(self): - """ - Returns an identifier which is understood by the frontend editing - javascript code. (It is used to find the URL which should be used - to load the form for every given block of content.) - """ - - return u'%s-%s-%s-%s-%s' % ( - cls._meta.app_label, - cls._meta.module_name, - self.__class__.__name__.lower(), - self.parent_id, - self.id, - ) - - def get_queryset(cls, filter_args): - return cls.objects.select_related().filter(filter_args) + def get_queryset(cls, filter_args=None): + qs = cls.objects.select_related() + if filter_args is not None: + return qs.filter(filter_args) + return qs attrs = { - '__module__': cls.__module__, # The basic content type is put into - # the same module as the CMS base type. - # If an app_label is not given, Django - # needs to know where a model comes - # from, therefore we ensure that the - # module is always known. - '__unicode__': __unicode__, - 'render': render, - 'fe_render': fe_render, - 'fe_identifier': fe_identifier, - 'get_queryset': classmethod(get_queryset), - 'Meta': Meta, - 'parent': models.ForeignKey(cls, related_name='%(class)s_set'), - 'region': models.CharField(max_length=255), - 'ordering': models.IntegerField(_('ordering'), default=0), - } + # The basic content type is put into + # the same module as the CMS base type. + # If an app_label is not given, Django + # needs to know where a model comes + # from, therefore we ensure that the + # module is always known. + "__module__": cls.__module__, + "__str__": __str__, + "render": render, + "get_queryset": classmethod(get_queryset), + "Meta": Meta, + "parent": models.ForeignKey( + cls, related_name="%(class)s_set", on_delete=models.CASCADE + ), + "region": models.CharField(max_length=255), + "ordering": models.IntegerField(_("ordering"), default=0), + } # create content base type and save reference on CMS class - cls._feincms_content_model = type('%sContent' % cls.__name__, - (models.Model,), attrs) + + name = "_Internal%sContentTypeBase" % cls.__name__ + if hasattr(sys.modules[cls.__module__], name): + warnings.warn( + "The class %s.%s has the same name as the class that " + "FeinCMS auto-generates based on %s.%s. To avoid database" + "errors and import clashes, rename one of these classes." + % (cls.__module__, name, cls.__module__, cls.__name__), + RuntimeWarning, + ) + + cls._feincms_content_model = type(str(name), (models.Model,), attrs) # list of concrete content types cls._feincms_content_types = [] @@ -564,24 +543,31 @@ def get_queryset(cls, filter_args): # before or after rendering the content: # # def process(self, request, **kwargs): - # May return a response early to short-circuit the request-response cycle + # May return a response early to short-circuit the + # request-response cycle # # def finalize(self, request, response) - # May modify the response or replace it entirely by returning a new one + # May modify the response or replace it entirely by returning + # a new one # cls._feincms_content_types_with_process = [] cls._feincms_content_types_with_finalize = [] - # list of item editor context processors, will be extended by content types - if hasattr(cls, 'feincms_item_editor_context_processors'): - cls.feincms_item_editor_context_processors = list(cls.feincms_item_editor_context_processors) + # list of item editor context processors, will be extended by + # content types + if hasattr(cls, "feincms_item_editor_context_processors"): + cls.feincms_item_editor_context_processors = list( + cls.feincms_item_editor_context_processors + ) else: cls.feincms_item_editor_context_processors = [] - # list of templates which should be included in the item editor, will be extended - # by content types - if hasattr(cls, 'feincms_item_editor_includes'): - cls.feincms_item_editor_includes = dict(cls.feincms_item_editor_includes) + # list of templates which should be included in the item editor, + # will be extended by content types + if hasattr(cls, "feincms_item_editor_includes"): + cls.feincms_item_editor_includes = dict( + cls.feincms_item_editor_includes + ) else: cls.feincms_item_editor_includes = {} @@ -590,118 +576,119 @@ def create_content_type(cls, model, regions=None, class_name=None, **kwargs): """ This is the method you'll use to create concrete content types. - If the CMS base class is ``page.models.Page``, its database table will be - ``page_page``. A concrete content type which is created from ``ImageContent`` - will use ``page_page_imagecontent`` as its table. + If the CMS base class is ``page.models.Page``, its database table + will be ``page_page``. A concrete content type which is created + from ``ImageContent`` will use ``page_page_imagecontent`` as its + table. - If you want a content type only available in a subset of regions, you can - pass a list/tuple of region keys as ``regions``. The content type will only - appear in the corresponding tabs in the item editor. + If you want a content type only available in a subset of regions, + you can pass a list/tuple of region keys as ``regions``. The + content type will only appear in the corresponding tabs in the item + editor. If you use two content types with the same name in the same module, name clashes will happen and the content type created first will shadow all subsequent content types. You can work around it by specifying the content type class name using the ``class_name`` - argument. Please note that this will have an effect on the entries in - ``django_content_type``, on ``related_name`` and on the table name - used and should therefore not be changed after running ``syncdb`` for - the first time. + argument. Please note that this will have an effect on the entries + in ``django_content_type``, on ``related_name`` and on the table + name used and should therefore not be changed after running + ``syncdb`` for the first time. Name clashes will also happen if a content type has defined a - relationship and you try to register that content type to more than one - Base model (in different modules). Django will raise an error when it - tries to create the backward relationship. The solution to that problem - is, as shown above, to specify the content type class name with the - ``class_name`` argument. + relationship and you try to register that content type to more than + one Base model (in different modules). Django will raise an error + when it tries to create the backward relationship. The solution to + that problem is, as shown above, to specify the content type class + name with the ``class_name`` argument. If you register a content type to more than one Base class, it is recommended to always specify a ``class_name`` when registering it a second time. - You can pass additional keyword arguments to this factory function. These - keyword arguments will be passed on to the concrete content type, provided - that it has a ``initialize_type`` classmethod. This is used f.e. in - ``MediaFileContent`` to pass a set of possible media positions (f.e. left, - right, centered) through to the content type. + You can pass additional keyword arguments to this factory function. + These keyword arguments will be passed on to the concrete content + type, provided that it has a ``initialize_type`` classmethod. This + is used f.e. in ``MediaFileContent`` to pass a set of possible + media positions (f.e. left, right, centered) through to the content + type. """ if not class_name: class_name = model.__name__ - # prevent double registration and registration of two different content types - # with the same class name because of related_name clashes + # prevent double registration and registration of two different + # content types with the same class name because of related_name + # clashes try: - getattr(cls, '%s_set' % class_name.lower()) + getattr(cls, "%s_set" % class_name.lower()) warnings.warn( - 'Cannot create content type using %s.%s for %s.%s, because %s_set is already taken.' % ( - model.__module__, class_name, - cls.__module__, cls.__name__, - class_name.lower()), - RuntimeWarning) + "Cannot create content type using %s.%s for %s.%s," + " because %s_set is already taken." + % ( + model.__module__, + class_name, + cls.__module__, + cls.__name__, + class_name.lower(), + ), + RuntimeWarning, + ) return except AttributeError: # everything ok pass - # Next name clash test. Happens when the same content type is created - # for two Base subclasses living in the same Django application - # (github issues #73 and #150) - other_model = get_model(cls._meta.app_label, class_name) - if other_model: - warnings.warn( - 'It seems that the content type %s exists twice in %s. Use the class_name argument to create_content_type to avoid this error.' % ( - model.__name__, - cls._meta.app_label), - RuntimeWarning) - if not model._meta.abstract: - raise ImproperlyConfigured, 'Cannot create content type from non-abstract model (yet).' + raise ImproperlyConfigured( + "Cannot create content type from non-abstract model (yet)." + ) - if not hasattr(cls, '_feincms_content_model'): + if not hasattr(cls, "_feincms_content_model"): cls._create_content_base() feincms_content_base = cls._feincms_content_model class Meta(feincms_content_base.Meta): - db_table = '%s_%s' % (cls._meta.db_table, class_name.lower()) + db_table = f"{cls._meta.db_table}_{class_name.lower()}" verbose_name = model._meta.verbose_name verbose_name_plural = model._meta.verbose_name_plural + permissions = model._meta.permissions attrs = { - '__module__': cls.__module__, # put the concrete content type into the - # same module as the CMS base type; this is - # necessary because 1. Django needs to know - # the module where a model lives and 2. a - # content type may be used by several CMS - # base models at the same time (f.e. in - # the blog and the page module). - 'Meta': Meta, - } - - new_type = type( - class_name, - (model, feincms_content_base,), - attrs) + # put the concrete content type into the + # same module as the CMS base type; this is + # necessary because 1. Django needs to know + # the module where a model lives and 2. a + # content type may be used by several CMS + # base models at the same time (f.e. in + # the blog and the page module). + "__module__": cls.__module__, + "Meta": Meta, + } + + new_type = type(str(class_name), (model, feincms_content_base), attrs) cls._feincms_content_types.append(new_type) - if hasattr(getattr(new_type, 'process', None), '__call__'): + if hasattr(getattr(new_type, "process", None), "__call__"): cls._feincms_content_types_with_process.append(new_type) - if hasattr(getattr(new_type, 'finalize', None), '__call__'): + if hasattr(getattr(new_type, "finalize", None), "__call__"): cls._feincms_content_types_with_finalize.append(new_type) # content types can be limited to a subset of regions if not regions: - regions = set([region.key for region in cls._feincms_all_regions]) + regions = {region.key for region in cls._feincms_all_regions} for region in cls._feincms_all_regions: if region.key in regions: region._content_types.append(new_type) - # Add a list of CMS base types for which a concrete content type has - # been created to the abstract content type. This is needed f.e. for the - # update_rsscontent management command, which needs to find all concrete - # RSSContent types, so that the RSS feeds can be fetched - if not hasattr(model, '_feincms_content_models'): + # Add a list of CMS base types for which a concrete content type + # has been created to the abstract content type. This is needed + # f.e. for the update_rsscontent management command, which needs to + # find all concrete RSSContent types, so that the RSS feeds can be + # fetched + if not hasattr(model, "_feincms_content_models"): model._feincms_content_models = [] model._feincms_content_models.append(new_type) @@ -709,55 +696,39 @@ class Meta(feincms_content_base.Meta): # Add a backlink from content-type to content holder class new_type._feincms_content_class = cls - # Handle optgroup argument for grouping content types in the item editor - optgroup = kwargs.pop('optgroup', None) + # Handle optgroup argument for grouping content types in the item + # editor + optgroup = kwargs.pop("optgroup", None) if optgroup: new_type.optgroup = optgroup # customization hook. - if hasattr(new_type, 'initialize_type'): + if hasattr(new_type, "initialize_type"): new_type.initialize_type(**kwargs) else: for k, v in kwargs.items(): setattr(new_type, k, v) # collect item editor context processors from the content type - if hasattr(model, 'feincms_item_editor_context_processors'): + if hasattr(model, "feincms_item_editor_context_processors"): cls.feincms_item_editor_context_processors.extend( - model.feincms_item_editor_context_processors) + model.feincms_item_editor_context_processors + ) # collect item editor includes from the content type - if hasattr(model, 'feincms_item_editor_includes'): - for key, includes in model.feincms_item_editor_includes.items(): - cls.feincms_item_editor_includes.setdefault(key, set()).update(includes) - - # Ensure meta information concerning related fields is up-to-date. - # - # Upon accessing the related fields information from Model._meta, the related - # fields are cached and never refreshed again (because models and model relations - # are defined upon import time, if you do not fumble around with models like we - # do right here.) - # - # Adding related models after this information has been cached leads to models - # not knowing about related items, which again causes the bug we had in - # issue #63 on github: - # - # http://github.com/feincms/feincms/issues/issue/63/ - # - # Currently, all methods filling up the Model.meta cache start with fill_. - # We call all these methods upon creation of a new content type to make sure - # that we really really do not forget a relation somewhere. Of course, we do - # too much here, but better a bit too much upon application startup than not - # enough like before. - for fn in [s for s in dir(cls._meta) if s[:6]=='_fill_']: - getattr(cls._meta, fn)() + if hasattr(model, "feincms_item_editor_includes"): + for key, incls in model.feincms_item_editor_includes.items(): + cls.feincms_item_editor_includes.setdefault(key, set()).update( + incls + ) return new_type @property def _django_content_type(self): - if getattr(self.__class__, '_cached_django_content_type', None) is None: - self.__class__._cached_django_content_type = ContentType.objects.get_for_model(self) + if not getattr(self, "_cached_django_content_type", None): + ct = ContentType.objects.get_for_model(self) + self.__class__._cached_django_content_type = ct return self.__class__._cached_django_content_type @classmethod @@ -769,41 +740,48 @@ def content_type_for(cls, model): concrete_type = Page.content_type_for(VideoContent) """ - if not hasattr(cls, '_feincms_content_types') or not cls._feincms_content_types: + if ( + not hasattr(cls, "_feincms_content_types") + or not cls._feincms_content_types + ): return None for type in cls._feincms_content_types: if issubclass(type, model): - return type + if type.__base__ is model: + return type return None @classmethod def _needs_templates(cls): - # helper which can be used to ensure that either register_regions or - # register_templates has been executed before proceeding - if not hasattr(cls, 'template'): - raise ImproperlyConfigured, 'You need to register at least one template or one region on %s.' % ( - cls.__name__, - ) + # helper which can be used to ensure that either register_regions + # or register_templates has been executed before proceeding + if not hasattr(cls, "template"): + raise ImproperlyConfigured( + "You need to register at least one" + " template or one region on %s." % cls.__name__ + ) @classmethod def _needs_content_types(cls): - ensure_completely_loaded() - - # Check whether any content types have been created for this base class - if not hasattr(cls, '_feincms_content_types') or not cls._feincms_content_types: - raise ImproperlyConfigured, 'You need to create at least one content type for the %s model.' % (cls.__name__) + # Check whether any content types have been created for this base + # class + if not getattr(cls, "_feincms_content_types", None): + raise ImproperlyConfigured( + "You need to create at least one" + " content type for the %s model." % cls.__name__ + ) def copy_content_from(self, obj): """ - Copy all content blocks over to another CMS base object. (Must be of the - same type, but this is not enforced. It will crash if you try to copy content - from another CMS base type.) + Copy all content blocks over to another CMS base object. (Must be + of the same type, but this is not enforced. It will crash if you + try to copy content from another CMS base type.) """ for cls in self._feincms_content_types: for content in cls.objects.filter(parent=obj): - new = copy_model_instance(content, exclude=('id', 'parent')) + new = copy_model_instance(content, exclude=("id", "parent")) new.parent = self new.save() @@ -811,7 +789,8 @@ def replace_content_with(self, obj): """ Replace the content of the current object with content of another. - Deletes all content blocks and calls ``copy_content_from`` afterwards. + Deletes all content blocks and calls ``copy_content_from`` + afterwards. """ for cls in self._feincms_content_types: @@ -819,17 +798,23 @@ def replace_content_with(self, obj): self.copy_content_from(obj) @classmethod - def register_with_reversion(cls): - if not reversion: - raise EnvironmentError("django-reversion is not installed") + def register_with_reversion(cls, **kwargs): + try: + from reversion.revisions import register + except ImportError: + try: + from reversion import register + except ImportError: + raise OSError("django-reversion is not installed") + follow = [] - for content_type_model in cls._feincms_content_types: - related_manager = "%s_set" % content_type_model.__name__.lower() - follow.append(related_manager) - reversion.register(content_type_model) - reversion.register(cls, follow=follow) + for content_type in cls._feincms_content_types: + follow.append("%s_set" % content_type.__name__.lower()) + register(content_type, **kwargs) + register(cls, follow=follow, **kwargs) return Base + # Legacy support Base = create_base_model() diff --git a/feincms/module/blog/admin.py b/feincms/module/blog/admin.py deleted file mode 100644 index 6a399e57c..000000000 --- a/feincms/module/blog/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin - -from feincms.module.blog.models import Entry, EntryAdmin - - -admin.site.register(Entry, EntryAdmin) diff --git a/feincms/module/blog/extensions/tags.py b/feincms/module/blog/extensions/tags.py deleted file mode 100644 index 2838d1b3c..000000000 --- a/feincms/module/blog/extensions/tags.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Simple tagging support using ``django-tagging``. -""" - -from django.utils.translation import ugettext_lazy as _ - -import tagging -from tagging.fields import TagField - - -def register(cls, admin_cls): - cls.add_to_class('tags', TagField(_('tags'))) - - # use another name for the tag descriptor - # See http://code.google.com/p/django-tagging/issues/detail?id=95 for the reason why - tagging.register(cls, tag_descriptor_attr='etags') diff --git a/feincms/module/blog/extensions/translations.py b/feincms/module/blog/extensions/translations.py deleted file mode 100644 index 97cd24404..000000000 --- a/feincms/module/blog/extensions/translations.py +++ /dev/null @@ -1,53 +0,0 @@ -""" -This extension adds a language field to every blog entry. - -Blog entries in secondary languages can be said to be a translation of a -blog entry in the primary language (the first language in settings.LANGUAGES), -thereby enabling deeplinks between translated blog entries. -""" - -from django.conf import settings -from django.db import models -from django.utils.translation import ugettext_lazy as _ - - -def register(cls, admin_cls): - primary_language = settings.LANGUAGES[0][0] - - cls.add_to_class('language', models.CharField(_('language'), max_length=10, - choices=settings.LANGUAGES)) - cls.add_to_class('translation_of', models.ForeignKey('self', - blank=True, null=True, verbose_name=_('translation of'), - related_name='translations', - limit_choices_to={'language': primary_language}, - help_text=_('Leave this empty for entries in the primary language.') - )) - - def available_translations(self): - if self.language == primary_language: - return self.translations.all() - elif self.translation_of: - return [self.translation_of] + list(self.translation_of.translations.exclude( - language=self.language)) - else: - return [] - - cls.available_translations = available_translations - - def available_translations_admin(self): - translations = self.available_translations() - - return u', '.join( - u'%s' % (page.id, page.language.upper()) for page in translations) - - available_translations_admin.allow_tags = True - available_translations_admin.short_description = _('available translations') - cls.available_translations_admin = available_translations_admin - - if getattr(admin_cls, 'fieldsets'): - admin_cls.fieldsets[0][1]['fields'].extend(['language']) - - admin_cls.list_display += ('language', 'available_translations_admin') - admin_cls.list_filter += ('language',) - - admin_cls.raw_id_fields.append('translation_of') diff --git a/feincms/module/blog/models.py b/feincms/module/blog/models.py deleted file mode 100644 index 1627d9c8d..000000000 --- a/feincms/module/blog/models.py +++ /dev/null @@ -1,76 +0,0 @@ -""" -This is more a proof-of-concept for your own :class:`feincms.module.Base` -subclasses than a polished or even sufficient blog module implementation. - -It does work, though. -""" - -from datetime import datetime - -from django.contrib import admin -from django.db import models -from django.db.models import signals -from django.utils.translation import ugettext_lazy as _ - -from feincms.admin import editor -from feincms.management.checker import check_database_schema -from feincms.models import Base -from feincms.utils import get_object - - -class EntryManager(models.Manager): - def published(self): - return self.filter( - published=True, - published_on__isnull=False, - published_on__lte=datetime.now(), - ) - - -class Entry(Base): - published = models.BooleanField(_('published'), default=False) - title = models.CharField(_('title'), max_length=100, - help_text=_('This is used for the generated navigation too.')) - slug = models.SlugField() - - published_on = models.DateTimeField(_('published on'), blank=True, null=True, - help_text=_('Will be set automatically once you tick the `published` checkbox above.')) - - class Meta: - get_latest_by = 'published_on' - ordering = ['-published_on'] - verbose_name = _('entry') - verbose_name_plural = _('entries') - - objects = EntryManager() - - def __unicode__(self): - return self.title - - def save(self, *args, **kwargs): - if self.published and not self.published_on: - self.published_on = datetime.now() - super(Entry, self).save(*args, **kwargs) - - @models.permalink - def get_absolute_url(self): - return ('blog_entry_detail', (self.id,), {}) - - @classmethod - def register_extension(cls, register_fn): - register_fn(cls, EntryAdmin) - - -signals.post_syncdb.connect(check_database_schema(Entry, __name__), weak=False) - - -class EntryAdmin(editor.ItemEditor): - date_hierarchy = 'published_on' - list_display = ('__unicode__', 'published', 'published_on') - list_filter = ('published',) - search_fields = ('title', 'slug',) - prepopulated_fields = { - 'slug': ('title',), - } - - raw_id_fields = [] diff --git a/feincms/module/extensions/changedate.py b/feincms/module/extensions/changedate.py index 89448b7b7..286d144fb 100644 --- a/feincms/module/extensions/changedate.py +++ b/feincms/module/extensions/changedate.py @@ -1,63 +1,11 @@ -# ------------------------------------------------------------------------ -# coding=utf-8 -# ------------------------------------------------------------------------ -""" -Track the modification date for objects. -""" +# flake8: noqa -import os +import warnings -try: - from email.utils import parsedate_tz, mktime_tz -except ImportError: # py 2.4 compat - from email.Utils import parsedate_tz, mktime_tz -from django.db import models -from django.db.models.signals import pre_save -from django.utils.translation import ugettext_lazy as _ - -# ------------------------------------------------------------------------ -def pre_save_handler(sender, instance, **kwargs): - """ - Intercept attempts to save and insert the current date and time into - creation and modification date fields. - """ - from datetime import datetime - - now = datetime.now() - if instance.id is None: - instance.creation_date = now - instance.modification_date = now - -# ------------------------------------------------------------------------ -def dt_to_utc_timestamp(dt): - from time import mktime - return int(mktime(dt.timetuple())) - -def register(cls, admin_cls): - cls.add_to_class('creation_date', models.DateTimeField(_('creation date'), null=True, editable=False)) - cls.add_to_class('modification_date', models.DateTimeField(_('modification date'), null=True, editable=False)) - - if hasattr(cls, 'cache_key_components'): - cls.cache_key_components.append(lambda page: page.modification_date and str(dt_to_utc_timestamp(page.modification_date))) - - cls.last_modified = lambda p: p.modification_date - - pre_save.connect(pre_save_handler, sender=cls) - -# ------------------------------------------------------------------------ -def last_modified_response_processor(page, request, response): - from django.utils.http import http_date - - # Don't include Last-Modified if we don't want to be cached - if "no-cache" in response.get('Cache-Control', ''): - return - - # If we already have a Last-Modified, take the later one - last_modified = dt_to_utc_timestamp(page.last_modified()) - if response.has_header('Last-Modified'): - last_modified = max(last_modified, mktime_tz(parsedate_tz(response['Last-Modified']))) - - response['Last-Modified'] = http_date(last_modified) - -# ------------------------------------------------------------------------ +warnings.warn( + "Import %(name)s from feincms.extensions.%(name)s" + % {"name": __name__.split(".")[-1]}, + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/module/extensions/ct_tracker.py b/feincms/module/extensions/ct_tracker.py index f47ddbdc2..286d144fb 100644 --- a/feincms/module/extensions/ct_tracker.py +++ b/feincms/module/extensions/ct_tracker.py @@ -1,140 +1,11 @@ -# ------------------------------------------------------------------------ -# coding=utf-8 -# ------------------------------------------------------------------------ -# -# ct_tracker.py -# FeinCMS -# -# Created by Martin J. Laubach on 02.10.09. -# Copyright (c) 2009 Martin J. Laubach. All rights reserved. -# Updated in 2011 by Matthias Kestenholz for the 1.3 release. -# -# ------------------------------------------------------------------------ +# flake8: noqa -""" -Track the content types for pages. Instead of gathering the content -types present in each page at run time, save the current state at -saving time, thus saving at least one DB query on page delivery. -""" +import warnings -from django.contrib.contenttypes.models import ContentType -from django.db import models -from django.db.models.signals import class_prepared, post_save, pre_save -from django.utils.translation import ugettext_lazy as _ -from feincms.contrib.fields import JSONField -from feincms.models import ContentProxy - - -INVENTORY_VERSION = 1 -_translation_map_cache = {} - - -# ------------------------------------------------------------------------ -class TrackerContentProxy(ContentProxy): - def _fetch_content_type_counts(self): - """ - If an object with an empty _ct_inventory is encountered, compute all the - content types currently used on that object and save the list in the - object itself. Further requests for that object can then access that - information and find out which content types are used without resorting - to multiple selects on different ct tables. - - It is therefore important that even an "empty" object does not have an - empty _ct_inventory. - """ - - if 'counts' not in self._cache: - if self.item._ct_inventory and \ - self.item._ct_inventory.get('_version_', -1) == INVENTORY_VERSION: - - try: - self._cache['counts'] = self._from_inventory(self.item._ct_inventory) - except KeyError: - pass - - if 'counts' not in self._cache: - super(TrackerContentProxy, self)._fetch_content_type_counts() - - self.item._ct_inventory = self._to_inventory(self._cache['counts']) - self.item.__class__.objects.filter(id=self.item.id).update( - _ct_inventory=self.item._ct_inventory) - - # Run post save handler by hand - if hasattr(self.item, 'get_descendants'): - self.item.get_descendants(include_self=False).update(_ct_inventory=None) - return self._cache['counts'] - - def _translation_map(self): - cls = self.item.__class__ - if not cls in _translation_map_cache: - # Prime translation map and cache it in the class. This needs to be - # done late as opposed to at class definition time as not all information - # is ready, especially when we are doing a "syncdb" the ContentType table - # does not yet exist - map = {} - for idx, fct in enumerate(self.item._feincms_content_types): - dct = ContentType.objects.get_for_model(fct) - - # Rely on non-negative primary keys - map[-dct.id] = idx # From-inventory map - map[idx] = dct.id # To-inventory map - - _translation_map_cache[cls] = map - return _translation_map_cache[cls] - - def _from_inventory(self, inventory): - """ - Transforms the inventory from Django's content types to FeinCMS's - ContentProxy counts format. - """ - - map = self._translation_map() - - return dict((region, [ - (pk, map[-ct]) for pk, ct in items - ]) for region, items in inventory.items() if region!='_version_') - - def _to_inventory(self, counts): - map = self._translation_map() - - inventory = dict((region, [ - (pk, map[ct]) for pk, ct in items - ]) for region, items in counts.items()) - inventory['_version_'] = INVENTORY_VERSION - return inventory - -# ------------------------------------------------------------------------ -def class_prepared_handler(sender, **kwargs): - # It might happen under rare circumstances that not all model classes - # are fully loaded and initialized when the translation map is accessed. - # This leads to (lots of) crashes on the server. Better be safe and - # kill the translation map when any class_prepared signal is received. - _translation_map_cache = {} -class_prepared.connect(class_prepared_handler) - -# ------------------------------------------------------------------------ -def tree_post_save_handler(sender, instance, **kwargs): - """ - Clobber the _ct_inventory attribute of this object and all sub-objects - on save. - """ - - instance.get_descendants(include_self=True).update(_ct_inventory=None) - -# ------------------------------------------------------------------------ -def single_pre_save_handler(sender, instance, **kwargs): - """Clobber the _ct_inventory attribute of this object""" - - instance._ct_inventory = None - -# ------------------------------------------------------------------------ -def register(cls, admin_cls): - cls.add_to_class('_ct_inventory', JSONField(_('content types'), editable=False, blank=True, null=True)) - cls.content_proxy_class = TrackerContentProxy - - if hasattr(cls, 'get_descendants'): - post_save.connect(tree_post_save_handler, sender=cls) - else: - pre_save.connect(single_pre_save_handler, sender=cls) -# ------------------------------------------------------------------------ +warnings.warn( + "Import %(name)s from feincms.extensions.%(name)s" + % {"name": __name__.split(".")[-1]}, + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/module/extensions/datepublisher.py b/feincms/module/extensions/datepublisher.py new file mode 100644 index 000000000..286d144fb --- /dev/null +++ b/feincms/module/extensions/datepublisher.py @@ -0,0 +1,11 @@ +# flake8: noqa + +import warnings + + +warnings.warn( + "Import %(name)s from feincms.extensions.%(name)s" + % {"name": __name__.split(".")[-1]}, + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/module/extensions/featured.py b/feincms/module/extensions/featured.py index f2afafdf0..286d144fb 100644 --- a/feincms/module/extensions/featured.py +++ b/feincms/module/extensions/featured.py @@ -1,17 +1,11 @@ -""" -Add a "featured" field to objects so admins can better direct top content. -""" +# flake8: noqa -from django.db import models -from django.utils.translation import ugettext_lazy as _ +import warnings -def register(cls, admin_cls): - cls.add_to_class('featured', models.BooleanField(_('featured'))) - if hasattr(cls, 'cache_key_components'): - cls.cache_key_components.append(lambda page: page.featured) - - admin_cls.fieldsets.append((_('Featured'), { - 'fields': ('featured',), - 'classes': ('collapse',), - })) +warnings.warn( + "Import %(name)s from feincms.extensions.%(name)s" + % {"name": __name__.split(".")[-1]}, + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/module/extensions/seo.py b/feincms/module/extensions/seo.py index 622dcfff9..286d144fb 100644 --- a/feincms/module/extensions/seo.py +++ b/feincms/module/extensions/seo.py @@ -1,21 +1,11 @@ -""" -Add a keyword and a description field which are helpful for SEO optimization. -""" +# flake8: noqa -from django.db import models -from django.utils.translation import ugettext_lazy as _ +import warnings -def register(cls, admin_cls): - cls.add_to_class('meta_keywords', models.TextField(_('meta keywords'), blank=True, - help_text=_('This will be prepended to the default keyword list.'))) - cls.add_to_class('meta_description', models.TextField(_('meta description'), blank=True, - help_text=_('This will be prepended to the default description.'))) - if admin_cls: - admin_cls.search_fields += ('meta_keywords', 'meta_description') - - if admin_cls.fieldsets: - admin_cls.fieldsets.append((_('Search engine optimization'), { - 'fields': ('meta_keywords', 'meta_description'), - 'classes': ('collapse',), - })) +warnings.warn( + "Import %(name)s from feincms.extensions.%(name)s" + % {"name": __name__.split(".")[-1]}, + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/module/extensions/translations.py b/feincms/module/extensions/translations.py new file mode 100644 index 000000000..286d144fb --- /dev/null +++ b/feincms/module/extensions/translations.py @@ -0,0 +1,11 @@ +# flake8: noqa + +import warnings + + +warnings.warn( + "Import %(name)s from feincms.extensions.%(name)s" + % {"name": __name__.split(".")[-1]}, + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/module/medialibrary/__init__.py b/feincms/module/medialibrary/__init__.py index e69de29bb..bb74687b2 100644 --- a/feincms/module/medialibrary/__init__.py +++ b/feincms/module/medialibrary/__init__.py @@ -0,0 +1,11 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + +import logging + + +# ------------------------------------------------------------------------ +logger = logging.getLogger("feincms.medialibrary") + +# ------------------------------------------------------------------------ diff --git a/feincms/module/medialibrary/admin.py b/feincms/module/medialibrary/admin.py index ad6d7bdbc..b9fa8f33f 100644 --- a/feincms/module/medialibrary/admin.py +++ b/feincms/module/medialibrary/admin.py @@ -1,6 +1,15 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + from django.contrib import admin -from feincms.module.medialibrary.models import Category, CategoryAdmin, MediaFile, MediaFileAdmin +from .modeladmins import CategoryAdmin, MediaFileAdmin +from .models import Category, MediaFile + +# ------------------------------------------------------------------------ admin.site.register(Category, CategoryAdmin) admin.site.register(MediaFile, MediaFileAdmin) + +# ------------------------------------------------------------------------ diff --git a/feincms/module/medialibrary/contents.py b/feincms/module/medialibrary/contents.py new file mode 100644 index 000000000..63a2153fc --- /dev/null +++ b/feincms/module/medialibrary/contents.py @@ -0,0 +1,76 @@ +from django.contrib import admin +from django.core.exceptions import ImproperlyConfigured +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from feincms.admin.item_editor import FeinCMSInline +from feincms.module.medialibrary.fields import ContentWithMediaFile +from feincms.utils.tuple import AutoRenderTuple + + +class MediaFileContentInline(FeinCMSInline): + raw_id_fields = ("mediafile",) + radio_fields = {"type": admin.VERTICAL} + + +class MediaFileContent(ContentWithMediaFile): + """ + Rehashed, backwards-incompatible media file content which does not contain + the problems from v1 anymore. + + Create a media file content as follows:: + + from feincms.content.medialibrary.models import MediaFileContent + Page.create_content_type(MediaFileContent, TYPE_CHOICES=( + ('default', _('Default')), + ('lightbox', _('Lightbox')), + ('whatever', _('Whatever')), + )) + + For a media file of type 'image' and type 'lightbox', the following + templates are tried in order: + + * content/mediafile/image_lightbox.html + * content/mediafile/image.html + * content/mediafile/lightbox.html + * content/mediafile/default.html + + The context contains ``content`` and ``request`` (if available). + """ + + feincms_item_editor_inline = MediaFileContentInline + + class Meta: + abstract = True + verbose_name = _("media file") + verbose_name_plural = _("media files") + + @classmethod + def initialize_type(cls, TYPE_CHOICES=None): + if TYPE_CHOICES is None: + raise ImproperlyConfigured( + "You have to set TYPE_CHOICES when creating a %s" % cls.__name__ + ) + + cls.add_to_class( + "type", + models.CharField( + _("type"), + max_length=20, + choices=TYPE_CHOICES, + default=TYPE_CHOICES[0][0], + ), + ) + + def render(self, **kwargs): + return AutoRenderTuple( + ( + [ + f"content/mediafile/{self.mediafile.type}_{self.type}.html", + "content/mediafile/%s.html" % self.mediafile.type, + "content/mediafile/%s.html" % self.type, + "content/mediafile/default.html", + ], + {"content": self}, + ) + ) diff --git a/feincms/module/medialibrary/fields.py b/feincms/module/medialibrary/fields.py index d8a0b35a3..0910ca540 100644 --- a/feincms/module/medialibrary/fields.py +++ b/feincms/module/medialibrary/fields.py @@ -1,57 +1,102 @@ -from django.contrib.admin.widgets import ForeignKeyRawIdWidget +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + +from django.contrib.admin.widgets import AdminFileWidget, ForeignKeyRawIdWidget from django.db import models -from django.utils.html import escape -from django.utils.text import truncate_words -from django.utils.translation import ugettext_lazy as _ +from django.utils.html import escape, mark_safe +from django.utils.translation import gettext_lazy as _ from feincms.admin.item_editor import FeinCMSInline -from feincms.module.medialibrary.models import MediaFile -from feincms.templatetags import feincms_thumbnail +from feincms.utils import shorten_string + +from .models import MediaFile +from .thumbnail import admin_thumbnail -__all__ = ('MediaFileForeignKey', 'ContentWithMediaFile') +__all__ = ("MediaFileForeignKey", "ContentWithMediaFile") +# ------------------------------------------------------------------------ class MediaFileForeignKeyRawIdWidget(ForeignKeyRawIdWidget): def __init__(self, original): self.__dict__ = original.__dict__ - def label_for_value(self, value): - key = self.rel.get_related_field().name + def label_and_url_for_value(self, value): + label, url = super().label_and_url_for_value(value) + key = "pk" try: - obj = self.rel.to._default_manager.using(self.db).get(**{key: value}) - label = [u' %s' % escape(truncate_words(obj, 14))] + obj = ( + self.rel.model._default_manager.using(self.db) + .filter(**{key: value}) + .first() + ) + label = [" %s" % escape(shorten_string(str(obj)))] + image = admin_thumbnail(obj) - if obj.type == 'image': - image = feincms_thumbnail.thumbnail(obj.file.name, '240x120') - label.append(u'
    ' % image) + if image: + label.append( + '
    ' % image + ) - return u''.join(label) - except (ValueError, self.rel.to.DoesNotExist): - return '' + return mark_safe("".join(label)), url + except (ValueError, self.rel.model.DoesNotExist): + return label, url class MediaFileForeignKey(models.ForeignKey): + """ + Drop-in replacement for Django's ``models.ForeignKey`` which automatically + adds a thumbnail of media files if the media file foreign key is shown + using ``raw_id_fields``. + """ + + def __init__(self, *args, **kwargs): + if not args and "to" not in kwargs: + args = (MediaFile,) + super().__init__(*args, **kwargs) + def formfield(self, **kwargs): - if 'widget' in kwargs and isinstance(kwargs['widget'], ForeignKeyRawIdWidget): - kwargs['widget'] = MediaFileForeignKeyRawIdWidget(kwargs['widget']) - return super(MediaFileForeignKey, self).formfield(**kwargs) + if "widget" in kwargs and isinstance(kwargs["widget"], ForeignKeyRawIdWidget): + kwargs["widget"] = MediaFileForeignKeyRawIdWidget(kwargs["widget"]) + return super().formfield(**kwargs) class ContentWithMediaFile(models.Model): class feincms_item_editor_inline(FeinCMSInline): - raw_id_fields = ('mediafile',) + raw_id_fields = ("mediafile",) - mediafile = MediaFileForeignKey(MediaFile, verbose_name=_('media file'), - related_name='+') + mediafile = MediaFileForeignKey( + MediaFile, + verbose_name=_("media file"), + related_name="+", + on_delete=models.PROTECT, + ) class Meta: abstract = True -try: - from south.modelsinspector import add_introspection_rules - add_introspection_rules(rules=[((MediaFileForeignKey,), [], {},)], - patterns=["^feincms\.module\.medialibrary\.fields"]) -except ImportError: - pass +# ------------------------------------------------------------------------ +class AdminFileWithPreviewWidget(AdminFileWidget): + """ + Simple AdminFileWidget, but detects if the file is an image and + tries to render a small thumbnail besides the input field. + """ + + def render(self, name, value, attrs=None, *args, **kwargs): + r = super().render(name, value, attrs=attrs, *args, **kwargs) + + if value and getattr(value, "instance", None): + image = admin_thumbnail(value.instance) + if image: + r = mark_safe( + ( + '" % image + ) + + r + ) + + return r diff --git a/feincms/module/medialibrary/forms.py b/feincms/module/medialibrary/forms.py new file mode 100644 index 000000000..c2f094322 --- /dev/null +++ b/feincms/module/medialibrary/forms.py @@ -0,0 +1,85 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + +import os + +from django import forms +from django.utils.translation import gettext_lazy as _ + +from feincms import settings + +from . import logger +from .fields import AdminFileWithPreviewWidget +from .models import Category, MediaFile + + +# ------------------------------------------------------------------------ +class MediaCategoryAdminForm(forms.ModelForm): + class Meta: + model = Category + fields = "__all__" + + def clean_parent(self): + data = self.cleaned_data["parent"] + if data is not None and self.instance in data.path_list(): + raise forms.ValidationError(_("This would create a loop in the hierarchy")) + + return data + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["parent"].queryset = self.fields["parent"].queryset.exclude( + pk=self.instance.pk + ) + + +# ------------------------------------------------------------------------ +class MediaFileAdminForm(forms.ModelForm): + class Meta: + model = MediaFile + widgets = {"file": AdminFileWithPreviewWidget} + fields = "__all__" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + if settings.FEINCMS_MEDIAFILE_OVERWRITE and self.instance.id: + field = self.instance.file.field + if not hasattr(field, "_feincms_generate_filename_patched"): + original_generate = field.generate_filename + + def _gen_fname(instance, filename): + if instance.id and hasattr(instance, "original_name"): + logger.info( + "Overwriting file %s with new data" + % (instance.original_name) + ) + instance.file.storage.delete(instance.original_name) + return instance.original_name + + return original_generate(instance, filename) + + field.generate_filename = _gen_fname + field._feincms_generate_filename_patched = True + + def clean_file(self): + if settings.FEINCMS_MEDIAFILE_OVERWRITE and self.instance.id: + new_base, new_ext = os.path.splitext(self.cleaned_data["file"].name) + old_base, old_ext = os.path.splitext(self.instance.file.name) + + if new_ext.lower() != old_ext.lower(): + raise forms.ValidationError( + _( + "Cannot overwrite with different file type (attempt to" + " overwrite a %(old_ext)s with a %(new_ext)s)" + ) + % {"old_ext": old_ext, "new_ext": new_ext} + ) + + self.instance.original_name = self.instance.file.name + + return self.cleaned_data["file"] + + +# ------------------------------------------------------------------------ diff --git a/feincms/module/medialibrary/modeladmins.py b/feincms/module/medialibrary/modeladmins.py new file mode 100644 index 000000000..721b97c04 --- /dev/null +++ b/feincms/module/medialibrary/modeladmins.py @@ -0,0 +1,235 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + +import os + +from django import forms +from django.conf import settings as django_settings +from django.contrib import admin, messages +from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME +from django.contrib.auth.decorators import permission_required +from django.contrib.sites.shortcuts import get_current_site +from django.core.files.images import get_image_dimensions +from django.http import HttpResponseRedirect +from django.shortcuts import render +from django.template.defaultfilters import filesizeformat +from django.urls import reverse +from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _, ngettext +from django.views.decorators.csrf import csrf_protect + +from feincms.extensions import ExtensionModelAdmin +from feincms.translations import admin_translationinline, lookup_translations +from feincms.utils import shorten_string + +from .forms import MediaCategoryAdminForm, MediaFileAdminForm +from .models import Category, MediaFileTranslation +from .thumbnail import admin_thumbnail +from .zip import import_zipfile + + +# ----------------------------------------------------------------------- +class CategoryAdmin(admin.ModelAdmin): + form = MediaCategoryAdminForm + list_display = ["path"] + list_filter = ["parent"] + list_per_page = 25 + search_fields = ["title"] + prepopulated_fields = {"slug": ("title",)} + + +# ------------------------------------------------------------------------ +@admin.action(description=_("Add selected media files to category")) +def assign_category(modeladmin, request, queryset): + class AddCategoryForm(forms.Form): + _selected_action = forms.CharField(widget=forms.MultipleHiddenInput) + category = forms.ModelChoiceField(Category.objects.all()) + + form = None + if "apply" in request.POST: + form = AddCategoryForm(request.POST) + if form.is_valid(): + category = form.cleaned_data["category"] + + count = 0 + for mediafile in queryset: + category.mediafile_set.add(mediafile) + count += 1 + + message = ngettext( + "Successfully added %(count)d media file to %(category)s.", + "Successfully added %(count)d media files to %(category)s.", + count, + ) % {"count": count, "category": category} + modeladmin.message_user(request, message) + return HttpResponseRedirect(request.get_full_path()) + if "cancel" in request.POST: + return HttpResponseRedirect(request.get_full_path()) + + if not form: + form = AddCategoryForm( + initial={"_selected_action": request.POST.getlist(ACTION_CHECKBOX_NAME)} + ) + + return render( + request, + "admin/medialibrary/add_to_category.html", + {"mediafiles": queryset, "category_form": form, "opts": modeladmin.model._meta}, + ) + + +# ------------------------------------------------------------------------- +@admin.action(description=_("Export selected media files as zip file")) +def save_as_zipfile(modeladmin, request, queryset): + from .zip import export_zipfile + + site = get_current_site(request) + try: + zip_name = export_zipfile(site, queryset) + messages.info(request, _("ZIP file exported as %s") % zip_name) + except Exception as e: + messages.error(request, _("ZIP file export failed: %s") % str(e)) + return + + return HttpResponseRedirect(os.path.join(django_settings.MEDIA_URL, zip_name)) + + +# ------------------------------------------------------------------------ +class MediaFileAdmin(ExtensionModelAdmin): + form = MediaFileAdminForm + + save_on_top = True + date_hierarchy = "created" + inlines = [admin_translationinline(MediaFileTranslation)] + list_display = ["admin_thumbnail", "__str__", "file_info", "formatted_created"] + list_display_links = ["__str__"] + list_filter = ["type", "categories"] + list_per_page = 25 + search_fields = ["copyright", "file", "translations__caption"] + filter_horizontal = ("categories",) + actions = [assign_category, save_as_zipfile] + + def get_urls(self): + from django.urls import path + + return [ + path( + "mediafile-bulk-upload/", + self.admin_site.admin_view(MediaFileAdmin.bulk_upload), + {}, + name="mediafile_bulk_upload", + ) + ] + super().get_urls() + + def changelist_view(self, request, extra_context=None): + if extra_context is None: + extra_context = {} + extra_context["categories"] = Category.objects.order_by("title") + return super().changelist_view(request, extra_context=extra_context) + + @admin.display(description=_("Preview")) + def admin_thumbnail(self, obj): + image = admin_thumbnail(obj) + if image: + return mark_safe( + """ + + + """ + % {"url": obj.file.url, "image": image} + ) + return "" + + @admin.display( + description=_("file size"), + ordering="file_size", + ) + def formatted_file_size(self, obj): + return filesizeformat(obj.file_size) + + @admin.display( + description=_("created"), + ordering="created", + ) + def formatted_created(self, obj): + return obj.created.strftime("%Y-%m-%d") + + @admin.display( + description=_("file type"), + ordering="type", + ) + def file_type(self, obj): + t = obj.filetypes_dict[obj.type] + if obj.type == "image": + # get_image_dimensions is expensive / slow if the storage is not + # local filesystem (indicated by availability the path property) + try: + obj.file.path + except NotImplementedError: + return t + try: + d = get_image_dimensions(obj.file.file, close=True) + if d: + t += " %d×%d" % (d[0], d[1]) + except (OSError, TypeError, ValueError) as e: + t += " (%s)" % e + return mark_safe(t) + + @admin.display( + description=_("file info"), + ordering="file", + ) + def file_info(self, obj): + """ + Method for showing the file name in admin. + + Note: This also includes a hidden field that can be used to extract + the file name later on, this can be used to access the file name from + JS, like for example a TinyMCE connector shim. + """ + return mark_safe( + ( + '' + " %s
    %s, %s" + ) + % ( + obj.id, + obj.file.name, + obj.id, + shorten_string(os.path.basename(obj.file.name), max_length=40), + self.file_type(obj), + self.formatted_file_size(obj), + ) + ) + + @staticmethod + @csrf_protect + @permission_required("medialibrary.add_mediafile") + def bulk_upload(request): + if request.method == "POST" and "data" in request.FILES: + try: + count = import_zipfile( + request.POST.get("category"), + request.POST.get("overwrite", False), + request.FILES["data"], + ) + messages.info(request, _("%d files imported") % count) + except Exception as e: + messages.error(request, _("ZIP import failed: %s") % e) + else: + messages.error(request, _("No input file given")) + + return HttpResponseRedirect(reverse("admin:medialibrary_mediafile_changelist")) + + def get_queryset(self, request): + return super().get_queryset(request).transform(lookup_translations()) + + def save_model(self, request, obj, form, change): + if obj.id: + obj.purge_translation_cache() + return super().save_model(request, obj, form, change) + + +# ------------------------------------------------------------------------ diff --git a/feincms/module/medialibrary/models.py b/feincms/module/medialibrary/models.py index ffb3fb860..056eb2852 100644 --- a/feincms/module/medialibrary/models.py +++ b/feincms/module/medialibrary/models.py @@ -1,42 +1,38 @@ # ------------------------------------------------------------------------ -# coding=utf-8 # ------------------------------------------------------------------------ -from datetime import datetime -import logging + import os import re -from PIL import Image -from django import forms -from django.contrib import admin, messages -from django.contrib.auth.decorators import permission_required -from django.contrib.contenttypes.models import ContentType -from django.conf import settings as django_settings from django.db import models -from django.http import HttpResponseRedirect -from django.shortcuts import render_to_response -from django.template.context import RequestContext -from django.template.defaultfilters import filesizeformat, slugify -from django.utils.safestring import mark_safe -from django.utils import translation -from django.utils.translation import ungettext, ugettext_lazy as _ -from django.views.decorators.csrf import csrf_protect +from django.db.models.signals import post_delete +from django.dispatch.dispatcher import receiver +from django.template.defaultfilters import slugify +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ from feincms import settings from feincms.models import ExtensionsMixin -from feincms.templatetags import feincms_thumbnail -from feincms.translations import TranslatedObjectMixin, Translation, \ - TranslatedObjectManager, admin_translationinline +from feincms.translations import ( + TranslatedObjectManager, + TranslatedObjectMixin, + Translation, +) + +from . import logger + # ------------------------------------------------------------------------ class CategoryManager(models.Manager): """ Simple manager which exists only to supply ``.select_related("parent")`` - on querysets since we can't even __unicode__ efficiently without it. + on querysets since we can't even __str__ efficiently without it. """ - def get_query_set(self): - return super(CategoryManager, self).get_query_set().select_related("parent") + + def get_queryset(self): + return super().get_queryset().select_related("parent") + # ------------------------------------------------------------------------ class Category(models.Model): @@ -45,23 +41,30 @@ class Category(models.Model): library. """ - title = models.CharField(_('title'), max_length=200) - parent = models.ForeignKey('self', blank=True, null=True, - related_name='children', limit_choices_to={'parent__isnull': True}, - verbose_name=_('parent')) + title = models.CharField(_("title"), max_length=200) + parent = models.ForeignKey( + "self", + blank=True, + null=True, + on_delete=models.CASCADE, + related_name="children", + limit_choices_to={"parent__isnull": True}, + verbose_name=_("parent"), + ) - slug = models.SlugField(_('slug'), max_length=150) + slug = models.SlugField(_("slug"), max_length=150) class Meta: - ordering = ['parent__title', 'title'] - verbose_name = _('category') - verbose_name_plural = _('categories') + ordering = ["parent__title", "title"] + verbose_name = _("category") + verbose_name_plural = _("categories") + app_label = "medialibrary" objects = CategoryManager() - def __unicode__(self): + def __str__(self): if self.parent_id: - return u'%s - %s' % (self.parent.title, self.title) + return f"{self.parent.title} - {self.title}" return self.title @@ -69,57 +72,58 @@ def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.title) - super(Category, self).save(*args, **kwargs) + super().save(*args, **kwargs) + + save.alters_data = True + def path_list(self): + if self.parent is None: + return [self] + p = self.parent.path_list() + p.append(self) + return p -class CategoryAdmin(admin.ModelAdmin): - list_display = ['parent', 'title'] - list_filter = ['parent'] - list_per_page = 25 - search_fields = ['title'] - prepopulated_fields = { 'slug': ('title',), } + def path(self): + return " - ".join(f.title for f in self.path_list()) # ------------------------------------------------------------------------ class MediaFileBase(models.Model, ExtensionsMixin, TranslatedObjectMixin): """ - Abstract media file class. Includes the :class:`feincms.models.ExtensionsMixin` - because of the (handy) extension mechanism. + Abstract media file class. Includes the + :class:`feincms.models.ExtensionsMixin` because of the (handy) extension + mechanism. """ - file = models.FileField(_('file'), max_length=255, upload_to=settings.FEINCMS_MEDIALIBRARY_UPLOAD_TO) - type = models.CharField(_('file type'), max_length=12, editable=False, choices=()) - created = models.DateTimeField(_('created'), editable=False, default=datetime.now) - copyright = models.CharField(_('copyright'), max_length=200, blank=True) - file_size = models.IntegerField(_("file size"), blank=True, null=True, editable=False) + file = models.FileField( + _("file"), max_length=255, upload_to=settings.FEINCMS_MEDIALIBRARY_UPLOAD_TO + ) + type = models.CharField(_("file type"), max_length=12, editable=False, choices=()) + created = models.DateTimeField(_("created"), editable=False, default=timezone.now) + copyright = models.CharField(_("copyright"), max_length=200, blank=True) + file_size = models.IntegerField( + _("file size"), blank=True, null=True, editable=False + ) - categories = models.ManyToManyField(Category, verbose_name=_('categories'), - blank=True, null=True) + categories = models.ManyToManyField( + Category, verbose_name=_("categories"), blank=True + ) categories.category_filter = True class Meta: abstract = True - verbose_name = _('media file') - verbose_name_plural = _('media files') + ordering = ["-created"] + verbose_name = _("media file") + verbose_name_plural = _("media files") objects = TranslatedObjectManager() - filetypes = [ ] - filetypes_dict = { } - - def formatted_file_size(self): - return filesizeformat(self.file_size) - formatted_file_size.short_description = _("file size") - formatted_file_size.admin_order_field = 'file_size' - - def formatted_created(self): - return self.created.strftime("%Y-%m-%d") - formatted_created.short_description = _("created") - formatted_created.admin_order_field = 'created' + filetypes = [] + filetypes_dict = {} @classmethod def reconfigure(cls, upload_to=None, storage=None): - f = cls._meta.get_field('file') + f = cls._meta.get_field("file") # Ugh. Copied relevant parts from django/db/models/fields/files.py # FileField.__init__ (around line 225) if storage: @@ -132,89 +136,47 @@ def reconfigure(cls, upload_to=None, storage=None): @classmethod def register_filetypes(cls, *types): cls.filetypes[0:0] = types - choices = [ t[0:2] for t in cls.filetypes ] + choices = [t[0:2] for t in cls.filetypes] cls.filetypes_dict = dict(choices) - cls._meta.get_field('type').choices[:] = choices + cls._meta.get_field("type").choices = choices def __init__(self, *args, **kwargs): - super(MediaFileBase, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if self.file: self._original_file_name = self.file.name - def __unicode__(self): - trans = None + def __str__(self): + if settings.FEINCMS_MEDIAFILE_TRANSLATIONS: + trans = None - # This might be provided using a .extra() clause to avoid hundreds of extra queries: - if hasattr(self, "preferred_translation"): - trans = getattr(self, "preferred_translation", u"") - else: try: - trans = unicode(self.translation) + trans = self.translation except models.ObjectDoesNotExist: pass - except AttributeError, e: + except AttributeError: pass - if trans and trans.strip(): - return trans - else: - return os.path.basename(self.file.name) + if trans: + trans = "%s" % trans + if trans.strip(): + return trans + + return os.path.basename(self.file.name) def get_absolute_url(self): return self.file.url - def file_type(self): - t = self.filetypes_dict[self.type] - if self.type == 'image': - # get_image_dimensions is expensive / slow if the storage is not local filesystem (indicated by availability the path property) - try: - self.file.path - except NotImplementedError: - return t - try: - from django.core.files.images import get_image_dimensions - d = get_image_dimensions(self.file.file) - if d: t += " %d×%d" % ( d[0], d[1] ) - except IOError, e: - t += " (%s)" % e.strerror - return t - file_type.admin_order_field = 'type' - file_type.short_description = _('file type') - file_type.allow_tags = True - - def file_info(self): - """ - Method for showing the file name in admin. - - Note: This also includes a hidden field that can be used to extract - the file name later on, this can be used to access the file name from - JS, like for example a TinyMCE connector shim. - """ - from os.path import basename - from feincms.utils import shorten_string - return u' %s
    %s, %s' % ( - self.id, - self.file.name, - shorten_string(basename(self.file.name), max_length=40), - self.file_type(), - self.formatted_file_size(), - ) - file_info.admin_order_field = 'file' - file_info.short_description = _('file info') - file_info.allow_tags = True - def determine_file_type(self, name): """ - >>> t = MediaFileBase() - >>> t.determine_file_type('foobar.jpg') + >>> str(MediaFile().determine_file_type('foobar.jpg')) 'image' - >>> t.determine_file_type('foobar.PDF') + >>> str(MediaFile().determine_file_type('foobar.PDF')) 'pdf' - >>> t.determine_file_type('foobar.jpg.pdf') + >>> str(MediaFile().determine_file_type('foobar.jpg.pdf')) 'pdf' - >>> t.determine_file_type('foobar.jgp') + >>> str(MediaFile().determine_file_type('foobar.jgp')) 'other' - >>> t.determine_file_type('foobar-jpg') + >>> str(MediaFile().determine_file_type('foobar-jpg')) 'other' """ for type_key, type_name, type_test in self.filetypes: @@ -224,258 +186,115 @@ def determine_file_type(self, name): def save(self, *args, **kwargs): if not self.id and not self.created: - self.created = datetime.now() + self.created = timezone.now() self.type = self.determine_file_type(self.file.name) if self.file: try: self.file_size = self.file.size - except (OSError, IOError, ValueError), e: - logging.error("Unable to read file size for %s: %s", self, e) + except (OSError, ValueError) as e: + logger.error(f"Unable to read file size for {self}: {e}") - # Try to detect things that are not really images - if self.type == 'image': - try: - try: - image = Image.open(self.file) - except (OSError, IOError): - image = Image.open(self.file.path) - - # Rotate image based on exif data. - if image: - try: - exif = image._getexif() - except (AttributeError, IOError): - exif = False - # PIL < 1.1.7 chokes on JPEGs with minimal EXIF data and - # throws a KeyError deep in its guts. - except KeyError: - exif = False - - if exif: - orientation = exif.get(274) - rotation = 0 - if orientation == 3: - rotation = 180 - elif orientation == 6: - rotation = 270 - elif orientation == 8: - rotation = 90 - if rotation: - image = image.rotate(rotation) - image.save(self.file.path) - except (OSError, IOError), e: - self.type = self.determine_file_type('***') # It's binary something - - if getattr(self, '_original_file_name', None): + super().save(*args, **kwargs) + + logger.info( + "Saved mediafile %d (%s, type %s, %d bytes)" + % (self.id, self.file.name, self.type, self.file_size or 0) + ) + + # User uploaded a new file. Try to get rid of the old file in + # storage, to avoid having orphaned files hanging around. + if getattr(self, "_original_file_name", None): if self.file.name != self._original_file_name: - self.file.storage.delete(self._original_file_name) + self.delete_mediafile(self._original_file_name) - super(MediaFileBase, self).save(*args, **kwargs) self.purge_translation_cache() + save.alters_data = True + + def delete_mediafile(self, name=None): + if name is None: + name = self.file.name + try: + self.file.storage.delete(name) + except Exception as e: + logger.warn(f"Cannot delete media file {name}: {e}") + + # ------------------------------------------------------------------------ MediaFileBase.register_filetypes( - # Should we be using imghdr.what instead of extension guessing? - ('image', _('Image'), lambda f: re.compile(r'\.(bmp|jpe?g|jp2|jxr|gif|png|tiff?)$', re.IGNORECASE).search(f)), - ('video', _('Video'), lambda f: re.compile(r'\.(mov|m[14]v|mp4|avi|mpe?g|qt|ogv|wmv)$', re.IGNORECASE).search(f)), - ('audio', _('Audio'), lambda f: re.compile(r'\.(au|mp3|m4a|wma|oga|ram|wav)$', re.IGNORECASE).search(f)), - ('pdf', _('PDF document'), lambda f: f.lower().endswith('.pdf')), - ('swf', _('Flash'), lambda f: f.lower().endswith('.swf')), - ('txt', _('Text'), lambda f: f.lower().endswith('.txt')), - ('rtf', _('Rich Text'), lambda f: f.lower().endswith('.rtf')), - ('zip', _('Zip archive'), lambda f: f.lower().endswith('.zip')), - ('doc', _('Microsoft Word'), lambda f: re.compile(r'\.docx?$', re.IGNORECASE).search(f)), - ('xls', _('Microsoft Excel'), lambda f: re.compile(r'\.xlsx?$', re.IGNORECASE).search(f)), - ('ppt', _('Microsoft PowerPoint'), lambda f: re.compile(r'\.pptx?$', re.IGNORECASE).search(f)), - ('other', _('Binary'), lambda f: True), # Must be last - ) + # Should we be using imghdr.what instead of extension guessing? + ( + "image", + _("Image"), + lambda f: re.compile( + r"\.(bmp|jpe?g|jp2|jxr|gif|png|tiff?|webp)$", re.IGNORECASE + ).search(f), + ), + ( + "video", + _("Video"), + lambda f: re.compile( + r"\.(mov|m[14]v|mp4|avi|mpe?g|qt|ogv|wmv|flv)$", re.IGNORECASE + ).search(f), + ), + ( + "audio", + _("Audio"), + lambda f: re.compile(r"\.(au|mp3|m4a|wma|oga|ram|wav)$", re.IGNORECASE).search( + f + ), + ), + ("pdf", _("PDF document"), lambda f: f.lower().endswith(".pdf")), + ("swf", _("Flash"), lambda f: f.lower().endswith(".swf")), + ("txt", _("Text"), lambda f: f.lower().endswith(".txt")), + ("rtf", _("Rich Text"), lambda f: f.lower().endswith(".rtf")), + ("zip", _("Zip archive"), lambda f: f.lower().endswith(".zip")), + ( + "doc", + _("Microsoft Word"), + lambda f: re.compile(r"\.docx?$", re.IGNORECASE).search(f), + ), + ( + "xls", + _("Microsoft Excel"), + lambda f: re.compile(r"\.xlsx?$", re.IGNORECASE).search(f), + ), + ( + "ppt", + _("Microsoft PowerPoint"), + lambda f: re.compile(r"\.pptx?$", re.IGNORECASE).search(f), + ), + ("other", _("Binary"), lambda f: True), # Must be last +) + # ------------------------------------------------------------------------ class MediaFile(MediaFileBase): - @classmethod - def register_extension(cls, register_fn): - register_fn(cls, MediaFileAdmin) + class Meta: + app_label = "medialibrary" + + +@receiver(post_delete, sender=MediaFile) +def _mediafile_post_delete(sender, instance, **kwargs): + instance.delete_mediafile() + logger.info("Deleted mediafile %d (%s)" % (instance.id, instance.file.name)) + -# ------------------------------------------------------------------------ # ------------------------------------------------------------------------ class MediaFileTranslation(Translation(MediaFile)): """ Translated media file caption and description. """ - caption = models.CharField(_('caption'), max_length=200) - description = models.TextField(_('description'), blank=True) + caption = models.CharField(_("caption"), max_length=1000) + description = models.TextField(_("description"), blank=True) class Meta: - verbose_name = _('media file translation') - verbose_name_plural = _('media file translations') + verbose_name = _("media file translation") + verbose_name_plural = _("media file translations") + unique_together = ("parent", "language_code") + app_label = "medialibrary" - def __unicode__(self): + def __str__(self): return self.caption - -#------------------------------------------------------------------------- -def admin_thumbnail(obj): - - if obj.type == 'image': - image = None - try: - image = feincms_thumbnail.thumbnail(obj.file.name, '100x100') - except: - pass - - if image: - return mark_safe(u""" - - - """ % { - 'url': obj.file.url, - 'image': image,}) - return '' -admin_thumbnail.short_description = _('Preview') -admin_thumbnail.allow_tags = True - -#------------------------------------------------------------------------- -def assign_category(modeladmin, request, queryset): - class AddCategoryForm(forms.Form): - _selected_action = forms.CharField(widget=forms.MultipleHiddenInput) - category = forms.ModelChoiceField(Category.objects.all()) - - form = None - if 'apply' in request.POST: - form = AddCategoryForm(request.POST) - if form.is_valid(): - category = form.cleaned_data['category'] - - count = 0 - for mediafile in queryset: - category.mediafile_set.add(mediafile) - count += 1 - - message = ungettext('Successfully added %(count)d media file to %(category)s.', - 'Successfully added %(count)d media files to %(category)s.', - count) % {'count':count, 'category':category} - modeladmin.message_user(request, message) - return HttpResponseRedirect(request.get_full_path()) - if 'cancel' in request.POST: - return HttpResponseRedirect(request.get_full_path()) - - if not form: - form = AddCategoryForm(initial={ - '_selected_action': request.POST.getlist(admin.ACTION_CHECKBOX_NAME), - }) - - return render_to_response('admin/medialibrary/add_to_category.html', { - 'mediafiles': queryset, - 'category_form': form, - }, context_instance=RequestContext(request)) - -assign_category.short_description = _('Add selected media files to category') - -#------------------------------------------------------------------------- - -class MediaFileAdmin(admin.ModelAdmin): - date_hierarchy = 'created' - inlines = [admin_translationinline(MediaFileTranslation)] - list_display = [admin_thumbnail, '__unicode__', 'file_info', 'formatted_created'] - list_display_links = ['__unicode__'] - list_filter = ['type', 'categories'] - list_per_page = 25 - search_fields = ['copyright', 'file', 'translations__caption'] - filter_horizontal = ("categories",) - actions = [assign_category] - - def get_urls(self): - from django.conf.urls.defaults import url, patterns - - urls = super(MediaFileAdmin, self).get_urls() - my_urls = patterns('', - url(r'^mediafile-bulk-upload/$', self.admin_site.admin_view(MediaFileAdmin.bulk_upload), {}, name='mediafile_bulk_upload') - ) - - return my_urls + urls - - def changelist_view(self, request, extra_context=None): - if extra_context is None: - extra_context = {} - extra_context['categories'] = Category.objects.all() - return super(MediaFileAdmin, self).changelist_view(request, extra_context=extra_context) - - @staticmethod - @csrf_protect - @permission_required('medialibrary.add_mediafile') - def bulk_upload(request): - from django.core.urlresolvers import reverse - from django.utils.functional import lazy - - def import_zipfile(request, category_id, data): - import zipfile - from os import path - - category = None - if category_id: - category = Category.objects.get(pk=int(category_id)) - - try: - z = zipfile.ZipFile(data) - - count = 0 - for zi in z.infolist(): - if not zi.filename.endswith('/'): - from django.template.defaultfilters import slugify - from django.core.files.base import ContentFile - - bname = path.basename(zi.filename) - if bname and not bname.startswith(".") and "." in bname: - fname, ext = path.splitext(bname) - target_fname = slugify(fname) + ext.lower() - - mf = MediaFile() - mf.file.save(target_fname, ContentFile(z.read(zi.filename))) - mf.save() - if category: - mf.categories.add(category) - count += 1 - - messages.info(request, _("%d files imported") % count) - except Exception, e: - messages.error(request, _("ZIP file invalid: %s") % str(e)) - return - - if request.method == 'POST' and 'data' in request.FILES: - import_zipfile(request, request.POST.get('category'), request.FILES['data']) - else: - messages.error(request, _("No input file given")) - - return HttpResponseRedirect(reverse('admin:medialibrary_mediafile_changelist')) - - def queryset(self, request): - qs = super(MediaFileAdmin, self).queryset(request) - - # FIXME: This is an ugly hack but it avoids 1-3 queries per *FILE* - # retrieving the translation information - # TODO: This should be adapted to multi-db. - if django_settings.DATABASE_ENGINE == 'postgresql_psycopg2': - qs = qs.extra( - select = { - 'preferred_translation': - """SELECT caption FROM medialibrary_mediafiletranslation - WHERE medialibrary_mediafiletranslation.parent_id = medialibrary_mediafile.id - ORDER BY - language_code = %s DESC, - language_code = %s DESC, - LENGTH(language_code) DESC - LIMIT 1 - """ - }, - select_params = (translation.get_language(), django_settings.LANGUAGE_CODE) - ) - return qs - - def save_model(self, request, obj, form, change): - obj.purge_translation_cache() - return super(MediaFileAdmin, self).save_model(request, obj, form, change) - - -#------------------------------------------------------------------------- - diff --git a/feincms/module/medialibrary/thumbnail.py b/feincms/module/medialibrary/thumbnail.py new file mode 100644 index 000000000..f833ee913 --- /dev/null +++ b/feincms/module/medialibrary/thumbnail.py @@ -0,0 +1,20 @@ +from feincms import settings +from feincms.templatetags import feincms_thumbnail +from feincms.utils import get_object + + +def default_admin_thumbnail(mediafile, dimensions="100x100", **kwargs): + if mediafile.type != "image": + return None + + return feincms_thumbnail.thumbnail(mediafile.file, dimensions) + + +_cached_thumbnailer = None + + +def admin_thumbnail(mediafile, dimensions="100x100"): + global _cached_thumbnailer + if not _cached_thumbnailer: + _cached_thumbnailer = get_object(settings.FEINCMS_MEDIALIBRARY_THUMBNAIL) + return _cached_thumbnailer(mediafile, dimensions=dimensions) diff --git a/feincms/module/medialibrary/zip.py b/feincms/module/medialibrary/zip.py new file mode 100644 index 000000000..182a43ac1 --- /dev/null +++ b/feincms/module/medialibrary/zip.py @@ -0,0 +1,203 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +# +# Created by Martin J. Laubach on 2011-12-07 +# Copyright (c) 2011 Martin J. Laubach. All rights reserved. +# +# ------------------------------------------------------------------------ + + +import json +import os +import time +import zipfile + +from django.conf import settings as django_settings +from django.core.files.base import ContentFile +from django.template.defaultfilters import slugify +from django.utils import timezone + +from .models import Category, MediaFile, MediaFileTranslation + + +# ------------------------------------------------------------------------ +export_magic = "feincms-export-01" + + +# ------------------------------------------------------------------------ +def import_zipfile(category_id, overwrite, data): + """ + Import a collection of media files from a zip file. + + category_id: if set, the pk of a Category that all uploaded + files will have added (eg. cathegory "newly uploaded files") + overwrite: attempt to overwrite existing files. This might + not work with non-trivial storage handlers + """ + category = None + if category_id: + category = Category.objects.get(pk=int(category_id)) + + z = zipfile.ZipFile(data) + + # Peek into zip file to find out whether it contains meta information + is_export_file = False + info = {} + try: + info = json.loads(z.comment) + if info["export_magic"] == export_magic: + is_export_file = True + except Exception: + pass + + # If meta information, do we need to create any categories? + # Also build translation map for category ids. + category_id_map = {} + if is_export_file: + for cat in sorted( + info.get("categories", []), key=lambda k: k.get("level", 999) + ): + new_cat, created = Category.objects.get_or_create( + slug=cat["slug"], title=cat["title"] + ) + category_id_map[cat["id"]] = new_cat + if created and cat.get("parent", 0): + parent_cat = category_id_map.get(cat.get("parent", 0), None) + if parent_cat: + new_cat.parent = parent_cat + new_cat.save() + + count = 0 + for zi in z.infolist(): + if not zi.filename.endswith("/"): + bname = os.path.basename(zi.filename) + if bname and not bname.startswith(".") and "." in bname: + fname, ext = os.path.splitext(bname) + wanted_dir = os.path.dirname(zi.filename) + target_fname = slugify(fname) + ext.lower() + + info = {} + if is_export_file: + info = json.loads(zi.comment) + + mf = None + if overwrite: + full_path = os.path.join(wanted_dir, target_fname) + try: + mf = MediaFile.objects.get(file=full_path) + mf.file.delete(save=False) + except MediaFile.DoesNotExist: + mf = None + + if mf is None: + mf = MediaFile() + if overwrite: + mf.file.field.upload_to = wanted_dir + mf.copyright = info.get("copyright", "") + mf.file.save(target_fname, ContentFile(z.read(zi.filename)), save=False) + mf.save() + + found_metadata = False + if is_export_file: + try: + for tr in info["translations"]: + found_metadata = True + mt, mt_created = MediaFileTranslation.objects.get_or_create( + parent=mf, language_code=tr["lang"] + ) + mt.caption = tr["caption"] + mt.description = tr.get("description", None) + mt.save() + + # Add categories + mf.categories = ( + category_id_map[cat_id] + for cat_id in info.get("categories", []) + ) + except Exception: + pass + + if not found_metadata: + mt = MediaFileTranslation() + mt.parent = mf + mt.caption = fname.replace("_", " ") + mt.save() + + if category: + mf.categories.add(category) + + count += 1 + + return count + + +# ------------------------------------------------------------------------ +def export_zipfile(site, queryset): + now = timezone.now() + zip_name = "export_%s_%04d%02d%02d.zip" % ( + slugify(site.domain), + now.year, + now.month, + now.day, + ) + + zip_data = open(os.path.join(django_settings.MEDIA_ROOT, zip_name), "w") + zip_file = zipfile.ZipFile(zip_data, "w", allowZip64=True) + + # Save the used categories in the zip file's global comment + used_categories = set() + for mf in queryset: + for cat in mf.categories.all(): + used_categories.update(cat.path_list()) + + info = { + "export_magic": export_magic, + "categories": [ + { + "id": cat.id, + "title": cat.title, + "slug": cat.slug, + "parent": cat.parent_id or 0, + "level": len(cat.path_list()), + } + for cat in used_categories + ], + } + zip_file.comment = json.dumps(info) + + for mf in queryset: + ctime = time.localtime(os.stat(mf.file.path).st_ctime) + info = json.dumps( + { + "copyright": mf.copyright, + "categories": [cat.id for cat in mf.categories.all()], + "translations": [ + { + "lang": t.language_code, + "caption": t.caption, + "description": t.description, + } + for t in mf.translations.all() + ], + } + ) + + with open(mf.file.path) as file_data: + zip_info = zipfile.ZipInfo( + filename=mf.file.name, + date_time=( + ctime.tm_year, + ctime.tm_mon, + ctime.tm_mday, + ctime.tm_hour, + ctime.tm_min, + ctime.tm_sec, + ), + ) + zip_info.comment = info + zip_file.writestr(zip_info, file_data.read()) + + return zip_name + + +# ------------------------------------------------------------------------ diff --git a/feincms/module/mixins.py b/feincms/module/mixins.py new file mode 100644 index 000000000..63f5f89c7 --- /dev/null +++ b/feincms/module/mixins.py @@ -0,0 +1,232 @@ +from collections import OrderedDict + +from django.http import Http404 +from django.template import Template +from django.utils.decorators import method_decorator +from django.views import generic +from django.views.generic.base import TemplateResponseMixin + +from feincms import settings +from feincms.content.application.models import standalone + + +class ContentModelMixin: + """ + Mixin for ``feincms.models.Base`` subclasses which need need some degree of + additional control over the request-response cycle. + """ + + #: Collection of request processors + request_processors = None + + #: Collection of response processors + response_processors = None + + @classmethod + def register_request_processor(cls, fn, key=None): + """ + Registers the passed callable as request processor. A request processor + always receives two arguments, the current object and the request. + """ + if cls.request_processors is None: + cls.request_processors = OrderedDict() + cls.request_processors[fn if key is None else key] = fn + + @classmethod + def register_response_processor(cls, fn, key=None): + """ + Registers the passed callable as response processor. A response + processor always receives three arguments, the current object, the + request and the response. + """ + if cls.response_processors is None: + cls.response_processors = OrderedDict() + cls.response_processors[fn if key is None else key] = fn + + # TODO Implement admin_urlname templatetag protocol + @property + def app_label(self): + """ + Implement the admin_urlname templatetag protocol, so one can easily + generate an admin link using :: + + {% url page|admin_urlname:'change' page.id %} + """ + return self._meta.app_label + + @property + def model_name(self): + "See app_label" + return self.__class__.__name__.lower() + + +class ContentObjectMixin(TemplateResponseMixin): + """ + Mixin for Django's class based views which knows how to handle + ``ContentModelMixin`` detail pages. + + This is a mixture of Django's ``SingleObjectMixin`` and + ``TemplateResponseMixin`` conceptually to support FeinCMS' + ``ApplicationContent`` inheritance. It does not inherit + ``SingleObjectMixin`` however, because that would set a + precedence for the way how detail objects are determined + (and would f.e. make the page and blog module implementation + harder). + """ + + context_object_name = None + + def handler(self, request, *args, **kwargs): + if not hasattr(self.request, "_feincms_extra_context"): + self.request._feincms_extra_context = {} + + r = self.run_request_processors() + if r: + return r + + r = self.process_content_types() + if r: + return r + + response = self.render_to_response(self.get_context_data()) + + r = self.finalize_content_types(response) + if r: + return r + + r = self.run_response_processors(response) + if r: + return r + + return response + + def get_template_names(self): + # According to the documentation this method is supposed to return + # a list. However, we can also return a Template instance... + if isinstance(self.template_name, (Template, list, tuple)): + return self.template_name + + if self.template_name: + return [self.template_name] + + self.object._needs_templates() + if self.object.template.path: + return [self.object.template.path] + + # Hopefully someone else has a usable get_template_names() + # implementation... + return super().get_template_names() + + def get_context_data(self, **kwargs): + context = self.request._feincms_extra_context + context[self.context_object_name or "feincms_object"] = self.object + context.update(kwargs) + return super().get_context_data(**context) + + @property + def __name__(self): + """ + Dummy property to make this handler behave like a normal function. + This property is used by django-debug-toolbar + """ + return self.__class__.__name__ + + def run_request_processors(self): + """ + Before rendering an object, run all registered request processors. A + request processor may peruse and modify the page or the request. It can + also return a ``HttpResponse`` for shortcutting the rendering and + returning that response immediately to the client. + """ + if not getattr(self.object, "request_processors", None): + return + + for fn in reversed(list(self.object.request_processors.values())): + r = fn(self.object, self.request) + if r: + return r + + def run_response_processors(self, response): + """ + After rendering an object to a response, the registered response + processors are called to modify the response, eg. for setting cache or + expiration headers, keeping statistics, etc. + """ + if not getattr(self.object, "response_processors", None): + return + + for fn in self.object.response_processors.values(): + r = fn(self.object, self.request, response) + if r: + return r + + def process_content_types(self): + """ + Run the ``process`` method of all content types sporting one + """ + # store eventual Http404 exceptions for re-raising, + # if no content type wants to handle the current self.request + http404 = None + # did any content type successfully end processing? + successful = False + + for content in self.object.content.all_of_type( + tuple(self.object._feincms_content_types_with_process) + ): + try: + r = content.process(self.request, view=self) + if r in (True, False): + successful = r + elif r: + return r + except Http404 as e: + http404 = e + + if not successful: + if http404: + # re-raise stored Http404 exception + raise http404 + + extra_context = self.request._feincms_extra_context + + if ( + not settings.FEINCMS_ALLOW_EXTRA_PATH + and extra_context.get("extra_path", "/") != "/" + # XXX Already inside application content. I'm not sure + # whether this fix is really correct... + and not extra_context.get("app_config") + ): + raise Http404( + "Not found (extra_path %r on %r)" + % (extra_context.get("extra_path", "/"), self.object) + ) + + def finalize_content_types(self, response): + """ + Runs finalize() on content types having such a method, adds headers and + returns the final response. + """ + + for content in self.object.content.all_of_type( + tuple(self.object._feincms_content_types_with_finalize) + ): + r = content.finalize(self.request, response) + if r: + return r + + +class ContentView(ContentObjectMixin, generic.DetailView): + def dispatch(self, request, *args, **kwargs): + if request.method.lower() not in self.http_method_names: + return self.http_method_not_allowed(request, *args, **kwargs) + self.request = request + self.args = args + self.kwargs = kwargs + self.object = self.get_object() + return self.handler(request, *args, **kwargs) + + +class StandaloneView(generic.View): + @method_decorator(standalone) + def dispatch(self, request, *args, **kwargs): + return super().dispatch(request, *args, **kwargs) diff --git a/feincms/module/page/__init__.py b/feincms/module/page/__init__.py index 8b1378917..e69de29bb 100644 --- a/feincms/module/page/__init__.py +++ b/feincms/module/page/__init__.py @@ -1 +0,0 @@ - diff --git a/feincms/module/page/admin.py b/feincms/module/page/admin.py index 9e4c55fbc..a4404b3cb 100644 --- a/feincms/module/page/admin.py +++ b/feincms/module/page/admin.py @@ -1,6 +1,30 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + from django.contrib import admin +from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured + +from feincms import settings + +from .modeladmins import PageAdmin +from .models import Page + + +# ------------------------------------------------------------------------ -from feincms.module.page.models import Page, PageAdmin +if settings.FEINCMS_USE_PAGE_ADMIN: + try: + Page._meta.get_field("template_key") + except FieldDoesNotExist: + raise ImproperlyConfigured( + "The page module requires a 'Page.register_templates()' call " + "somewhere ('Page.register_regions()' is not sufficient). " + "If you're not using the default Page admin, maybe try " + "FEINCMS_USE_PAGE_ADMIN=False to avoid this warning." + ) + admin.site.register(Page, PageAdmin) -admin.site.register(Page, PageAdmin) +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ diff --git a/feincms/module/page/extensions/datepublisher.py b/feincms/module/page/extensions/datepublisher.py deleted file mode 100644 index f4a5ca0f3..000000000 --- a/feincms/module/page/extensions/datepublisher.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Allows setting a date range for when the page is active. Modifies the active() -manager method so that only pages inside the given range are used in the default -views and the template tags. - -Depends on the page class having a "active_filters" list that will be used by -the page's manager to determine which entries are to be considered active. -""" -# ------------------------------------------------------------------------ - -from datetime import datetime - -from django.db import models -from django.db.models import Q -from django.utils.translation import ugettext_lazy as _ - -# ------------------------------------------------------------------------ -def format_date(d, if_none=''): - """ - Format a date in a nice human readable way: Omit the year if it's the current - year. Also return a default value if no date is passed in. - """ - - if d is None: return if_none - - now = datetime.now() - fmt = (d.year == now.year) and '%d.%m' or '%d.%m.%Y' - return d.strftime(fmt) - -def latest_children(self): - return self.get_children().order_by('-publication_date') - -# ------------------------------------------------------------------------ -def granular_now(n=None): - """ - A datetime.now look-alike that returns times rounded to a five minute - boundary. This helps the backend database to optimize/reuse/cache its - queries by not creating a brand new query each time. - - Also useful if you are using johnny-cache or a similar queryset cache. - """ - if n is None: - n = datetime.now() - return datetime(n.year, n.month, n.day, n.hour, (n.minute // 5) * 5) - -# ------------------------------------------------------------------------ -def register(cls, admin_cls): - cls.add_to_class('publication_date', models.DateTimeField(_('publication date'), - default=granular_now)) - cls.add_to_class('publication_end_date', models.DateTimeField(_('publication end date'), - blank=True, null=True, - help_text=_('Leave empty if the entry should stay active forever.'))) - cls.add_to_class('latest_children', latest_children) - - # Patch in rounding the pub and pub_end dates on save - orig_save = cls.save - def granular_save(obj, *args, **kwargs): - if obj.publication_date: - obj.publication_date = granular_now(obj.publication_date) - if obj.publication_end_date: - obj.publication_end_date = granular_now(obj.publication_end_date) - orig_save(obj, *args, **kwargs) - cls.save = granular_save - - # Append publication date active check - if hasattr(cls.objects, 'add_to_active_filters'): - cls.objects.add_to_active_filters( - Q(publication_date__lte=granular_now) & - (Q(publication_end_date__isnull=True) | Q(publication_end_date__gt=granular_now)) - ) - - def datepublisher_admin(self, page): - return u'%s – %s' % ( - format_date(page.publication_date), - format_date(page.publication_end_date, '∞'), - ) - datepublisher_admin.allow_tags = True - datepublisher_admin.short_description = _('visible from - to') - - admin_cls.datepublisher_admin = datepublisher_admin - try: - pos = admin_cls.list_display.index('is_visible_admin') - except ValueError: - pos = len(admin_cls.list_display) - - admin_cls.list_display.insert(pos + 1, 'datepublisher_admin') - - admin_cls.fieldsets.append((_('Date-based publishing'), { - 'fields': ('publication_date', 'publication_end_date'), - 'classes': ('collapse',), - })) - -# ------------------------------------------------------------------------ diff --git a/feincms/module/page/extensions/excerpt.py b/feincms/module/page/extensions/excerpt.py index b13aa174b..2212f2c50 100644 --- a/feincms/module/page/extensions/excerpt.py +++ b/feincms/module/page/extensions/excerpt.py @@ -3,13 +3,25 @@ """ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ -def register(cls, admin_cls): - cls.add_to_class('excerpt', models.TextField(_('excerpt'), blank=True, - help_text=_('Add a brief excerpt summarizing the content of this page.'))) +from feincms import extensions - admin_cls.fieldsets.append((_('Excerpt'), { - 'fields': ('excerpt',), - 'classes': ('collapse',), - })) + +class Extension(extensions.Extension): + def handle_model(self): + self.model.add_to_class( + "excerpt", + models.TextField( + _("excerpt"), + blank=True, + help_text=_( + "Add a brief excerpt summarizing the content of this page." + ), + ), + ) + + def handle_modeladmin(self, modeladmin): + modeladmin.add_extension_options( + _("Excerpt"), {"fields": ("excerpt",), "classes": ("collapse",)} + ) diff --git a/feincms/module/page/extensions/navigation.py b/feincms/module/page/extensions/navigation.py index 153ccd38b..915bd3863 100644 --- a/feincms/module/page/extensions/navigation.py +++ b/feincms/module/page/extensions/navigation.py @@ -3,45 +3,52 @@ This extension allows the website administrator to select an extension which processes, modifies or adds subnavigation entries. The bundled -``feincms_navigation`` template tag knows how to collect navigation entries, +``feincms_nav`` template tag knows how to collect navigation entries, be they real Page instances or extended navigation entries. """ +import types +from collections import OrderedDict + from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ -from feincms.utils import get_object +from feincms import extensions from feincms._internal import monkeypatch_method +from feincms.utils import get_object, shorten_string class TypeRegistryMetaClass(type): """ You can access the list of subclasses as .types + + TODO use NavigationExtension.__subclasses__() instead? """ def __init__(cls, name, bases, attrs): - if not hasattr(cls, 'types'): + if not hasattr(cls, "types"): cls.types = [] else: cls.types.append(cls) -class PagePretender(object): +class PagePretender: """ A PagePretender pretends to be a page, but in reality is just a shim layer that implements enough functionality to inject fake pages eg. into the navigation tree. For use as fake navigation page, you should at least define the following - parameters on creation: title, url, level. If using the translation extension, - also add language. + parameters on creation: title, url, level. If using the translation + extension, also add language. """ - # emulate mptt properties to get the template tags working - class _meta: - level_attr = 'level' + pk = None + + # emulate mptt properties to get the template tags working class _mptt_meta: - level_attr = 'level' + level_attr = "level" def __init__(self, **kwargs): for k, v in kwargs.items(): @@ -57,6 +64,7 @@ def get_level(self): return self.level def get_children(self): + """overwrite this if you want nested extensions using recursetree""" return [] def available_translations(self): @@ -65,24 +73,27 @@ def available_translations(self): def get_original_translation(self, page): return page + def short_title(self): + return shorten_string(self.title) + -class NavigationExtension(object): +class NavigationExtension(metaclass=TypeRegistryMetaClass): """ Base class for all navigation extensions. The name attribute is shown to the website administrator. """ - __metaclass__ = TypeRegistryMetaClass - name = _('navigation extension') + name = _("navigation extension") def children(self, page, **kwargs): """ - This is the method which must be overridden in every navigation extension. + This is the method which must be overridden in every navigation + extension. - It receives the page the extension is attached to, the depth up to which - the navigation should be resolved, and the current request object if it - is available. + It receives the page the extension is attached to, the depth up to + which the navigation should be resolved, and the current request object + if it is available. """ raise NotImplementedError @@ -90,26 +101,78 @@ def children(self, page, **kwargs): def navigation_extension_choices(): for ext in NavigationExtension.types: - yield ('%s.%s' % (ext.__module__, ext.__name__), ext.name) + if issubclass(ext, NavigationExtension) and ext is not NavigationExtension: + yield (f"{ext.__module__}.{ext.__name__}", ext.name) -def register(cls, admin_cls): - cls.add_to_class('navigation_extension', models.CharField(_('navigation extension'), - choices=navigation_extension_choices(), blank=True, null=True, max_length=200, - help_text=_('Select the module providing subpages for this page if you need to customize the navigation.'))) +def get_extension_class(extension): + extension = get_object(extension) + if isinstance(extension, types.ModuleType): + return getattr(extension, "Extension") + return extension - @monkeypatch_method(cls) - def extended_navigation(self, **kwargs): - if not self.navigation_extension: - return self.children.in_navigation() - cls = get_object(self.navigation_extension, fail_silently=True) - if not cls or not callable(cls): - return self.children.in_navigation() +class Extension(extensions.Extension): + ident = "navigation" # TODO actually use this + navigation_extensions = None + + @cached_property + def _extensions(self): + if self.navigation_extensions is None: + return OrderedDict( + (f"{ext.__module__}.{ext.__name__}", ext) + for ext in NavigationExtension.types + if ( + issubclass(ext, NavigationExtension) + and ext is not NavigationExtension + ) + ) - return cls().children(self, **kwargs) + else: + return OrderedDict( + (f"{ext.__module__}.{ext.__name__}", ext) + for ext in map(get_extension_class, self.navigation_extensions) + ) + + def handle_model(self): + choices = [(path, ext.name) for path, ext in self._extensions.items()] + + self.model.add_to_class( + "navigation_extension", + models.CharField( + _("navigation extension"), + choices=choices, + blank=True, + null=True, + max_length=200, + help_text=_( + "Select the module providing subpages for this page if" + " you need to customize the navigation." + ), + ), + ) + + extension = self + + @monkeypatch_method(self.model) + def extended_navigation(self, **kwargs): + if not self.navigation_extension: + return self.children.in_navigation() + + cls = None + + try: + cls = extension._extensions[self.navigation_extension] + except KeyError: + cls = get_object(self.navigation_extension, fail_silently=True) + extension._extensions[self.navigation_extension] = cls + + if cls: + return cls().children(self, **kwargs) + return self.children.in_navigation() - admin_cls.fieldsets.append((_('Navigation extension'), { - 'fields': ('navigation_extension',), - 'classes': ('collapse',), - })) + def handle_modeladmin(self, modeladmin): + modeladmin.add_extension_options( + _("Navigation extension"), + {"fields": ("navigation_extension",), "classes": ("collapse",)}, + ) diff --git a/feincms/module/page/extensions/navigationgroups.py b/feincms/module/page/extensions/navigationgroups.py new file mode 100644 index 000000000..67a0c4f53 --- /dev/null +++ b/feincms/module/page/extensions/navigationgroups.py @@ -0,0 +1,32 @@ +""" +Page navigation groups allow assigning pages to differing navigation lists +such as header, footer and what else. +""" + +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from feincms import extensions + + +class Extension(extensions.Extension): + ident = "navigationgroups" + groups = [("default", _("Default")), ("footer", _("Footer"))] + + def handle_model(self): + self.model.add_to_class( + "navigation_group", + models.CharField( + _("navigation group"), + choices=self.groups, + default=self.groups[0][0], + max_length=20, + blank=True, + db_index=True, + ), + ) + + def handle_modeladmin(self, modeladmin): + modeladmin.add_extension_options("navigation_group") + modeladmin.extend_list("list_display", ["navigation_group"]) + modeladmin.extend_list("list_filter", ["navigation_group"]) diff --git a/feincms/module/page/extensions/relatedpages.py b/feincms/module/page/extensions/relatedpages.py index 9a9146a8c..e0b4a9340 100644 --- a/feincms/module/page/extensions/relatedpages.py +++ b/feincms/module/page/extensions/relatedpages.py @@ -3,21 +3,26 @@ """ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ -from feincms.module.page.models import Page +from feincms import extensions, settings -def register(cls, admin_cls): - cls.add_to_class('related_pages', models.ManyToManyField(Page, blank=True, - related_name='%(app_label)s_%(class)s_related', - null=True, help_text=_('Select pages that should be listed as related content.'))) - try: - admin_cls.filter_horizontal.append('related_pages') - except AttributeError: - admin_cls.filter_horizontal = ['related_pages'] +class Extension(extensions.Extension): + def handle_model(self): + self.model.add_to_class( + "related_pages", + models.ManyToManyField( + settings.FEINCMS_DEFAULT_PAGE_MODEL, + blank=True, + related_name="%(app_label)s_%(class)s_related", + help_text=_("Select pages that should be listed as related content."), + ), + ) - admin_cls.fieldsets.append((_('Related pages'), { - 'fields': ('related_pages',), - 'classes': ('collapse',), - })) + def handle_modeladmin(self, modeladmin): + modeladmin.extend_list("filter_horizontal", ["related_pages"]) + + modeladmin.add_extension_options( + _("Related pages"), {"fields": ("related_pages",), "classes": ("collapse",)} + ) diff --git a/feincms/module/page/extensions/sites.py b/feincms/module/page/extensions/sites.py index 76a7cacbb..4a4da11e8 100644 --- a/feincms/module/page/extensions/sites.py +++ b/feincms/module/page/extensions/sites.py @@ -1,8 +1,9 @@ -from django.utils.translation import ugettext_lazy as _ from django.conf import settings -from django.db import models from django.contrib.sites.models import Site +from django.db import models +from django.utils.translation import gettext_lazy as _ +from feincms import extensions from feincms.module.page.models import PageManager @@ -10,13 +11,21 @@ def current_site(queryset): return queryset.filter(site=Site.objects.get_current()) -def register(cls, admin_cls): - cls.add_to_class('site', - models.ForeignKey(Site, - verbose_name=_('Site'), - default=settings.SITE_ID, )) +class Extension(extensions.Extension): + def handle_model(self): + self.model.add_to_class( + "site", + models.ForeignKey( + Site, + verbose_name=_("Site"), + default=settings.SITE_ID, + on_delete=models.CASCADE, + ), + ) - PageManager.add_to_active_filters(current_site) + PageManager.add_to_active_filters(current_site, key="current_site") - admin_cls.list_display.extend(['site']) - + def handle_modeladmin(self, modeladmin): + modeladmin.extend_list("list_display", ["site"]) + modeladmin.extend_list("list_filter", ["site"]) + modeladmin.add_extension_options("site") diff --git a/feincms/module/page/extensions/symlinks.py b/feincms/module/page/extensions/symlinks.py index 56db992d7..52433da37 100644 --- a/feincms/module/page/extensions/symlinks.py +++ b/feincms/module/page/extensions/symlinks.py @@ -4,30 +4,37 @@ """ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ +from feincms import extensions from feincms._internal import monkeypatch_property -def register(cls, admin_cls): - cls.add_to_class('symlinked_page', models.ForeignKey('self', blank=True, null=True, - related_name='%(app_label)s_%(class)s_symlinks', - verbose_name=_('symlinked page'), - help_text=_('All content is inherited from this page if given.'))) - - @monkeypatch_property(cls) - def content(self): - if not hasattr(self, '_content_proxy'): - if self.symlinked_page: - self._content_proxy = self.content_proxy_class(self.symlinked_page) - else: - self._content_proxy = self.content_proxy_class(self) - - return self._content_proxy - - admin_cls.raw_id_fields.append('symlinked_page') - - admin_cls.fieldsets.append((_('Symlinked page'), { - 'fields': ('symlinked_page',), - 'classes': ('collapse',), - })) +class Extension(extensions.Extension): + def handle_model(self): + self.model.add_to_class( + "symlinked_page", + models.ForeignKey( + "self", + blank=True, + null=True, + on_delete=models.CASCADE, + related_name="%(app_label)s_%(class)s_symlinks", + verbose_name=_("symlinked page"), + help_text=_("All content is inherited from this page if given."), + ), + ) + + @monkeypatch_property(self.model) + def content(self): + if not hasattr(self, "_content_proxy"): + if self.symlinked_page: + self._content_proxy = self.content_proxy_class(self.symlinked_page) + else: + self._content_proxy = self.content_proxy_class(self) + + return self._content_proxy + + def handle_modeladmin(self, modeladmin): + modeladmin.extend_list("raw_id_fields", ["symlinked_page"]) + modeladmin.add_extension_options("symlinked_page") diff --git a/feincms/module/page/extensions/titles.py b/feincms/module/page/extensions/titles.py index 10121baf8..7269c3564 100644 --- a/feincms/module/page/extensions/titles.py +++ b/feincms/module/page/extensions/titles.py @@ -1,46 +1,71 @@ """ -Sometimes, a single title is not enough, you'd like subtitles, and maybe differing -titles in the navigation and in the -tag. -This extension lets you do that. +Sometimes, a single title is not enough, you'd like subtitles, and maybe +differing titles in the navigation and in the <title>-tag. This extension lets +you do that. """ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ +from feincms import extensions from feincms._internal import monkeypatch_property -def register(cls, admin_cls): - cls.add_to_class('_content_title', models.TextField(_('content title'), blank=True, - help_text=_('The first line is the main title, the following lines are subtitles.'))) - cls.add_to_class('_page_title', models.CharField(_('page title'), max_length=100, blank=True, - help_text=_('Page title for browser window. Same as title by default.'))) - - @monkeypatch_property(cls) - def page_title(self): - """ - Use this for the browser window (<title>-tag in the <head> of the HTML document) - """ - - if self._page_title: - return self._page_title - return self.content_title - - @monkeypatch_property(cls) - def content_title(self): - """ - This should be used f.e. for the <h1>-tag - """ - - if not self._content_title: - return self.title - - return self._content_title.splitlines()[0] - - @monkeypatch_property(cls) - def content_subtitle(self): - return u'\n'.join(self._content_title.splitlines()[1:]) - - admin_cls.fieldsets.append((_('Titles'), { - 'fields': ('_content_title', '_page_title'), - 'classes': ('collapse',), - })) + +class Extension(extensions.Extension): + def handle_model(self): + self.model.add_to_class( + "_content_title", + models.TextField( + _("content title"), + blank=True, + help_text=_( + "The first line is the main title, the following" + " lines are subtitles." + ), + ), + ) + + self.model.add_to_class( + "_page_title", + models.CharField( + _("page title"), + max_length=69, + blank=True, + help_text=_( + "Page title for browser window. Same as title by" + " default. Must be 69 characters or fewer." + ), + ), + ) + + @monkeypatch_property(self.model) + def page_title(self): + """ + Use this for the browser window (<title>-tag in the <head> of the + HTML document) + """ + + if self._page_title: + return self._page_title + return self.content_title + + @monkeypatch_property(self.model) + def content_title(self): + """ + This should be used f.e. for the <h1>-tag + """ + + if not self._content_title: + return self.title + + return self._content_title.splitlines()[0] + + @monkeypatch_property(self.model) + def content_subtitle(self): + return "\n".join(self._content_title.splitlines()[1:]) + + def handle_modeladmin(self, modeladmin): + modeladmin.add_extension_options( + _("Titles"), + {"fields": ("_content_title", "_page_title"), "classes": ("collapse",)}, + ) diff --git a/feincms/module/page/extensions/translations.py b/feincms/module/page/extensions/translations.py deleted file mode 100644 index 21885ea0a..000000000 --- a/feincms/module/page/extensions/translations.py +++ /dev/null @@ -1,189 +0,0 @@ -""" -This extension adds a language field to every page. When calling setup_request, -the page's language is activated. -Pages in secondary languages can be said to be a translation of a page in the -primary language (the first language in settings.LANGUAGES), thereby enabling -deeplinks between translated pages. - -This extension requires an activated LocaleMiddleware or something equivalent. -""" - -# ------------------------------------------------------------------------ -from django.conf import settings as django_settings -from django.db import models -from django.http import HttpResponseRedirect -from django.utils import translation -from django.utils.translation import ugettext_lazy as _ - -from feincms import settings -from feincms.translations import is_primary_language -from feincms._internal import monkeypatch_method, monkeypatch_property - -# ------------------------------------------------------------------------ -def user_has_language_set(request): - """ - Determine whether the user has explicitely set a language earlier on. - This is taken later on as an indication that we should not mess with the - site's language settings, after all, the user's decision is what counts. - """ - if hasattr(request, 'session') and request.session.get('django_language') is not None: - return True - if django_settings.LANGUAGE_COOKIE_NAME in request.COOKIES: - return True - return False - -# ------------------------------------------------------------------------ -def translation_set_language(request, select_language): - """ - Set and activate a language, if that language is available. - """ - if translation.check_for_language(select_language): - fallback = False - else: - # The page is in a language that Django has no messages for. - # We display anyhow, but fall back to primary language for - # other messages and other applications. It is *highly* recommended to - # create a new django.po for the language instead of - # using this behaviour. - select_language = django_settings.LANGUAGES[0][0] - fallback = True - - translation.activate(select_language) - request.LANGUAGE_CODE = translation.get_language() - - if hasattr(request, 'session'): - # User has a session, then set this language there - if select_language != request.session.get('django_language'): - request.session['django_language'] = select_language - elif request.method == 'GET' and not fallback: - # No session is active. We need to set a cookie for the language - # so that it persists when the user changes his location to somewhere - # not under the control of the CMS. - # Only do this when request method is GET (mainly, do not abort - # POST requests) - response = HttpResponseRedirect(request.get_full_path()) - response.set_cookie(django_settings.LANGUAGE_COOKIE_NAME, select_language) - return response - -# ------------------------------------------------------------------------ -def translations_request_processor_explicit(page, request): - # If this page is just a redirect, don't do any language specific setup - if page.redirect_to: - return - - # Until further notice, the user might be wanting to switch to the - # page's language... - desired_language = page.language - - # ...except if the user explicitely wants to switch language - if 'set_language' in request.GET: - desired_language = request.GET['set_language'] - # ...or the user already has explicitely set a language, bail out and - # don't change it for him behind her back - elif user_has_language_set(request): - return - - return translation_set_language(request, desired_language) - -# ------------------------------------------------------------------------ -def translations_request_processor_standard(page, request): - # If this page is just a redirect, don't do any language specific setup - if page.redirect_to: - return - - if page.language == translation.get_language(): - return - - return translation_set_language(request, page.language) - -# ------------------------------------------------------------------------ -# ------------------------------------------------------------------------ -def register(cls, admin_cls): - cls.add_to_class('language', models.CharField(_('language'), max_length=10, - choices=django_settings.LANGUAGES, default=django_settings.LANGUAGES[0][0])) - cls.add_to_class('translation_of', models.ForeignKey('self', - blank=True, null=True, verbose_name=_('translation of'), - related_name='translations', - limit_choices_to={'language': django_settings.LANGUAGES[0][0]}, - help_text=_('Leave this empty for entries in the primary language.') - )) - - if settings.FEINCMS_TRANSLATION_POLICY == "EXPLICIT": - cls.register_request_processors(translations_request_processor_explicit) - else: # STANDARD - cls.register_request_processors(translations_request_processor_standard) - - @monkeypatch_method(cls) - def get_redirect_to_target(self, request): - """ - Find an acceptable redirect target. If this is a local link, then try - to find the page this redirect references and translate it according - to the user's language. This way, one can easily implement a localized - "/"-url to welcome page redirection. - """ - target = self.redirect_to - if target and target.find('//') == -1: # Not an offsite link http://bla/blubb - try: - page = cls.objects.page_for_path(target) - page = page.get_translation(getattr(request, 'LANGUAGE_CODE', None)) - target = page.get_absolute_url() - except cls.DoesNotExist: - pass - return target - - @monkeypatch_method(cls) - def available_translations(self): - if not self.id: # New, unsaved pages have no translations - return [] - if is_primary_language(self.language): - return self.translations.all() - elif self.translation_of: - return [self.translation_of] + list(self.translation_of.translations.exclude( - language=self.language)) - else: - return [] - - @monkeypatch_method(cls) - def get_original_translation(self, *args, **kwargs): - if is_primary_language(self.language): - return self - return self.translation_of - - @monkeypatch_property(cls) - def original_translation(self): - return self.get_original_translation() - - @monkeypatch_method(cls) - def get_translation(self, language): - return self.original_translation.translations.get(language=language) - - def available_translations_admin(self, page): - translations = dict((p.language, p.id) for p in page.available_translations()) - - links = [] - - for key, title in django_settings.LANGUAGES: - if key == page.language: - continue - - if key in translations: - links.append(u'<a href="%s/" title="%s">%s</a>' % ( - translations[key], _('Edit translation'), key.upper())) - else: - links.append(u'<a style="color:#baa" href="add/?translation_of=%s&language=%s" title="%s">%s</a>' % ( - page.id, key, _('Create translation'), key.upper())) - - return u' | '.join(links) - - available_translations_admin.allow_tags = True - available_translations_admin.short_description = _('translations') - admin_cls.available_translations_admin = available_translations_admin - - admin_cls.fieldsets[0][1]['fields'].extend(['language', 'translation_of']) - admin_cls.list_display.extend(['language', 'available_translations_admin']) - admin_cls.list_filter.extend(['language']) - - admin_cls.raw_id_fields.append('translation_of') - -# ------------------------------------------------------------------------ -# ------------------------------------------------------------------------ diff --git a/feincms/module/page/forms.py b/feincms/module/page/forms.py new file mode 100644 index 000000000..8e9415888 --- /dev/null +++ b/feincms/module/page/forms.py @@ -0,0 +1,226 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + +import re + +from django.apps import apps +from django.contrib.admin.widgets import ForeignKeyRawIdWidget +from django.forms.models import model_to_dict +from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ +from mptt.forms import MPTTAdminForm + + +class RedirectToWidget(ForeignKeyRawIdWidget): + def label_for_value(self, value): + match = re.match( + # XXX this regex would be available as .models.REDIRECT_TO_RE + r"^(?P<app_label>\w+).(?P<model_name>\w+):(?P<pk>\d+)$", + value, + ) + + if match: + matches = match.groupdict() + model = apps.get_model(matches["app_label"], matches["model_name"]) + try: + instance = model._default_manager.get(pk=int(matches["pk"])) + return " <strong>{} ({})</strong>".format( + instance, + instance.get_absolute_url(), + ) + + except model.DoesNotExist: + pass + + return "" + + +# ------------------------------------------------------------------------ +class PageAdminForm(MPTTAdminForm): + never_copy_fields = ( + "title", + "slug", + "parent", + "active", + "override_url", + "translation_of", + "_content_title", + "_page_title", + ) + + @property + def page_model(self): + return self._meta.model + + @property + def page_manager(self): + return self.page_model._default_manager + + def __init__(self, *args, **kwargs): + if "initial" in kwargs: + if "parent" in kwargs["initial"]: + # Prefill a few form values from the parent page + try: + page = self.page_manager.get(pk=kwargs["initial"]["parent"]) + + data = model_to_dict(page) + + for field in self.page_manager.exclude_from_copy: + if field in data: + del data[field] + + # These are always excluded from prefilling + for field in self.never_copy_fields: + if field in data: + del data[field] + + data.update(kwargs["initial"]) + if page.template.child_template: + data["template_key"] = page.template.child_template + kwargs["initial"] = data + except self.page_model.DoesNotExist: + pass + + elif "translation_of" in kwargs["initial"]: + # Only if translation extension is active + try: + page = self.page_manager.get(pk=kwargs["initial"]["translation_of"]) + original = page.original_translation + + data = { + "translation_of": original.id, + "template_key": original.template_key, + "active": original.active, + "in_navigation": original.in_navigation, + } + + if original.parent: + try: + data["parent"] = original.parent.get_translation( + kwargs["initial"]["language"] + ).id + except self.page_model.DoesNotExist: + # ignore this -- the translation does not exist + pass + + data.update(kwargs["initial"]) + kwargs["initial"] = data + except (AttributeError, self.page_model.DoesNotExist): + pass + + # Not required, only a nice-to-have for the `redirect_to` field + modeladmin = kwargs.pop("modeladmin", None) + super().__init__(*args, **kwargs) + if modeladmin and "redirect_to" in self.fields: + # Note: Using `parent` is not strictly correct, but we can be + # sure that `parent` always points to another page instance, + # and that's good enough for us. + field = self.page_model._meta.get_field("parent") + self.fields["redirect_to"].widget = RedirectToWidget( + field.remote_field if hasattr(field, "remote_field") else field.rel, # noqa + modeladmin.admin_site, + ) + + if "template_key" in self.fields: + choices = [] + for key, template_name in self.page_model.TEMPLATE_CHOICES: + template = self.page_model._feincms_templates[key] + pages_for_template = self.page_model._default_manager.filter( + template_key=key + ) + pk = kwargs["instance"].pk if kwargs.get("instance") else None + other_pages_for_template = pages_for_template.exclude(pk=pk) + if template.singleton and other_pages_for_template.exists(): + continue # don't allow selection of singleton if in use + if template.preview_image: + choices.append( + ( + template.key, + mark_safe( + '<img src="%s" alt="%s" /> %s' + % (template.preview_image, template.key, template.title) + ), + ) + ) + else: + choices.append((template.key, template.title)) + + self.fields["template_key"].choices = choices + + def clean(self): + cleaned_data = super().clean() + + # No need to think further, let the user correct errors first + if self._errors: + return cleaned_data + + current_id = None + # See the comment below on why we do not use Page.objects.active(), + # at least for now. + active_pages = self.page_manager.filter(active=True) + + if self.instance: + current_id = self.instance.id + active_pages = active_pages.exclude(id=current_id) + + sites_is_installed = apps.is_installed("django.contrib.sites") + if sites_is_installed and "site" in cleaned_data: + active_pages = active_pages.filter(site=cleaned_data["site"]) + + # Convert PK in redirect_to field to something nicer for the future + redirect_to = cleaned_data.get("redirect_to") + if redirect_to and re.match(r"^\d+$", redirect_to): + opts = self.page_model._meta + cleaned_data["redirect_to"] = "{}.{}:{}".format( + opts.app_label, + opts.model_name, + redirect_to, + ) + + if "active" in cleaned_data and not cleaned_data["active"]: + # If the current item is inactive, we do not need to conduct + # further validation. Note that we only check for the flag, not + # for any other active filters. This is because we do not want + # to inspect the active filters to determine whether two pages + # really won't be active at the same time. + return cleaned_data + + if "override_url" in cleaned_data and cleaned_data["override_url"]: + if active_pages.filter(_cached_url=cleaned_data["override_url"]).count(): + self._errors["override_url"] = self.error_class( + [_("This URL is already taken by an active page.")] + ) + del cleaned_data["override_url"] + + return cleaned_data + + if current_id: + # We are editing an existing page + parent = self.page_manager.get(pk=current_id).parent + else: + # The user tries to create a new page + parent = cleaned_data["parent"] + + if parent: + new_url = "{}{}/".format(parent._cached_url, cleaned_data["slug"]) + else: + new_url = "/%s/" % cleaned_data["slug"] + + if active_pages.filter(_cached_url=new_url).count(): + self._errors["active"] = self.error_class( + [_("This URL is already taken by another active page.")] + ) + del cleaned_data["active"] + + if parent and parent.template.enforce_leaf: + self._errors["parent"] = self.error_class( + [_("This page does not allow attachment of child pages")] + ) + del cleaned_data["parent"] + + return cleaned_data + + +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ diff --git a/feincms/module/page/modeladmins.py b/feincms/module/page/modeladmins.py new file mode 100644 index 000000000..2855a1c5f --- /dev/null +++ b/feincms/module/page/modeladmins.py @@ -0,0 +1,257 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + +from functools import partial +from threading import local + +from django.conf import settings as django_settings +from django.contrib import admin, messages +from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import PermissionDenied +from django.http import HttpResponseRedirect +from django.templatetags.static import static +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ + +from feincms import settings +from feincms.admin import item_editor, tree_editor + +# ------------------------------------------------------------------------ +from .forms import PageAdminForm + + +_local = local() + + +# ------------------------------------------------------------------------ +class PageAdmin(item_editor.ItemEditor, tree_editor.TreeEditor): + class Media: + css = {} + js = [] + + form = PageAdminForm + + fieldset_insertion_index = 2 + fieldsets = [ + (None, {"fields": [("title", "slug"), ("active", "in_navigation")]}), + ( + _("Other options"), + { + "classes": ["collapse"], + "fields": ["template_key", "parent", "override_url", "redirect_to"], + }, + ), + # <-- insertion point, extensions appear here, see insertion_index + # above + item_editor.FEINCMS_CONTENT_FIELDSET, + ] + readonly_fields = [] + list_display = [ + "short_title", + "is_visible_admin", + "in_navigation_toggle", + "template", + ] + list_filter = ["active", "in_navigation", "template_key", "parent"] + search_fields = ["title", "slug"] + prepopulated_fields = {"slug": ("title",)} + + raw_id_fields = ["parent"] + radio_fields = {"template_key": admin.HORIZONTAL} + + @classmethod + def add_extension_options(cls, *f): + if isinstance(f[-1], dict): # called with a fieldset + cls.fieldsets.insert(cls.fieldset_insertion_index, f) + f[1]["classes"] = list(f[1].get("classes", [])) + f[1]["classes"].append("collapse") + else: # assume called with "other" fields + cls.fieldsets[1][1]["fields"].extend(f) + + def __init__(self, model, admin_site): + if len(model._feincms_templates) > 4 and "template_key" in self.radio_fields: + del self.radio_fields["template_key"] + + super().__init__(model, admin_site) + + in_navigation_toggle = tree_editor.ajax_editable_boolean( + "in_navigation", _("in navigation") + ) + + def get_readonly_fields(self, request, obj=None): + readonly = super().get_readonly_fields(request, obj=obj) + if not settings.FEINCMS_SINGLETON_TEMPLATE_CHANGE_ALLOWED: + if obj and obj.template and obj.template.singleton: + return tuple(readonly) + ("template_key",) + return readonly + + def get_form(self, *args, **kwargs): + form = super().get_form(*args, **kwargs) + return partial(form, modeladmin=self) + + def _actions_column(self, page): + addable = getattr(page, "feincms_addable", True) + + preview_url = "../../r/{}/{}/".format( + ContentType.objects.get_for_model(self.model).id, + page.id, + ) + actions = super()._actions_column(page) + + if addable: + if not page.template.enforce_leaf: + actions.insert( + 0, + '<a href="add/?parent=%s" title="%s">' + '<img src="%s" alt="%s" />' + "</a>" + % ( + page.pk, + _("Add child page"), + static("feincms/img/icon_addlink.gif"), + _("Add child page"), + ), + ) + actions.insert( + 0, + '<a href="%s" title="%s">' + '<img src="%s" alt="%s" />' + "</a>" + % ( + preview_url, + _("View on site"), + static("feincms/img/selector-search.gif"), + _("View on site"), + ), + ) + return actions + + def add_view(self, request, **kwargs): + kwargs["form_url"] = request.get_full_path() # Preserve GET parameters + if "translation_of" in request.GET and "language" in request.GET: + try: + original = self.model._tree_manager.get( + pk=request.GET.get("translation_of") + ) + except (AttributeError, self.model.DoesNotExist): + pass + else: + language_code = request.GET["language"] + language = dict(django_settings.LANGUAGES).get(language_code, "") + kwargs["extra_context"] = { + "adding_translation": True, + "title": _('Add %(language)s translation of "%(page)s"') + % {"language": language, "page": original}, + "language_name": language, + "translation_of": original, + } + return super().add_view(request, **kwargs) + + def response_add(self, request, obj, *args, **kwargs): + response = super().response_add(request, obj, *args, **kwargs) + if ( + "parent" in request.GET + and "_addanother" in request.POST + and response.status_code in (301, 302) + ): + # Preserve GET parameters if we are about to add another page + response["Location"] += "?parent=%s" % request.GET["parent"] + + if ( + "translation_of" in request.GET + and "_copy_content_from_original" in request.POST + ): + # Copy all contents + for content_type in obj._feincms_content_types: + if content_type.objects.filter(parent=obj).exists(): + # Short-circuit processing -- don't copy any contents if + # newly added object already has some + return response + + try: + original = self.model._tree_manager.get( + pk=request.GET.get("translation_of") + ) + original = original.original_translation + obj.copy_content_from(original) + obj.save() + + self.message_user( + request, + _( + "The content from the original translation has been copied" + " to the newly created page." + ), + ) + except (AttributeError, self.model.DoesNotExist): + pass + + return response + + def change_view(self, request, object_id, **kwargs): + try: + return super().change_view(request, object_id, **kwargs) + except PermissionDenied: + messages.add_message( + request, + messages.ERROR, + _("You don't have the necessary permissions to edit this object"), + ) + return HttpResponseRedirect(reverse("admin:page_page_changelist")) + + def has_delete_permission(self, request, obj=None): + if not settings.FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED: + if obj and obj.template.singleton: + return False + return super().has_delete_permission(request, obj=obj) + + def changelist_view(self, request, *args, **kwargs): + _local.visible_pages = list( + self.model.objects.active().values_list("id", flat=True) + ) + return super().changelist_view(request, *args, **kwargs) + + @admin.display(description=_("is active")) + def is_visible_admin(self, page): + """ + Instead of just showing an on/off boolean, also indicate whether this + page is not visible because of publishing dates or inherited status. + """ + if page.parent_id and page.parent_id not in _local.visible_pages: + # parent page's invisibility is inherited + if page.id in _local.visible_pages: + _local.visible_pages.remove(page.id) + return tree_editor.ajax_editable_boolean_cell( + page, "active", override=False, text=_("inherited") + ) + + if page.active and page.id not in _local.visible_pages: + # is active but should not be shown, so visibility limited by + # extension: show a "not active" + return tree_editor.ajax_editable_boolean_cell( + page, "active", override=False, text=_("extensions") + ) + + return tree_editor.ajax_editable_boolean_cell(page, "active") + + is_visible_admin.editable_boolean_field = "active" + + # active toggle needs more sophisticated result function + def is_visible_recursive(self, page): + # Have to refresh visible_pages here, because TreeEditor.toggle_boolean + # will have changed the value when inside this code path. + _local.visible_pages = list( + self.model.objects.active().values_list("id", flat=True) + ) + + retval = [] + for c in page.get_descendants(include_self=True): + retval.append(self.is_visible_admin(c)) + return retval + + is_visible_admin.editable_boolean_result = is_visible_recursive + + +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ diff --git a/feincms/module/page/models.py b/feincms/module/page/models.py index e3254d171..b269b9867 100644 --- a/feincms/module/page/models.py +++ b/feincms/module/page/models.py @@ -1,113 +1,32 @@ # ------------------------------------------------------------------------ -# coding=utf-8 # ------------------------------------------------------------------------ -try: - from hashlib import md5 -except ImportError: - import md5 -import re -import sys -import warnings - -from django import forms -from django.core.cache import cache as django_cache from django.core.exceptions import PermissionDenied -from django.conf import settings as django_settings -from django.contrib.contenttypes.models import ContentType -from django.contrib.sites.models import Site -from django.contrib import admin -from django.core.urlresolvers import reverse -from django.db import models -from django.db.models import Q, signals -from django.forms.models import model_to_dict -from django.forms.util import ErrorList -from django.http import Http404, HttpResponseRedirect -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _, ugettext -from django.db.transaction import commit_on_success - -import mptt - -from feincms import settings, ensure_completely_loaded -from feincms.admin import editor -from feincms.admin import item_editor -from feincms.management.checker import check_database_schema -from feincms.models import Base, create_base_model -from feincms.utils import get_object, copy_model_instance -import feincms.admin.filterspecs - -# ------------------------------------------------------------------------ -class ActiveAwareContentManagerMixin(object): - """ - Implement what's necessary to add some kind of "active" state for content - objects. The notion of active is defined by a number of filter rules that - must all match (AND) for the object to be active. - - A Manager for a content class using the "datepublisher" extension - should either adopt this mixin or implement a similar interface. - """ +from django.db import models, transaction +from django.db.models import Q +from django.http import Http404 +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ +from mptt.models import MPTTModel, TreeManager - # A list of filters which are used to determine whether a page is active or not. - # Extended for example in the datepublisher extension (date-based publishing and - # un-publishing of pages) - active_filters = () +from feincms import settings +from feincms.models import create_base_model +from feincms.module.mixins import ContentModelMixin +from feincms.module.page import processors +from feincms.utils import get_model_instance, match_model_string, shorten_string +from feincms.utils.managers import ActiveAwareContentManagerMixin - @classmethod - def apply_active_filters(cls, queryset): - """ - Apply all filters defined to the queryset passed and return the result. - """ - for filt in cls.active_filters: - if callable(filt): - queryset = filt(queryset) - else: - queryset = queryset.filter(filt) - - return queryset - - @classmethod - def add_to_active_filters(cls, filter): - """ - Add a new clause to the active filters. A filter may be either - a Q object to be applied to the content class or a callable taking - a queryset and spitting out a new one. - """ - if not cls.active_filters: - cls.active_filters = list() - cls.active_filters.append(filter) - - def active(self): - """ - Return only currently active objects. - """ - return self.apply_active_filters(self) # ------------------------------------------------------------------------ -def path_to_cache_key(path): - from django.utils.encoding import iri_to_uri - path = iri_to_uri(path) - - # logic below borrowed from http://richwklein.com/2009/08/04/improving-django-cache-part-ii/ - # via acdha's django-sugar - if len(path) > 250: - m = md5() - m.update(path) - path = m.hexdigest() + '-' + path[:200] - - cache_key = 'PAGE-FOR-URL-%d-%s' % ( django_settings.SITE_ID, path ) - - return cache_key - -class PageManager(models.Manager, ActiveAwareContentManagerMixin): +class BasePageManager(ActiveAwareContentManagerMixin, TreeManager): """ The page manager. Only adds new methods, does not modify standard Django manager behavior in any way. """ # The fields which should be excluded when creating a copy. - exclude_from_copy = ['id', 'tree_id', 'lft', 'rght', 'level', 'redirect_to'] + exclude_from_copy = ["id", "tree_id", "lft", "rght", "level", "redirect_to"] def page_for_path(self, path, raise404=False): """ @@ -118,20 +37,21 @@ def page_for_path(self, path, raise404=False): Page.objects.page_for_path(request.path) """ - stripped = path.strip('/') + stripped = path.strip("/") try: - return self.active().get(_cached_url=stripped and u'/%s/' % stripped or '/') + page = self.active().get(_cached_url="/%s/" % stripped if stripped else "/") + + if not page.are_ancestors_active(): + raise self.model.DoesNotExist("Parents are inactive.") + + return page + except self.model.DoesNotExist: if raise404: - raise Http404 + raise Http404() raise - def page_for_path_or_404(self, path): - warnings.warn('page_for_path_or_404 is deprecated. Use page_for_path instead.', - DeprecationWarning) - return self.page_for_path(path, raise404=True) - def best_match_for_path(self, path, raise404=False): """ Return the best match for a path. If the path as given is unavailable, @@ -144,32 +64,29 @@ def best_match_for_path(self, path, raise404=False): page with url '/photos/album/'. """ - paths = ['/'] - path = path.strip('/') - - # Cache path -> page resolving. - # We flush the cache entry on page saving, so the cache should always - # be up to date. - - if settings.FEINCMS_USE_CACHE: - ck = path_to_cache_key(path) - page = django_cache.get(ck) - if page: - return page + paths = ["/"] + path = path.strip("/") if path: - tokens = path.split('/') - paths += ['/%s/' % '/'.join(tokens[:i]) for i in range(1, len(tokens)+1)] + tokens = path.split("/") + paths += ["/%s/" % "/".join(tokens[:i]) for i in range(1, len(tokens) + 1)] try: - page = self.active().filter(_cached_url__in=paths).extra( - select={'_url_length': 'LENGTH(_cached_url)'}).order_by('-_url_length')[0] - if settings.FEINCMS_USE_CACHE: - django_cache.set(ck, page) + page = ( + self.active() + .filter(_cached_url__in=paths) + .extra(select={"_url_length": "LENGTH(_cached_url)"}) + .order_by("-_url_length")[0] + ) + + if not page.are_ancestors_active(): + raise IndexError("Parents are inactive.") + return page + except IndexError: if raise404: - raise Http404 + raise Http404() raise self.model.DoesNotExist @@ -187,7 +104,7 @@ def toplevel_navigation(self): return self.in_navigation().filter(parent__isnull=True) - def for_request(self, request, raise404=False, best_match=False, setup=True): + def for_request(self, request, raise404=False, best_match=False, path=None): """ Return a page for the request @@ -201,87 +118,85 @@ def for_request(self, request, raise404=False, best_match=False, setup=True): could be determined. """ - if hasattr(request, '_feincms_page'): - page = request._feincms_page - else: + if not hasattr(request, "_feincms_page"): + path = path or request.path_info or request.path + if best_match: - page = self.best_match_for_path(request.path, raise404=raise404) + request._feincms_page = self.best_match_for_path( + path, raise404=raise404 + ) else: - page = self.page_for_path(request.path, raise404=raise404) + request._feincms_page = self.page_for_path(path, raise404=raise404) - if setup: - page.setup_request(request) - return page + return request._feincms_page - def for_request_or_404(self, request): - warnings.warn('for_request_or_404 is deprecated. Use for_request instead.', - DeprecationWarning) - return self.for_request(request, raise404=True) - def best_match_for_request(self, request, raise404=False): - warnings.warn('best_match_for_request is deprecated. Use for_request instead.', - DeprecationWarning) - page = self.best_match_for_path(request.path, raise404=raise404) - page.setup_request(request) - return page - - def from_request(self, request, best_match=False): - warnings.warn('from_request is deprecated. Use for_request instead.', - DeprecationWarning) +# ------------------------------------------------------------------------ +class PageManager(BasePageManager): + pass - if hasattr(request, '_feincms_page'): - return request._feincms_page - if best_match: - return self.best_match_for_request(request, raise404=False) - return self.for_request(request) +PageManager.add_to_active_filters(Q(active=True), key="is_active") -PageManager.add_to_active_filters( Q(active=True) ) -# MARK: - # ------------------------------------------------------------------------ - -try: - # MPTT 0.4 - from mptt.models import MPTTModel - mptt_register = False - Base = create_base_model(MPTTModel) -except ImportError: - # MPTT 0.3 - mptt_register = True - - -class Page(Base): - active = models.BooleanField(_('active'), default=False) +class BasePage(create_base_model(MPTTModel), ContentModelMixin): + active = models.BooleanField(_("active"), default=True) # structure and navigation - title = models.CharField(_('title'), max_length=200, - help_text=_('This is used for the generated navigation too.')) - slug = models.SlugField(_('slug'), max_length=150) - parent = models.ForeignKey('self', verbose_name=_('Parent'), blank=True, null=True, related_name='children') - parent.parent_filter = True # Custom list_filter - see admin/filterspecs.py - in_navigation = models.BooleanField(_('in navigation'), default=False) - override_url = models.CharField(_('override URL'), max_length=300, blank=True, - help_text=_('Override the target URL. Be sure to include slashes at the beginning and at the end if it is a local URL. This affects both the navigation and subpages\' URLs.')) - redirect_to = models.CharField(_('redirect to'), max_length=300, blank=True, - help_text=_('Target URL for automatic redirects.')) - _cached_url = models.CharField(_('Cached URL'), max_length=300, blank=True, - editable=False, default='', db_index=True) - - request_processors = [] - response_processors = [] - cache_key_components = [ lambda p: django_settings.SITE_ID, - lambda p: p._django_content_type.id, - lambda p: p.id ] + title = models.CharField( + _("title"), + max_length=200, + help_text=_("This title is also used for navigation menu items."), + ) + slug = models.SlugField( + _("slug"), + max_length=150, + help_text=_("This is used to build the URL for this page"), + ) + parent = models.ForeignKey( + "self", + verbose_name=_("Parent"), + blank=True, + on_delete=models.CASCADE, + null=True, + related_name="children", + ) + # Custom list_filter - see admin/filterspecs.py + parent.parent_filter = True + in_navigation = models.BooleanField(_("in navigation"), default=False) + override_url = models.CharField( + _("override URL"), + max_length=255, + blank=True, + help_text=_( + "Override the target URL. Be sure to include slashes at the " + "beginning and at the end if it is a local URL. This " + "affects both the navigation and subpages' URLs." + ), + ) + redirect_to = models.CharField( + _("redirect to"), + max_length=255, + blank=True, + help_text=_("Target URL for automatic redirects or the primary key of a page."), + ) + _cached_url = models.CharField( + _("Cached URL"), + max_length=255, + blank=True, + editable=False, + default="", + db_index=True, + ) class Meta: - ordering = ['tree_id', 'lft'] - verbose_name = _('page') - verbose_name_plural = _('pages') + ordering = ["tree_id", "lft"] + abstract = True objects = PageManager() - def __unicode__(self): + def __str__(self): return self.short_title() def is_active(self): @@ -292,9 +207,16 @@ def is_active(self): if not self.pk: return False - pages = Page.objects.active().filter(tree_id=self.tree_id, lft__lte=self.lft, rght__gte=self.rght) + # No need to hit DB if page itself is inactive + if not self.active: + return False + + pages = self.__class__.objects.active().filter( + tree_id=self.tree_id, lft__lte=self.lft, rght__gte=self.rght + ) return pages.count() > self.level - is_active.short_description = _('is active') + + is_active.short_description = _("is active") def are_ancestors_active(self): """ @@ -307,43 +229,21 @@ def are_ancestors_active(self): queryset = PageManager.apply_active_filters(self.get_ancestors()) return queryset.count() >= self.level - def active_children(self): - """ - Returns a queryset describing all active children of the current page. - This is different than page.get_descendants (from mptt) as it will - additionally select only child pages that are active. - """ - warnings.warn('active_children is deprecated. Use self.children.active() instead.', - DeprecationWarning) - return Page.objects.active().filter(parent=self) - - def active_children_in_navigation(self): - """ - Returns a queryset describing all active children that also have the - in_navigation flag set. This might be used eg. in building navigation - menues (only show a disclosure indicator if there actually is something - to disclose). - """ - warnings.warn('active_children_in_navigation is deprecated. Use self.children.in_navigation() instead.', - DeprecationWarning) - return self.active_children().filter(in_navigation=True) - def short_title(self): """ Title shortened for display. """ - from feincms.utils import shorten_string return shorten_string(self.title) - short_title.admin_order_field = 'title' - short_title.short_description = _('title') + + short_title.admin_order_field = "title" + short_title.short_description = _("title") def __init__(self, *args, **kwargs): - super(Page, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Cache a copy of the loaded _cached_url value so we can reliably # determine whether it has been changed in the save handler: self._original_cached_url = self._cached_url - @commit_on_success def save(self, *args, **kwargs): """ Overridden save method which updates the ``_cached_url`` attribute of @@ -357,73 +257,69 @@ def save(self, *args, **kwargs): if self.override_url: self._cached_url = self.override_url elif self.is_root_node(): - self._cached_url = u'/%s/' % self.slug + self._cached_url = "/%s/" % self.slug else: - self._cached_url = u'%s%s/' % (self.parent._cached_url, self.slug) + self._cached_url = f"{self.parent._cached_url}{self.slug}/" cached_page_urls[self.id] = self._cached_url - super(Page, self).save(*args, **kwargs) - - # Okay, we changed the URL -- remove the old stale entry from the cache - if settings.FEINCMS_USE_CACHE: - ck = path_to_cache_key( self._original_cached_url.strip('/') ) - django_cache.delete(ck) - - # If our cached URL changed we need to update all descendants to - # reflect the changes. Since this is a very expensive operation - # on large sites we'll check whether our _cached_url actually changed - # or if the updates weren't navigation related: - if self._cached_url == self._original_cached_url: - return - - # TODO: Does not find everything it should when ContentProxy content - # inheritance has been customized. - pages = self.get_descendants().order_by('lft') - - for page in pages: - if page.override_url: - page._cached_url = page.override_url - else: - # cannot be root node by definition - page._cached_url = u'%s%s/' % ( - cached_page_urls[page.parent_id], - page.slug) - cached_page_urls[page.id] = page._cached_url - super(Page, page).save() # do not recurse + with transaction.atomic(): + super().save(*args, **kwargs) + + # If our cached URL changed we need to update all descendants to + # reflect the changes. Since this is a very expensive operation + # on large sites we'll check whether our _cached_url actually changed + # or if the updates weren't navigation related: + if self._cached_url != self._original_cached_url: + pages = self.get_descendants().order_by("lft") + + for page in pages: + if page.override_url: + page._cached_url = page.override_url + else: + # cannot be root node by definition + page._cached_url = "{}{}/".format( + cached_page_urls[page.parent_id], + page.slug, + ) + + cached_page_urls[page.id] = page._cached_url + super(BasePage, page).save() # do not recurse + + save.alters_data = True + + def delete(self, *args, **kwargs): + if not settings.FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED: + if self.template.singleton: + raise PermissionDenied( + _( + "This %(page_class)s uses a singleton template, and " + "FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" + % {"page_class": self._meta.verbose_name} + ) + ) + super().delete(*args, **kwargs) + + delete.alters_data = True - @models.permalink def get_absolute_url(self): """ Return the absolute URL of this page. """ - - url = self._cached_url[1:-1] + # result url never begins or ends with a slash + url = self._cached_url.strip("/") if url: - return ('feincms_handler', (url,), {}) - return ('feincms_home', (), {}) + return reverse("feincms_handler", args=(url,)) + return reverse("feincms_home") def get_navigation_url(self): """ Return either ``redirect_to`` if it is set, or the URL of this page. """ - return self.redirect_to or self._cached_url - - def get_siblings_and_self(page): - """ - As the name says. - """ - warnings.warn('get_siblings_and_self is deprecated. You probably want self.parent.children.active() anyway.', - DeprecationWarning) - return page.get_siblings(include_self=True) - - def cache_key(self): - """ - Return a string that may be used as cache key for the current page. - The cache_key is unique for each content type and content instance. - """ - return '-'.join(unicode(x(self)) for x in self.cache_key_components) + if self.redirect_to: + return self.get_redirect_to_target() + return self._cached_url def etag(self, request): """ @@ -444,453 +340,56 @@ def last_modified(self, request): """ return None - def setup_request(self, request): + def get_redirect_to_page(self): """ - Before rendering a page, run all registered request processors. A request - processor may peruse and modify the page or the request. It can also return - a HttpResponse for shortcutting the page rendering and returning that response - immediately to the client. + This might be overriden/extended by extension modules. """ - if hasattr(request, '_feincms_page'): - return - - request._feincms_page = self - - if not hasattr(request, '_feincms_extra_context'): - request._feincms_extra_context = {} - - request._feincms_extra_context.update({ - 'in_appcontent_subpage': False, # XXX This variable name isn't accurate anymore. - # We _are_ in a subpage, but it isn't necessarily - # an appcontent subpage. - 'extra_path': '/', - }) - - if request.path != self.get_absolute_url(): - request._feincms_extra_context.update({ - 'in_appcontent_subpage': True, - 'extra_path': re.sub('^' + re.escape(self.get_absolute_url()[:-1]), '', - request.path), - }) + if not self.redirect_to: + return None - for fn in self.request_processors: - r = fn(self, request) - if r: return r + # It might be an identifier for a different object + whereto = match_model_string(self.redirect_to) + if not whereto: + return None - def finalize_response(self, request, response): - """ - After rendering a page to a response, the registered response processors are - called to modify the response, eg. for setting cache or expiration headers, - keeping statistics, etc. - """ - for fn in self.response_processors: - fn(self, request, response) - - def require_path_active_request_processor(self, request): - """ - Checks whether any ancestors are actually inaccessible (ie. not - inactive or expired) and raise a 404 if so. - """ - if not self.are_ancestors_active(): - raise Http404() + return get_model_instance(*whereto) - def get_redirect_to_target(self, request): + def get_redirect_to_target(self, request=None): """ This might be overriden/extended by extension modules. """ - return self.redirect_to - - def redirect_request_processor(self, request): - target = self.get_redirect_to_target(request) - if target: - return HttpResponseRedirect(target) - - def frontendediting_request_processor(self, request): - if not 'frontend_editing' in request.GET: - return - - if request.user.has_module_perms('page'): - try: - enable_fe = int(request.GET['frontend_editing']) > 0 - except ValueError: - enable_fe = False - - request.session['frontend_editing'] = enable_fe - - # Redirect to cleanup URLs - return HttpResponseRedirect(request.path) - - def etag_request_processor(self, request): - - # XXX is this a performance concern? Does it create a new class - # every time the processor is called or is this optimized to a static - # class?? - class DummyResponse(dict): - """ - This is a dummy class with enough behaviour of HttpResponse so we - can use the condition decorator without too much pain. - """ - def has_header(self, what): - return False - - def dummy_response_handler(*args, **kwargs): - return DummyResponse() - - def etagger(request, page, *args, **kwargs): - etag = page.etag(request) - return etag - - def lastmodifier(request, page, *args, **kwargs): - lm = page.last_modified() - return lm - - # Unavailable in Django 1.0 -- the current implementation of ETag support - # requires Django 1.1 unfortunately. - from django.views.decorators.http import condition - - # Now wrap the condition decorator around our dummy handler: - # the net effect is that we will be getting a DummyResponse from - # the handler if processing is to continue and a non-DummyResponse - # (should be a "304 not modified") if the etag matches. - rsp = condition(etag_func=etagger, last_modified_func=lastmodifier)(dummy_response_handler)(request, self) - - # If dummy then don't do anything, if a real response, return and - # thus shortcut the request processing. - if not isinstance(rsp, DummyResponse): - return rsp - - def etag_response_processor(self, request, response): - """ - Response processor to set an etag header on outgoing responses. - The Page.etag() method must return something valid as etag content - whenever you want an etag header generated. - """ - etag = self.etag(request) - if etag is not None: - response['ETag'] = '"' + etag + '"' - - @staticmethod - def debug_sql_queries_response_processor(verbose=False, file=sys.stderr): - if not django_settings.DEBUG: - return lambda self, request, response: None - - def processor(self, request, response): - from django.db import connection - - print_sql = lambda x: x - try: - import sqlparse - print_sql = lambda x: sqlparse.format(x, reindent=True, keyword_case='upper') - except: - pass - - if verbose: - print >> file, "--------------------------------------------------------------" - time = 0.0 - i = 0 - for q in connection.queries: - i += 1 - if verbose: - print >> file, "%d : [%s]\n%s\n" % ( i, q['time'], print_sql(q['sql'])) - time += float(q['time']) - - print >> file, "--------------------------------------------------------------" - print >> file, "Total: %d queries, %.3f ms" % (i, time) - print >> file, "--------------------------------------------------------------" - - return processor - @classmethod - def register_request_processors(cls, *processors): - """ - Registers all passed callables as request processors. A request processor - always receives two arguments, the current page object and the request. - """ + target_page = self.get_redirect_to_page() + if target_page is None: + return self.redirect_to - cls.request_processors[0:0] = processors + return target_page.get_absolute_url() @classmethod - def register_response_processors(cls, *processors): + def register_default_processors(cls): """ - Registers all passed callables as response processors. A response processor - always receives three arguments, the current page object, the request - and the response. + Register our default request processors for the out-of-the-box + Page experience. """ - - cls.response_processors.extend(processors) - - @classmethod - def register_extension(cls, register_fn): - register_fn(cls, PageAdmin) + cls.register_request_processor( + processors.redirect_request_processor, key="redirect" + ) + cls.register_request_processor( + processors.extra_context_request_processor, key="extra_context" + ) # ------------------------------------------------------------------------ -if mptt_register: # MPTT 0.3 legacy support - mptt.register(Page) - -# Our default request processors -Page.register_request_processors(Page.require_path_active_request_processor, - Page.frontendediting_request_processor, - Page.redirect_request_processor) - -signals.post_syncdb.connect(check_database_schema(Page, __name__), weak=False) - -# MARK: - -# ------------------------------------------------------------------------ -class PageAdminForm(forms.ModelForm): - never_copy_fields = ('title', 'slug', 'parent', 'active', 'override_url', - 'translation_of', '_content_title', '_page_title') - - def __init__(self, *args, **kwargs): - ensure_completely_loaded() - - if 'initial' in kwargs: - if 'parent' in kwargs['initial']: - # Prefill a few form values from the parent page - try: - page = Page.objects.get(pk=kwargs['initial']['parent']) - data = model_to_dict(page) - - for field in PageManager.exclude_from_copy: - if field in data: - del data[field] - - # These are always excluded from prefilling - for field in self.never_copy_fields: - if field in data: - del data[field] - - kwargs['initial'].update(data) - except Page.DoesNotExist: - pass - - elif 'translation_of' in kwargs['initial']: - # Only if translation extension is active - try: - page = Page.objects.get(pk=kwargs['initial']['translation_of']) - original = page.original_translation - - data = { - 'translation_of': original.id, - 'template_key': original.template_key, - 'active': original.active, - 'in_navigation': original.in_navigation, - } - - if original.parent: - try: - data['parent'] = original.parent.get_translation(kwargs['initial']['language']).id - except Page.DoesNotExist: - # ignore this -- the translation does not exist - pass - - kwargs['initial'].update(data) - except Page.DoesNotExist: - pass - - super(PageAdminForm, self).__init__(*args, **kwargs) - if 'instance' in kwargs: - choices = [] - for key, template in kwargs['instance'].TEMPLATE_CHOICES: - template = kwargs['instance']._feincms_templates[key] - if template.preview_image: - choices.append((template.key, - mark_safe(u'<img src="%s" alt="%s" /> %s' % ( - template.preview_image, template.key, template.title)))) - else: - choices.append((template.key, template.title)) - - self.fields['template_key'].choices = choices - - def clean(self): - cleaned_data = super(PageAdminForm, self).clean() - - # No need to think further, let the user correct errors first - if self._errors: - return cleaned_data - - current_id = None - # See the comment below on why we do not use Page.objects.active(), - # at least for now. - active_pages = Page.objects.filter(active=True) - - if self.instance: - current_id = self.instance.id - active_pages = active_pages.exclude(id=current_id) - - if hasattr(Site, 'page_set') and 'site' in cleaned_data: - active_pages = active_pages.filter(site=cleaned_data['site']) - - if not cleaned_data['active']: - # If the current item is inactive, we do not need to conduct - # further validation. Note that we only check for the flag, not - # for any other active filters. This is because we do not want - # to inspect the active filters to determine whether two pages - # really won't be active at the same time. - return cleaned_data - - if cleaned_data['override_url']: - if active_pages.filter(_cached_url=cleaned_data['override_url']).count(): - self._errors['override_url'] = ErrorList([_('This URL is already taken by an active page.')]) - del cleaned_data['override_url'] - - return cleaned_data - - if current_id: - # We are editing an existing page - parent = Page.objects.get(pk=current_id).parent - else: - # The user tries to create a new page - parent = cleaned_data['parent'] - - if parent: - new_url = '%s%s/' % (parent._cached_url, cleaned_data['slug']) - else: - new_url = '/%s/' % cleaned_data['slug'] - - if active_pages.filter(_cached_url=new_url).count(): - self._errors['active'] = ErrorList([_('This URL is already taken by another active page.')]) - del cleaned_data['active'] - - return cleaned_data +class Page(BasePage): + class Meta: + ordering = ["tree_id", "lft"] + verbose_name = _("page") + verbose_name_plural = _("pages") + app_label = "page" + # not yet # permissions = (("edit_page", _("Can edit page metadata")),) -# ------------------------------------------------------------------------ -class PageAdmin(editor.ItemEditor, editor.TreeEditor): - class Media: - css = {} - js = [] - - form = PageAdminForm - - # the fieldsets config here is used for the add_view, it has no effect - # for the change_view which is completely customized anyway - unknown_fields = ['override_url', 'redirect_to'] - fieldsets = [ - (None, { - 'fields': ['active', 'in_navigation', 'template_key', 'title', 'slug', - 'parent'], - }), - item_editor.FEINCMS_CONTENT_FIELDSET, - (_('Other options'), { - 'classes': ['collapse',], - 'fields': unknown_fields, - }), - ] - readonly_fields = [] - list_display = ['short_title', 'is_visible_admin', 'in_navigation_toggle', 'template'] - list_filter = ['active', 'in_navigation', 'template_key', 'parent'] - search_fields = ['title', 'slug'] - prepopulated_fields = { 'slug': ('title',), } - - raw_id_fields = ['parent'] - radio_fields = {'template_key': admin.HORIZONTAL} - def __init__(self, *args, **kwargs): - ensure_completely_loaded() - - if len(Page._feincms_templates) > 4: - del(self.radio_fields['template_key']) - - super(PageAdmin, self).__init__(*args, **kwargs) - - # The use of fieldsets makes only fields explicitly listed in there - # actually appear in the admin form. However, extensions should not be - # aware that there is a fieldsets structure and even less modify it; - # we therefore enumerate all of the model's field and forcibly add them - # to the last section in the admin. That way, nobody is left behind. - from django.contrib.admin.util import flatten_fieldsets - present_fields = flatten_fieldsets(self.fieldsets) - - for f in self.model._meta.fields: - if not f.name.startswith('_') and not f.name in ('id', 'lft', 'rght', 'tree_id', 'level') and \ - not f.auto_created and not f.name in present_fields and f.editable: - self.unknown_fields.append(f.name) - if not f.editable: - self.readonly_fields.append(f.name) - - in_navigation_toggle = editor.ajax_editable_boolean('in_navigation', _('in navigation')) - - def _actions_column(self, page): - editable = getattr(page, 'feincms_editable', True) - - preview_url = "../../r/%s/%s/" % ( - ContentType.objects.get_for_model(self.model).id, - page.id) - actions = super(PageAdmin, self)._actions_column(page) - if editable: - actions.insert(0, u'<a href="add/?parent=%s" title="%s"><img src="%sicon_addlink.gif" alt="%s"></a>' % ( - page.pk, _('Add child page'), settings._HACK_ADMIN_MEDIA_IMAGES ,_('Add child page'))) - actions.insert(0, u'<a href="%s" title="%s"><img src="%sselector-search.gif" alt="%s" /></a>' % ( - preview_url, _('View on site'), settings._HACK_ADMIN_MEDIA_IMAGES, _('View on site'))) - - return actions - - def add_view(self, request, form_url='', extra_context=None): - # Preserve GET parameters - return super(PageAdmin, self).add_view( - request=request, - form_url=request.get_full_path(), - extra_context=extra_context) - - def response_add(self, request, obj, *args, **kwargs): - response = super(PageAdmin, self).response_add(request, obj, *args, **kwargs) - if 'parent' in request.GET and '_addanother' in request.POST and response.status_code in (301, 302): - # Preserve GET parameters if we are about to add another page - response['Location'] += '?parent=%s' % request.GET['parent'] - if 'translation_of' in request.GET: - # Copy all contents - try: - original = self.model._tree_manager.get(pk=request.GET.get('translation_of')) - original = original.original_translation - obj.copy_content_from(original) - obj.save() - except self.model.DoesNotExist: - pass - - return response - - def _refresh_changelist_caches(self, *args, **kwargs): - self._visible_pages = list(self.model.objects.active().values_list('id', flat=True)) - - def change_view(self, request, object_id, extra_context=None): - try: - return super(PageAdmin, self).change_view(request, object_id, extra_context) - except PermissionDenied: - from django.contrib import messages - messages.add_message(request, messages.ERROR, _("You don't have the necessary permissions to edit this object")) - return HttpResponseRedirect(reverse('admin:page_page_changelist')) - - def is_visible_admin(self, page): - """ - Instead of just showing an on/off boolean, also indicate whether this - page is not visible because of publishing dates or inherited status. - """ - if not hasattr(self, "_visible_pages"): - self._visible_pages = list() # Sanity check in case this is not already defined - - if page.parent_id and not page.parent_id in self._visible_pages: - # parent page's invisibility is inherited - if page.id in self._visible_pages: - self._visible_pages.remove(page.id) - return editor.ajax_editable_boolean_cell(page, 'active', override=False, text=_('inherited')) - - if page.active and not page.id in self._visible_pages: - # is active but should not be shown, so visibility limited by extension: show a "not active" - return editor.ajax_editable_boolean_cell(page, 'active', override=False, text=_('extensions')) - - return editor.ajax_editable_boolean_cell(page, 'active') - is_visible_admin.allow_tags = True - is_visible_admin.short_description = _('is active') - is_visible_admin.editable_boolean_field = 'active' - - # active toggle needs more sophisticated result function - def is_visible_recursive(self, page): - retval = [] - for c in page.get_descendants(include_self=True): - retval.append(self.is_visible_admin(c)) - return retval - is_visible_admin.editable_boolean_result = is_visible_recursive +Page.register_default_processors() # ------------------------------------------------------------------------ -# ------------------------------------------------------------------------ diff --git a/feincms/module/page/processors.py b/feincms/module/page/processors.py new file mode 100644 index 000000000..6b8016b77 --- /dev/null +++ b/feincms/module/page/processors.py @@ -0,0 +1,156 @@ +import logging +import re +import sys + +from django.conf import settings as django_settings +from django.http import Http404, HttpResponseRedirect +from django.views.decorators.http import condition + + +logger = logging.getLogger(__name__) + + +def redirect_request_processor(page, request): + """ + Returns a ``HttpResponseRedirect`` instance if the current page says + a redirect should happen. + """ + target = page.get_redirect_to_target(request) + if target: + extra_path = request._feincms_extra_context.get("extra_path", "/") + if extra_path == "/": + return HttpResponseRedirect(target) + logger.debug( + "Page redirect on '%s' not taken because extra path '%s' present", + page.get_absolute_url(), + extra_path, + ) + raise Http404() + + +def extra_context_request_processor(page, request): + """ + Fills ``request._feincms_extra_context`` with a few useful variables. + """ + request._feincms_extra_context.update( + { + # XXX This variable name isn't accurate anymore. + "in_appcontent_subpage": False, + "extra_path": "/", + } + ) + + url = page.get_absolute_url() + if request.path != url: + request._feincms_extra_context.update( + { + "in_appcontent_subpage": True, + "extra_path": re.sub( + "^" + re.escape(url.rstrip("/")), "", request.path + ), + } + ) + + +class __DummyResponse(dict): + """ + This is a dummy class with enough behaviour of HttpResponse so we + can use the condition decorator without too much pain. + """ + + @property + def headers(self): + return self + + def has_header(self, what): + return False + + +def etag_request_processor(page, request): + """ + Short-circuits the request-response cycle if the ETag matches. + """ + + def dummy_response_handler(*args, **kwargs): + return __DummyResponse() + + def etagger(request, page, *args, **kwargs): + etag = page.etag(request) + return etag + + def lastmodifier(request, page, *args, **kwargs): + lm = page.last_modified() + return lm + + # Now wrap the condition decorator around our dummy handler: + # the net effect is that we will be getting a DummyResponse from + # the handler if processing is to continue and a non-DummyResponse + # (should be a "304 not modified") if the etag matches. + rsp = condition(etag_func=etagger, last_modified_func=lastmodifier)( + dummy_response_handler + )(request, page) + + # If dummy then don't do anything, if a real response, return and + # thus shortcut the request processing. + if not isinstance(rsp, __DummyResponse): + return rsp + + +def etag_response_processor(page, request, response): + """ + Response processor to set an etag header on outgoing responses. + The Page.etag() method must return something valid as etag content + whenever you want an etag header generated. + """ + etag = page.etag(request) + if etag is not None: + response["ETag"] = '"' + etag + '"' + + +def debug_sql_queries_response_processor(verbose=False, file=sys.stderr): + """ + Attaches a handler which prints the query count (and optionally all + individual queries which have been executed) on the console. Does nothing + if ``DEBUG = False``. + + Example:: + + from feincms.module.page import models, processors + models.Page.register_response_processor( + processors.debug_sql_queries_response_processor(verbose=True), + ) + """ + if not django_settings.DEBUG: + return lambda page, request, response: None + + def processor(page, request, response): + from django.db import connection + + try: + import sqlparse + + def print_sql(x): + return sqlparse.format(x, reindent=True, keyword_case="upper") + + except Exception: + + def print_sql(x): + return x + + if verbose: + print("-" * 60, file=file) + time = 0.0 + i = 0 + for q in connection.queries: + i += 1 + if verbose: + print( + "%d : [%s]\n%s\n" % (i, q["time"], print_sql(q["sql"])), file=file + ) + time += float(q["time"]) + + print("-" * 60, file=file) + print("Total: %d queries, %.3f ms" % (i, time), file=file) + print("-" * 60, file=file) + + return processor diff --git a/feincms/module/page/sitemap.py b/feincms/module/page/sitemap.py index ef640bf95..babe80f4b 100644 --- a/feincms/module/page/sitemap.py +++ b/feincms/module/page/sitemap.py @@ -1,11 +1,13 @@ # ------------------------------------------------------------------------ -# coding=utf-8 # ------------------------------------------------------------------------ -from django.db.models import Max + +from django.apps import apps from django.contrib.sitemaps import Sitemap +from django.db.models import Max + +from feincms import settings -from feincms.module.page.models import Page # ------------------------------------------------------------------------ class PageSitemap(Sitemap): @@ -13,31 +15,48 @@ class PageSitemap(Sitemap): The PageSitemap can be used to automatically generate sitemap.xml files for submission to index engines. See http://www.sitemaps.org/ for details. """ - def __init__(self, navigation_only=False, max_depth=0, changefreq=None, queryset=None, filter=None, *args, **kwargs): + + def __init__( + self, + navigation_only=False, + max_depth=0, + changefreq=None, + queryset=None, + filter=None, + extended_navigation=False, + page_model=settings.FEINCMS_DEFAULT_PAGE_MODEL, + *args, + **kwargs, + ): """ The PageSitemap accepts the following parameters for customisation of the resulting sitemap.xml output: - * navigation_only -- if set to True, only pages that are in_navigation + * navigation_only -- if set to True, only pages that are in_navigation will appear in the site map. * max_depth -- if set to a non-negative integer, will limit the sitemap generated to this page hierarchy depth. - * changefreq -- should be a string or callable specifiying the page + * changefreq -- should be a string or callable specifying the page update frequency, according to the sitemap protocol. * queryset -- pass in a query set to restrict the Pages to include in the site map. * filter -- pass in a callable that transforms a queryset to filter out the pages you want to include in the site map. + * extended_navigation -- if set to True, adds pages from any navigation + extensions. If using PagePretender, make sure to include title, url, + level, in_navigation and optionally modification_date. """ - super(PageSitemap, self).__init__(*args, **kwargs) - self.depth_cutoff = max_depth + super().__init__(*args, **kwargs) + self.depth_cutoff = max_depth self.navigation_only = navigation_only - self.changefreq = changefreq - self.filter = filter + self.changefreq = changefreq + self.filter = filter + self.extended_navigation = extended_navigation if queryset is not None: - self.queryset = queryset + self.queryset = queryset else: - self.queryset = Page.objects.active() + Page = apps.get_model(*page_model.split(".")) + self.queryset = Page.objects.active() def items(self): """ @@ -48,24 +67,43 @@ def items(self): if callable(base_qs): base_qs = base_qs() - self.max_depth = base_qs.aggregate(Max('level'))['level__max'] + self.max_depth = base_qs.aggregate(Max("level"))["level__max"] or 0 if self.depth_cutoff > 0: self.max_depth = min(self.depth_cutoff, self.max_depth) - self.per_level = 1.0 / (self.max_depth + 1.0) - qs = base_qs.filter(redirect_to="") if self.filter: qs = self.filter(qs) if self.navigation_only: qs = qs.filter(in_navigation=True) if self.depth_cutoff > 0: - qs = qs.filter(level__lte=self.max_depth-1) + qs = qs.filter(level__lte=self.max_depth - 1) + + pages = [p for p in qs if p.is_active()] + + if self.extended_navigation: + for idx, page in enumerate(pages): + if self.depth_cutoff > 0 and page.level == self.max_depth: + continue + if getattr(page, "navigation_extension", None): + cnt = 0 + for p in page.extended_navigation(): + depth_too_deep = ( + self.depth_cutoff > 0 and p.level > self.depth_cutoff + ) + not_in_nav = self.navigation_only and not p.in_navigation + if depth_too_deep or not_in_nav: + continue + cnt += 1 + pages.insert(idx + cnt, p) + if p.level > self.max_depth: + self.max_depth = p.level - return [ p for p in qs if p.is_active() ] + self.per_level = 1.0 / (self.max_depth + 1.0) + return pages def lastmod(self, obj): - return getattr(obj, 'modification_date', None) + return getattr(obj, "modification_date", None) # the priority is computed of the depth in the tree of a page # may we should make an extension to give control to the user for priority @@ -75,7 +113,10 @@ def priority(self, obj): the site. Top level get highest priority, then each level is decreased by per_level. """ - prio = 1.0 - (obj.level + 1) * self.per_level + if getattr(obj, "override_url", "") == "/": + prio = 1.0 + else: + prio = 1.0 - (obj.level + 1) * self.per_level # If the page is in_navigation, then it's more important, so boost # its importance @@ -85,12 +126,4 @@ def priority(self, obj): return "%0.2g" % min(1.0, prio) - # After a call to the sitemap, be sure to erase the cached _paginator - # attribute, so next time we'll re-fetch the items list instead of using - # a stale list. - def get_urls(self, *args, **kwargs): - urls = super(PageSitemap, self).get_urls(*args, **kwargs) - del(self._paginator) - return urls - # ------------------------------------------------------------------------ diff --git a/feincms/module/page/templatetags/__init__.py b/feincms/module/page/templatetags/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/feincms/module/page/templatetags/feincms_page_tags.py b/feincms/module/page/templatetags/feincms_page_tags.py deleted file mode 100644 index 825295394..000000000 --- a/feincms/module/page/templatetags/feincms_page_tags.py +++ /dev/null @@ -1,340 +0,0 @@ -# ------------------------------------------------------------------------ -# coding=utf-8 -# ------------------------------------------------------------------------ - -from django import template -from django.conf import settings -from django.db.models import Q -from django.http import HttpRequest - -from feincms.module.page.models import Page, PageManager -from feincms.utils.templatetags import * -from feincms.utils.templatetags import _parse_args - -register = template.Library() - - -# ------------------------------------------------------------------------ -# ------------------------------------------------------------------------ -class NavigationNode(SimpleAssignmentNodeWithVarAndArgs): - """ - Return a list of pages to be used for the navigation - - level: 1 = toplevel, 2 = sublevel, 3 = sub-sublevel - depth: 1 = only one level, 2 = subpages too - extended: run navigation extension on returned pages, not only on top-level node - - If you set depth to something else than 1, you might want to look into - the tree_info template tag from the mptt_tags library. - - Example:: - - {% feincms_navigation of feincms_page as sublevel level=2,depth=1 %} - {% for p in sublevel %} - <a href="{{ p.get_absolute_url }}">{{ p.title }}</a> - {% endfor %} - """ - - def what(self, instance, args): - level = int(args.get('level', 1)) - depth = int(args.get('depth', 1)) - mptt_limit = level + depth - 1 # adjust limit to mptt level indexing - - if isinstance(instance, HttpRequest): - instance = Page.objects.for_request(instance) - - entries = self._what(instance, level, depth) - - if args.get('extended', False): - _entries = list(entries) - entries = [] - - for entry in _entries: - entries.append(entry) - - if getattr(entry, 'navigation_extension', None): - entries.extend(e for e in entry.extended_navigation(depth=depth, - request=self.render_context.get('request', None)) - if getattr(e, 'level', 0) < mptt_limit) - - return entries - - def _in_navigation_depth(self, level, depth): - q = Q(level__lt=level + depth) - for i in range(depth): - q &= Q(level__lt=level + i) | Q(**{ - 'parent__' * i + 'in_navigation': True, - 'level__gte': level + i, - }) - return q - - def _what(self, instance, level, depth): - if level <= 1: - if depth == 1: - return Page.objects.toplevel_navigation() - else: - return Page.objects.active().filter( - self._in_navigation_depth(0, depth)) - - # mptt starts counting at 0, NavigationNode at 1; if we need the submenu - # of the current page, we have to add 2 to the mptt level - if instance.level + 2 == level: - pass - elif instance.level + 2 < level: - try: - queryset = instance.get_descendants().filter(level=level - 2, in_navigation=True) - instance = PageManager.apply_active_filters(queryset)[0] - except IndexError: - return [] - else: - instance = instance.get_ancestors()[level - 2] - - # special case for the navigation extension - if getattr(instance, 'navigation_extension', None): - return instance.extended_navigation(depth=depth, - request=self.render_context.get('request', None)) - else: - if depth == 1: - return instance.children.in_navigation() - else: - queryset = instance.get_descendants().filter( - self._in_navigation_depth(level - 1, depth)) - return PageManager.apply_active_filters(queryset) -register.tag('feincms_navigation', do_simple_assignment_node_with_var_and_args_helper(NavigationNode)) - -# ------------------------------------------------------------------------ -class ExtendedNavigationNode(NavigationNode): - def render(self, context): - self.render_context = context - try: - instance = self.in_var.resolve(context) - except template.VariableDoesNotExist: - context[self.var_name] = [] - return '' - - context[self.var_name] = self.what(instance, _parse_args(self.args, context)) - - return '' -register.tag('feincms_navigation_extended', do_simple_assignment_node_with_var_and_args_helper(ExtendedNavigationNode)) - -# ------------------------------------------------------------------------ -class ParentLinkNode(SimpleNodeWithVarAndArgs): - """ - {% feincms_parentlink of feincms_page level=1 %} - """ - - def what(self, page, args): - level = int(args.get('level', 1)) - - if page.level + 1 == level: - return page.get_absolute_url() - elif page.level + 1 < level: - return '#' - - try: - return page.get_ancestors()[level - 1].get_absolute_url() - except IndexError: - return '#' -register.tag('feincms_parentlink', do_simple_node_with_var_and_args_helper(ParentLinkNode)) - -# ------------------------------------------------------------------------ -class LanguageLinksNode(SimpleAssignmentNodeWithVarAndArgs): - """ - :: - - {% feincms_languagelinks for feincms_page as links [args] %} - - This template tag needs the translations extension. - - Arguments can be any combination of: - - * all or existing: Return all languages or only those where a translation exists - * excludecurrent: Excludes the item in the current language from the list - * request=request: The current request object, only needed if you are using - AppContents and need to append the "extra path" - - The default behavior is to return an entry for all languages including the - current language. - - Example:: - - {% feincms_languagelinks for entry as links all,excludecurrent %} - {% for key, name, link in links %} - <a href="{% if link %}{{ link }}{% else %}/{{ key }}/{% endif %}">{% trans name %}</a> - {% endfor %} - """ - - def what(self, page, args): - only_existing = args.get('existing', False) - exclude_current = args.get('excludecurrent', False) - - # Preserve the trailing path when switching languages if extra_path - # exists (this is mostly the case when we are working inside an - # ApplicationContent-managed page subtree) - trailing_path = u'' - request = args.get('request', None) - if request: - # Trailing path without first slash - trailing_path = request._feincms_extra_context.get('extra_path', '')[1:] - - translations = dict((t.language, t) for t in page.available_translations()) - translations[page.language] = page - - links = [] - for key, name in settings.LANGUAGES: - if exclude_current and key == page.language: - continue - - # hardcoded paths... bleh - if key in translations: - links.append((key, name, translations[key].get_absolute_url()+trailing_path)) - elif not only_existing: - links.append((key, name, None)) - - return links -register.tag('feincms_languagelinks', do_simple_assignment_node_with_var_and_args_helper(LanguageLinksNode)) - -# ------------------------------------------------------------------------ -# ------------------------------------------------------------------------ -def _translate_page_into(page, language, default=None): - """ - Return the translation for a given page - """ - # Optimisation shortcut: No need to dive into translations if page already what we want - if page.language == language: - return page - - if language is not None: - translations = dict((t.language, t) for t in page.available_translations()) - if language in translations: - return translations[language] - - if hasattr(default, '__call__'): - return default(page=page) - return default - -# ------------------------------------------------------------------------ -class TranslatedPageNode(SimpleAssignmentNodeWithVarAndArgs): - """ - :: - - {% feincms_translatedpage for feincms_page as feincms_transpage language=en %} - {% feincms_translatedpage for feincms_page as originalpage %} - {% feincms_translatedpage for some_page as translatedpage language=feincms_page.language %} - - This template tag needs the translations extension. - - Returns the requested translation of the page if it exists. If the language - argument is omitted the primary language will be returned (the first language - specified in settings.LANGUAGES). - - Note: To distinguish between a bare language code and a variable we check whether - settings LANGUAGES contains that code -- so naming a variable "en" will probably - not do what is intended. - """ - def what(self, page, args, default=None): - language = args.get('language', None) - - if language is None: - language = settings.LANGUAGES[0][0] - else: - if language not in (x[0] for x in settings.LANGUAGES): - try: - language = template.Variable(language).resolve(self.render_context) - except template.VariableDoesNotExist: - language = settings.LANGUAGES[0][0] - - return _translate_page_into(page, language, default=default) -register.tag('feincms_translatedpage', do_simple_assignment_node_with_var_and_args_helper(TranslatedPageNode)) - -# ------------------------------------------------------------------------ -class TranslatedPageNodeOrBase(TranslatedPageNode): - def what(self, page, args): - return super(TranslatedPageNodeOrBase, self).what(page, args, default=page.get_original_translation) -register.tag('feincms_translatedpage_or_base', do_simple_assignment_node_with_var_and_args_helper(TranslatedPageNodeOrBase)) - -# ------------------------------------------------------------------------ -@register.filter -def feincms_translated_or_base(pages, language=None): - if not hasattr(pages, '__iter__'): - pages = [ pages ] - for page in pages: - yield _translate_page_into(page, language, default=page.get_original_translation) - -# ------------------------------------------------------------------------ -@register.inclusion_tag("breadcrumbs.html") -def feincms_breadcrumbs(page, include_self=True): - """ - Generate a list of the page's ancestors suitable for use as breadcrumb navigation. - - By default, generates an unordered list with the id "breadcrumbs" - - override breadcrumbs.html to change this. - - :: - - {% feincms_breadcrumbs feincms_page %} - """ - - if not page or not isinstance(page, Page): - raise ValueError("feincms_breadcrumbs must be called with a valid Page object") - - ancs = page.get_ancestors() - - bc = [(anc.get_absolute_url(), anc.short_title()) for anc in ancs] - - if include_self: - bc.append((None, page.short_title())) - - return {"trail": bc} - -# ------------------------------------------------------------------------ -@register.filter -def is_parent_of(page1, page2): - """ - Determines whether a given page is the parent of another page - - Example:: - - {% if page|is_parent_of:feincms_page %} ... {% endif %} - """ - - try: - return page1.tree_id == page2.tree_id and page1.lft < page2.lft and page1.rght > page2.rght - except AttributeError: - return False - -# ------------------------------------------------------------------------ -@register.filter -def is_equal_or_parent_of(page1, page2): - """ - Determines whether a given page is equal to or the parent of another - page. This is especially handy when generating the navigation. The following - example adds a CSS class ``current`` to the current main navigation entry:: - - {% for page in navigation %} - <a {% if page|is_equal_or_parent_of:feincms_page %}class="mark"{% endif %}> - {{ page.title }}</a> - {% endfor %} - """ - try: - return page1.tree_id == page2.tree_id and page1.lft <= page2.lft and page1.rght >= page2.rght - except AttributeError: - return False - -# ------------------------------------------------------------------------ -@register.filter -def is_sibling_of(page1, page2): - """ - Determines whether a given page is a sibling of another page - - :: - - {% if page|is_sibling_of:feincms_page %} ... {% endif %} - """ - - try: - return page1.parent_id == page2.parent_id - except AttributeError: - return False - -# ------------------------------------------------------------------------ diff --git a/feincms/shortcuts.py b/feincms/shortcuts.py index be63353ac..1d2afb882 100644 --- a/feincms/shortcuts.py +++ b/feincms/shortcuts.py @@ -1,5 +1,4 @@ -from django.shortcuts import render_to_response -from django.template import RequestContext +from django.shortcuts import render from feincms.module.page.models import Page @@ -10,7 +9,6 @@ def render_to_response_best_match(request, template_name, dictionary=None): """ dictionary = dictionary or {} - dictionary['feincms_page'] = Page.objects.best_match_for_request(request) + dictionary["feincms_page"] = Page.objects.best_match_for_request(request) - return render_to_response(template_name, dictionary, - context_instance=RequestContext(request)) + return render(request, template_name, dictionary) diff --git a/feincms/signals.py b/feincms/signals.py index ce0e8d399..f75fec0b6 100644 --- a/feincms/signals.py +++ b/feincms/signals.py @@ -1,18 +1,19 @@ # ------------------------------------------------------------------------ -# coding=utf-8 # ------------------------------------------------------------------------ # -# Created by Martin J. Laubach on 2011-08-01 +# Created by Martin J. Laubach on 2011-08-01 # Copyright (c) 2011 Martin J. Laubach. All rights reserved. # # ------------------------------------------------------------------------ + from django.dispatch import Signal + # ------------------------------------------------------------------------ # This signal is sent when an item editor managed object is completely # saved, especially including all foreign or manytomany dependencies. -itemeditor_post_save_related = Signal(providing_args=["instance", "created"]) +itemeditor_post_save_related = Signal() # ------------------------------------------------------------------------ diff --git a/feincms/static/feincms/fein_tree.css b/feincms/static/feincms/fein_tree.css deleted file mode 100644 index 8047a248b..000000000 --- a/feincms/static/feincms/fein_tree.css +++ /dev/null @@ -1,54 +0,0 @@ -.drag_handle { - width: 16px; - height: 16px; - background: url(img/arrow-move.png); - display: inline-block; - vertical-align: middle; -} - -.drag_handle:hover { - cursor: move; -} - -.drag_order img { - cursor: move; -} - -table tr.dragging { - opacity: .3; -} - -table tr.dragging td { - color: #333; -} - -table tr.folded { - background: red; -} - -div#drag_line { - position: relative; - height: 3px; - font-size: 0px; - background-color: #800080; -} - -div#drag_line div { - position: absolute; - height: 9px; - width: 9px; - background-image: url(img/drag_order_circle.png); - margin: -3px 0 0 -9px; -} - -.page_marker.children { - background: url(img/disclosure-down.png) no-repeat right top; -} - -.page_marker.closed { - background: url(img/disclosure-right.png) no-repeat right top; -} - -tr.non-editable a:link, tr.non-editable a:visited, tr.non-editable td { - color: #AAA; -} \ No newline at end of file diff --git a/feincms/static/feincms/fein_tree.js b/feincms/static/feincms/fein_tree.js deleted file mode 100755 index 90fa41ab0..000000000 --- a/feincms/static/feincms/fein_tree.js +++ /dev/null @@ -1,377 +0,0 @@ - -/* Suppress initial rendering of result list, but only if we can show it with JS later on */ -document.write('<style type="text/css">#result_list { display: none }</style>'); - - -feincms.jQuery(function($){ - // recolor tree after expand/collapse - $.extend($.fn.recolorRows = function() { - $('tr:visible:even', this).removeClass('row2').addClass('row1'); - $('tr:visible:odd', this).removeClass('row1').addClass('row2'); - }); - - function isExpandedNode(id) { - return feincms.collapsed_nodes.indexOf(id) == -1; - } - - function markNodeAsExpanded(id) { - // remove itemId from array of collapsed nodes - var idx = feincms.collapsed_nodes.indexOf(id); - if(idx >= 0) - feincms.collapsed_nodes.splice(idx, 1); - } - - function markNodeAsCollapsed(id) { - if(isExpandedNode(id)) - feincms.collapsed_nodes.push(id); - } - - // toggle children - function doToggle(id, show) { - var children = feincms.tree_structure[id]; - for (var i=0; i<children.length; ++i) { - var childId = children[i]; - if(show) { - $('#item-' + childId).show(); - // only reveal children if current node is not collapsed - if(isExpandedNode(childId)) { - doToggle(childId, show); - } - } else { - $('#item-' + childId).hide(); - // always recursively hide children - doToggle(childId, show); - } - } - } - - /* - * FeinCMS Drag-n-drop tree reordering. - * Based upon code by bright4 for Radiant CMS, rewritten for - * FeinCMS by Bjorn Post. - * - * September 2010 - * - */ - $.extend($.fn.feinTree = function() { - $('tr', this).each(function(i, el) { - // adds 'children' class to all parents - var pageId = extract_item_id($('.page_marker', el).attr('id')); - $(el).attr('id', 'item-' + pageId); - if (feincms.tree_structure[pageId].length) { - $('.page_marker', el).addClass('children'); - } - - // set 'level' on rel attribute - var pixels = $('.page_marker', el).css('width').replace(/[^\d]/ig,""); - var rel = Math.round(pixels/18); - $(el).attr('rel', rel); - }); - - $('div.drag_handle').bind('mousedown', function(event) { - BEFORE = 0; - AFTER = 1; - CHILD = 2; - CHILD_PAD = 20; - var originalRow = $(event.target).closest('tr'); - var rowHeight = originalRow.height(); - var childEdge = $(event.target).offset().left + $(event.target).width(); - var moveTo = new Object(); - var expandObj = new Object(); - - $("body").disableSelection().bind('mousemove', function(event) { - // attach dragged item to mouse - var cloned = originalRow.clone(); - if($('#ghost').length == 0) { - $('<div id="ghost"></div>').appendTo('body'); - } - $('#ghost').html(cloned).css({ 'opacity': .8, 'position': 'absolute', 'top': event.pageY, 'left': event.pageX-30, 'width': 600 }); - - // check on edge of screen - if(event.pageY+100 > $(window).height()+$(window).scrollTop()) { - $('html,body').stop().animate({scrollTop: $(window).scrollTop()+250 }, 500); - } else if(event.pageY-50 < $(window).scrollTop()) { - $('html,body').stop().animate({scrollTop: $(window).scrollTop()-250 }, 500); - } - - // check if drag_line element already exists, else append - if($("#drag_line").length < 1) { - $("body").append('<div id="drag_line" style="position:absolute">line<div></div></div>'); - } - - var top; - - // loop trough all rows - $("tr", originalRow.parent()).each(function(index, element) { - element = $(element); - top = element.offset().top; - - // check if mouse is over a row - if (event.pageY >= top && event.pageY <= top + rowHeight) { - // check if collapsed children, if so, on hover with simple timeout - if( - $('span.page_marker', element).hasClass('children') && - $('span.page_marker', element).hasClass('closed') - ) { - var id = extract_item_id($('span.page_marker', element).attr('id')); - setTimeout(function() { - doToggle(id, true); - $('#result_list tbody').recolorRows(); - $('span.page_marker', element).removeClass('closed'); - }, 750); - } - - var targetRow = null; - var targetLoc; - if(event.pageY >= top && event.pageY <= top + rowHeight / 2 && element.prev()) { - // upper half row - targetRow = element; - targetLoc = BEFORE; - } else if(event.pageY > top + rowHeight / 2 && event.pageY <= top + rowHeight) { - // lower half row - if(element.next().size() > 0) { - next = element.next().attr('rel').replace(/[^\d]/ig,"") - } else { - next = 0; - } - - if(next < element.attr('rel').replace(/[^\d]/ig,"") && event.pageX < 100) { - targetRow = element; - targetLoc = AFTER; - } else { - targetRow = element; - targetLoc = CHILD; - } - } - - if(targetRow) { - var padding = 30 + element.attr('rel') * CHILD_PAD; - - $("#drag_line").css({ - 'width': targetRow.width() - padding - (targetLoc == CHILD ? CHILD_PAD : 0 ), - 'left': targetRow.offset().left + padding + (targetLoc == CHILD ? CHILD_PAD : 0), - 'top': targetRow.offset().top + (targetLoc == AFTER || targetLoc == CHILD ? rowHeight: 0) -1 - }); - - // Store the found row and options - moveTo.hovering = element; - moveTo.relativeTo = targetRow; - moveTo.side = targetLoc; - - return true; - } - } - }); - }); - - $('body').keydown(function(event) { - if (event.which == '27') { - $("#drag_line").remove(); - $("#ghost").remove(); - $("body").enableSelection().unbind('mousemove').unbind('mouseup'); - event.preventDefault(); - } - }); - - $("body").bind('mouseup', function(event) { - var cutItem = extract_item_id(originalRow.find('.page_marker').attr('id')); - var pastedOn = extract_item_id(moveTo.relativeTo.find('.page_marker').attr('id')); - - // get out early if items are the same - if(cutItem != pastedOn) { - var isParent = (moveTo.relativeTo.next().attr('rel') > moveTo.relativeTo.attr('rel')); - // determine position - if(moveTo.side == CHILD && !isParent) { - var position = 'last-child'; - } else { - var position = 'left'; - } - - // save - $.post('.', { - '__cmd': 'move_node', - 'position': position, - 'cut_item': cutItem, - 'pasted_on': pastedOn - }, function(data) { - window.location.reload(); - }); - } else { - $("#drag_line").remove(); - $("#ghost").remove(); - } - $("body").enableSelection().unbind('mousemove').unbind('mouseup'); - }); - - }); - - return this; - }); - - /* Every time the user expands or collapses a part of the tree, we remember - the current state of the tree so we can restore it on a reload. - Note: We might use html5's session storage? */ - function storeCollapsedNodes(nodes) { - $.cookie('feincms_collapsed_nodes', "[" + nodes.join(",") + "]", { expires: 7 }); - } - - function retrieveCollapsedNodes() { - var n = $.cookie('feincms_collapsed_nodes'); - if(n != null) { - try { - n = $.parseJSON(n); - } catch(e) { - n = null; - } - } - return n; - } - - function expandOrCollapseNode(item) { - var show = true; - - if(!item.hasClass('children')) - return; - - var itemId = extract_item_id(item.attr('id')); - - if(!isExpandedNode(itemId)) { - item.removeClass('closed'); - markNodeAsExpanded(itemId); - } else { - item.addClass('closed'); - show = false; - markNodeAsCollapsed(itemId); - } - - storeCollapsedNodes(feincms.collapsed_nodes); - - doToggle(itemId, show); - - $('#result_list tbody').recolorRows(); - } - - $.extend($.fn.feinTreeToggleItem = function() { - $(this).click(function(event){ - expandOrCollapseNode($(this)); - if(event.stopPropagation) { - event.stopPropagation(); - } else { - event.cancelBubble = true; - } - - return false; - }); - return this; - }); - - // bind the collapse all children event - $.extend($.fn.bindCollapseTreeEvent = function() { - $(this).click(function() { - rlist = $("#result_list"); - rlist.hide(); - $('tbody tr', rlist).each(function(i, el) { - var marker = $('.page_marker', el); - if(marker.hasClass('children')) { - var itemId = extract_item_id(marker.attr('id')); - doToggle(itemId, false); - marker.addClass('closed'); - markNodeAsCollapsed(itemId); - } - }); - storeCollapsedNodes(feincms.collapsed_nodes); - rlist.show(); - $('tbody', rlist).recolorRows(); - }); - return this; - }); - - // bind the open all children event - $.extend($.fn.bindOpenTreeEvent = function() { - $(this).click(function() { - rlist = $("#result_list"); - rlist.hide(); - $('tbody tr', rlist).each(function(i, el) { - var marker = $('span.page_marker', el); - if(marker.hasClass('children')) { - var itemId = extract_item_id($('span.page_marker', el).attr('id')); - doToggle(itemId, true); - marker.removeClass('closed'); - markNodeAsExpanded(itemId); - } - }); - storeCollapsedNodes([]); - rlist.show(); - $('tbody', rlist).recolorRows(); - }); - return this; - }); - - var changelist_tab = function(elem, event, direction) { - event.preventDefault(); - elem = $(elem); - var ne = (direction > 0) ? elem.nextAll(':visible:first') : elem.prevAll(':visible:first'); - if(ne) { - elem.attr('tabindex', -1); - ne.attr('tabindex', '0'); - ne.focus(); - } - }; - - function keyboardNavigationHandler(event) { - // console.log('keydown', this, event.keyCode); - switch(event.keyCode) { - case 40: // down - changelist_tab(this, event, 1); - break; - case 38: // up - changelist_tab(this, event, -1); - break; - case 37: // left - case 39: // right - expandOrCollapseNode($(this).find('.page_marker')); - break; - case 13: // return - where_to = extract_item_id($('span', this).attr('id')); - document.location = document.location.pathname + where_to + '/' - break; - default: - break; - }; - } - - // fire! - rlist = $("#result_list"); - if($('tbody tr', rlist).length > 1) { - rlist.hide(); - $('tbody', rlist).feinTree(); - $('span.page_marker', rlist).feinTreeToggleItem(); - $('#collapse_entire_tree').bindCollapseTreeEvent(); - $('#open_entire_tree').bindOpenTreeEvent(); - - // Disable things user cannot do anyway (object level permissions) - non_editable_fields = $('.tree-item-not-editable', rlist).parents('tr'); - non_editable_fields.addClass('non-editable'); - $('input:checkbox', non_editable_fields).attr('disabled', 'disabled'); - $('a:first', non_editable_fields).click(function(e){e.preventDefault()}); - $('.drag_handle', non_editable_fields).removeClass('drag_handle'); - - /* Enable focussing, put focus on first result, add handler for keyboard navigation */ - $('tr', rlist).attr('tabindex', -1); - $('tbody tr:first', rlist).attr('tabindex', 0).focus(); - $('tr', rlist).keydown(keyboardNavigationHandler); - - feincms.collapsed_nodes = []; - var storedNodes = retrieveCollapsedNodes(); - if(storedNodes == null) { - $('#collapse_entire_tree').click(); - } else { - for(var i=0; i<storedNodes.length; i++) { - $('#page_marker-' + storedNodes[i]).click(); - } - } - } - - rlist.show(); - $('tbody', rlist).recolorRows(); -}); diff --git a/feincms/static/feincms/frontend_editing.css b/feincms/static/feincms/frontend_editing.css deleted file mode 100644 index d953189d0..000000000 --- a/feincms/static/feincms/frontend_editing.css +++ /dev/null @@ -1,68 +0,0 @@ -.fe_box { - position: relative; - opacity: 0.6; - width: 100%!important; -} - -.fe_box .content { - overflow: hidden; -} - -#fe_controls, #fe_tools { - z-index: 100; - background-color: white; - /* CSS3 standard: */ - opacity: 0.8; - /* IE proprietary: */ - filter: alpha(opacity=80); -} - -#fe_controls { - position: fixed; - top: 0; - right: 0; - margin: 0; - font-size: smaller; - padding: 3px 3px 3px 4px; - border-left: solid #7f7f7f 2px; - border-bottom: solid #7f7f7f 2px; - border-bottom-left-radius: 3px; - -moz-border-radius-bottomleft: 3px; - -webkit-border-bottom-left-radius: 3px; -} - -#fe_controls > a { - display: inline-block; - border-radius: 2px; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; - border: 1px solid #7f7f7f; - white-space: nowrap; - text-decoration: none; - padding: 1px 3px 2px; - background-color: #e8e8e8; - color: #000; -} - -#fe_controls > a:hover { - background-color: #b4d5ff; -} - -#fe_tools { - position: absolute; - right: 0px; - top: 0px; - display: none; - border: 1px solid #000; - font-size: 70%; -} - -#fe_tools a { - background: #000; - color: #fff; - text-decoration: none; - margin: 0 10px 0 0; - padding: 4px 5px; - display: block; - float: left; -} \ No newline at end of file diff --git a/feincms/static/feincms/frontend_editing.js b/feincms/static/feincms/frontend_editing.js deleted file mode 100644 index e1a132c6d..000000000 --- a/feincms/static/feincms/frontend_editing.js +++ /dev/null @@ -1,40 +0,0 @@ -(function($){ - $(function(){ - feincms.fe_init_animations(); - - $("#fe_tools > a").live("click", function() { - var fe_box = $(this).parents('div.fe_box'); - - if (this.id == 'fe_tools_edit') { - res = fe_box.attr('id').match(/([^\-]+)-([^\-]+)-([^\-]+)-(\d+)-(\d+)/); - var base = feincms.admin_index + res[1] +"/"+ res[2]; - window.open(base+"/"+res[4]+'|'+res[3]+'|'+res[5]+'/', - 'fe_editor', - 'height=500,width=800,resizable=yes,scrollbars=yes'); - } - - return false; - }); - }); - - feincms.fe_init_animations = function() { - var fe_tools = $('#fe_tools'); - $('.fe_box').hover( - function(){ - $(this).css('background', '#e8e8ff').animate({'opacity': 1}, 100).append(fe_tools); - fe_tools.show(); - }, - function(){ - $(this).animate({'opacity': 0.6}, 100).css('background', 'none'); - fe_tools.hide(); - } - ); - } - - feincms.fe_update_content = function(identifier, content) { - var region = $('#' + identifier); - region.animate({'opacity': 0}).html(content); - region.animate({'opacity': 1.5}).animate({'opacity': 0.6}); - feincms.fe_init_animations(); - } -})(feincms.jQuery); diff --git a/feincms/static/feincms/ie_compat.js b/feincms/static/feincms/ie_compat.js deleted file mode 100644 index 7cd1ec39f..000000000 --- a/feincms/static/feincms/ie_compat.js +++ /dev/null @@ -1,60 +0,0 @@ -/* Re-implement some methods IE sadly does not */ - -if(typeof(Array.prototype.indexOf) == 'undefined') { - // indexOf() function prototype for IE6/7/8 compatibility, taken from - // JavaScript Standard Library - http://www.devpro.it/JSL/ - Array.prototype.indexOf=function(elm,i){ - var j=this.length; - if(!i)i=0; - if(i>=0){while(i<j){if(this[i++]===elm){ - i=i-1+j;j=i-j; - }}} - else - j=this.indexOf(elm,j+i); - return j!==this.length?j:-1; - } -} - -if (!Array.prototype.filter) -{ - Array.prototype.filter = function(fun /*, thisp*/) - { - var len = this.length; - if (typeof fun != "function") - throw new TypeError(); - - var res = new Array(); - var thisp = arguments[1]; - for (var i = 0; i < len; i++) - { - if (i in this) - { - var val = this[i]; // in case fun mutates this - if (fun.call(thisp, val, i, this)) - res.push(val); - } - } - - return res; - }; -} - -if (!Array.prototype.map) -{ - Array.prototype.map = function(fun /*, thisp*/) - { - var len = this.length; - if (typeof fun != "function") - throw new TypeError(); - - var res = new Array(len); - var thisp = arguments[1]; - for (var i = 0; i < len; i++) - { - if (i in this) - res[i] = fun.call(thisp, this[i], i, this); - } - - return res; - }; -} \ No newline at end of file diff --git a/feincms/static/feincms/img/drag_order_circle.png b/feincms/static/feincms/img/drag_order_circle.png deleted file mode 100644 index 2e95561f1..000000000 Binary files a/feincms/static/feincms/img/drag_order_circle.png and /dev/null differ diff --git a/feincms/static/feincms/img/help.gif b/feincms/static/feincms/img/help.gif deleted file mode 100644 index 3b5142531..000000000 Binary files a/feincms/static/feincms/img/help.gif and /dev/null differ diff --git a/feincms/static/feincms/img/icon-no.gif b/feincms/static/feincms/img/icon-no.gif new file mode 100644 index 000000000..1b4ee5814 Binary files /dev/null and b/feincms/static/feincms/img/icon-no.gif differ diff --git a/feincms/static/feincms/img/icon-unknown.gif b/feincms/static/feincms/img/icon-unknown.gif new file mode 100644 index 000000000..cfd2b02ad Binary files /dev/null and b/feincms/static/feincms/img/icon-unknown.gif differ diff --git a/feincms/static/feincms/img/icon-yes.gif b/feincms/static/feincms/img/icon-yes.gif new file mode 100644 index 000000000..739928274 Binary files /dev/null and b/feincms/static/feincms/img/icon-yes.gif differ diff --git a/feincms/static/feincms/img/icon_addlink.gif b/feincms/static/feincms/img/icon_addlink.gif new file mode 100644 index 000000000..ee70e1adb Binary files /dev/null and b/feincms/static/feincms/img/icon_addlink.gif differ diff --git a/feincms/static/feincms/img/important.gif b/feincms/static/feincms/img/important.gif deleted file mode 100644 index 41d49438f..000000000 Binary files a/feincms/static/feincms/img/important.gif and /dev/null differ diff --git a/feincms/static/feincms/img/info.gif b/feincms/static/feincms/img/info.gif deleted file mode 100644 index c81828d1c..000000000 Binary files a/feincms/static/feincms/img/info.gif and /dev/null differ diff --git a/feincms/static/feincms/img/item_editor_form_title.png b/feincms/static/feincms/img/item_editor_form_title.png deleted file mode 100644 index bea3c14ea..000000000 Binary files a/feincms/static/feincms/img/item_editor_form_title.png and /dev/null differ diff --git a/feincms/static/feincms/img/nav-bg.gif b/feincms/static/feincms/img/nav-bg.gif deleted file mode 100644 index f8402b809..000000000 Binary files a/feincms/static/feincms/img/nav-bg.gif and /dev/null differ diff --git a/feincms/static/feincms/img/selector-search.gif b/feincms/static/feincms/img/selector-search.gif new file mode 100644 index 000000000..6d5f4c749 Binary files /dev/null and b/feincms/static/feincms/img/selector-search.gif differ diff --git a/feincms/static/feincms/img/title.gif b/feincms/static/feincms/img/title.gif deleted file mode 100644 index f92b59665..000000000 Binary files a/feincms/static/feincms/img/title.gif and /dev/null differ diff --git a/feincms/static/feincms/img/wrench.png b/feincms/static/feincms/img/wrench.png deleted file mode 100644 index 5c8213fef..000000000 Binary files a/feincms/static/feincms/img/wrench.png and /dev/null differ diff --git a/feincms/static/feincms/item_editor.css b/feincms/static/feincms/item_editor.css new file mode 100644 index 000000000..0d2e47680 --- /dev/null +++ b/feincms/static/feincms/item_editor.css @@ -0,0 +1,308 @@ +.navi_tab { + float: left; + padding: 8px 10px; + cursor: pointer; + margin-top: 3px; + font-weight: bold; + font-size: 11px; + color: #666; + background: #f6f6f6; + border: 1px solid #eee; + text-transform: uppercase; +} +.tab_active { + background: #79aec8; + color: white; + border-color: #79aec8; +} + +#feincmsmain { + clear: both; + padding: 10px 10px 10px 10px; + border: 1px solid #eee; + margin: 0 0 10px 0; +} + +.panel { + display: none; + position: relative; + padding-bottom: 39px; +} + +.order-item { + margin: 0 0 10px 0; + position: relative; +} + +.order-item h2 { + background-image: url("img/arrow-move.png"); + background-repeat: no-repeat; + background-position: 6px 9px; +} + +.order-item .handle { + display: inline-block; + height: 14px; + width: 15px; + cursor: move; +} + +.order-item .collapse { + cursor: pointer; + /*color: #444;*/ + font-weight: normal; + + border-bottom: 1px solid rgba(255, 255, 255, 0.25); +} + +.order-item .collapse:hover { + opacity: 0.7; +} + +.item-delete { + cursor: pointer; + float: right; + margin: 4px 3px 0px 0; +} + +.highlight, +.helper { + height: 34px; + margin: 0 0 10px 0; + border: none; + opacity: 0.3; + background: #79aec8; +} + +.helper { + height: 25px !important; + opacity: 1; +} + +.button { + margin: 5px; + padding: 5px; + font-weight: bold; + cursor: pointer; + border: 1px solid #678; +} + +select { + max-width: 580px; +} + +#feincmsmain_wrapper { + margin: 10px 0 30px 0; +} + +.clearfix:before, +.clearfix:after { + content: " "; + display: table; +} +.clearfix:after { + clear: both; +} + +textarea { + width: 580px; + margin-top: 5px; + margin-bottom: 5px; +} + +.inline-group .tabular textarea { + width: auto; +} + +.item-controls { + position: absolute; + top: 2px; + right: 32px; +} + +.item-control-unit { + float: left; +} + +.item-controls select { + margin-left: 7px; +} + +.machine-control { + padding: 5px 10px 5px 10px; + border: 1px solid #eee; + background-color: #f8f8f8; + position: absolute; + left: -11px; + bottom: -30px; + width: 100%; + + height: 55px; +} + +.machine-control .button { + padding-top: 6px; + padding-bottom: 6px; +} + +.control-unit { + float: left; + padding: 0 20px 0 5px; + border-left: 1px solid #eee; +} + +.control-unit:first-child { + border-left: none; +} + +.control-unit span { + font-weight: bold; +} + +a.actionbutton { + display: block; + background-repeat: no-repeat; + width: 50px; + height: 50px; + float: left; + margin: 5px 0 0 20px; + text-indent: -7000px; + transition: none; +} + +a.richtextcontent { + background: url(img/contenttypes.png) no-repeat 0 0; +} +a.richtextcontent:hover { + background-position: 0 -70px; +} + +a.imagecontent { + background: url(img/contenttypes.png) no-repeat -70px 0; +} +a.imagecontent:hover { + background-position: -70px -70px; +} + +a.gallerycontent { + background: url(img/contenttypes.png) no-repeat -140px 0; +} +a.gallerycontent:hover { + background-position: -140px -70px; +} + +a.oembedcontent { + background: url(img/contenttypes.png) no-repeat -280px 0; +} +a.oembedcontent:hover { + background-position: -280px -70px; +} + +a.pdfcontent { + background: url(img/contenttypes.png) no-repeat -210px 0; +} +a.pdfcontent:hover { + background-position: -210px -70px; +} + +a.audiocontent { + background: url(img/contenttypes.png) no-repeat -350px 0; +} +a.audiocontent:hover { + background-position: -350px -70px; +} + +.control-unit select { + float: left; + position: relative; + top: 13px; +} + +.empty-machine-msg { + margin: 10px 0px 20px 20px; + font-size: 14px; +} + +td span select { + width: 600px; +} + +.change-template-button { + margin-left: 7em; + padding-left: 30px; +} + +/* Allow nested lists in error items */ +ul.errorlist ul { + margin-left: 1em; + padding-left: 0; + list-style-type: square; +} + +ul.errorlist li li { + /* Avoid repeating the warning image every time*/ + background-image: none; + padding: 0; +} + +div.order-machine div.inline-related > h3 { + display: none; +} + +.hidden-form-row { + display: none; +} + +#extension_options_wrapper { + border-bottom: 1px solid #eee; +} + +#extension_options > .module.aligned { + border-top: 1px solid #eee; + margin-bottom: -1px; +} + +/* various overrides */ +#id_redirect_to { + width: 20em; +} /* raw_id_fields act-a-like for redirect_to */ + +/* overwrite flat theme default label width because of problems with the CKEditor */ +.aligned .text label { + width: auto; +} + +/* django suit hacks */ +/*********************/ + +#suit-center #feincmsmain { + clear: none; +} + +#suit-center .panel { + padding-top: 15px; +} + +#suit-center .form-horizontal .inline-related fieldset { + margin-top: 10px; +} + +#suit-center .panel h2 { + color: white; + font-size: 13px; + line-height: 12px; + margin-left: 0; + text-shadow: none; +} + +#suit-center .item-delete { + margin: 3px 5px 0 0; +} + +#suit-center .order-item .handle { + height: 36px; +} + +#suit-center .order-machine .order-item { + margin-top: 10px; +} diff --git a/feincms/static/feincms/item_editor.js b/feincms/static/feincms/item_editor.js index d194b9979..e8dd30dd7 100644 --- a/feincms/static/feincms/item_editor.js +++ b/feincms/static/feincms/item_editor.js @@ -1,580 +1,673 @@ -if(!Array.indexOf) { - Array.prototype.indexOf = function(obj) { - for(var i=0; i<this.length; i++) { - if(this[i]==obj) { - return i; - } - } - return -1; - } -} - -(function($){ - // Patch up urlify maps to generate nicer slugs in german - if(typeof(Downcoder) != "undefined"){ - Downcoder.Initialize() ; - Downcoder.map["ö"] = Downcoder.map["Ö"] = "oe"; - Downcoder.map["ä"] = Downcoder.map["Ä"] = "ae"; - Downcoder.map["ü"] = Downcoder.map["Ü"] = "ue"; - } - - function feincms_gettext(s) { - // Unfortunately, we cannot use Django's jsi18n view for this - // because it only sends translations from the package - // "django.conf" -- our own djangojs domain strings won't be - // picked up - - if (FEINCMS_ITEM_EDITOR_GETTEXT[s]) - return FEINCMS_ITEM_EDITOR_GETTEXT[s]; - return s; - } - - function create_new_item_from_form(form, modname, modvar){ - var fieldset = $("<fieldset>").addClass("module aligned order-item item-wrapper-" + modvar); - - var wrp = []; - wrp.push('<h2><img class="item-delete" src="'+IMG_DELETELINK_PATH+'" /><span class="handle"></span> <span class="modname">'+modname+'</span>  (<span class="collapse">'+feincms_gettext('Hide')+'</span>)</h2>'); - wrp.push('<div class="item-content"></div>'); - fieldset.append(wrp.join("")); - - fieldset.children(".item-content").append(form); //relocates, not clone - - $("<div>").addClass("item-controls").appendTo(fieldset); - - return fieldset; +/* global Downcoder, django, feincms, ItemEditor, interpolate */ +/* global IMG_DELETELINK_PATH, REGION_MAP, REGION_NAMES, CONTENT_NAMES, FEINCMS_ITEM_EDITOR_GETTEXT, CONTENT_TYPE_BUTTONS */ +/* global contentblock_init_handlers, contentblock_move_handlers */ +/* global id_to_windowname */ +/* global template_regions */ + +;(($) => { + // Patch up urlify maps to generate nicer slugs in german + if (typeof Downcoder !== "undefined") { + Downcoder.Initialize() + Downcoder.map.ö = Downcoder.map.Ö = "oe" + Downcoder.map.ä = Downcoder.map.Ä = "ae" + Downcoder.map.ü = Downcoder.map.Ü = "ue" + } + + function feincms_gettext(s) { + // Unfortunately, we cannot use Django's jsi18n view for this + // because it only sends translations from the package + // "django.conf" -- our own djangojs domain strings won't be + // picked up + + if (FEINCMS_ITEM_EDITOR_GETTEXT[s]) return FEINCMS_ITEM_EDITOR_GETTEXT[s] + return s + } + + function create_new_item_from_form(form, modname, modvar) { + const fieldset = $("<fieldset>").addClass( + `module aligned order-item item-wrapper-${modvar}`, + ) + const original_id_id = `#id_${form.attr("id")}-id` + + const wrp = ["<h2>"] + // If original has delete checkbox or this is a freshly added CT? Add delete link! + if ($(".delete", form).length || !$(original_id_id, form).val()) { + wrp.push(`<img class="item-delete" src="${IMG_DELETELINK_PATH}" />`) } - - - SELECTS = {}; - function save_content_type_selects() { - $('#main>.panel').each(function() { - SELECTS[this.id.replace(/_body$/, '')] = $("select[name=order-machine-add-select]", this).clone().removeAttr("name"); - }); - } - - function update_item_controls(item, target_region_id){ - var item_controls = item.find(".item-controls"); - item_controls.find(".item-control-units").remove(); // Remove all controls, if any. - - // (Re)build controls - var control_units = $("<div>").addClass("item-control-units").appendTo(item_controls); - - // Insert control unit - var insert_control = $("<div>").addClass("item-control-unit"); - var select_content = SELECTS[REGION_MAP[target_region_id]].clone(); - var insert_after = $("<input>").attr("type", "button").addClass("button").attr("value", feincms_gettext('After')).click(function(){ - var modvar = select_content.val(); - var modname = select_content.find("option:selected").html(); - var new_fieldset = create_new_fieldset_from_module(modvar, modname); - add_fieldset(target_region_id, new_fieldset, {where:'insertAfter', relative_to:item, animate:true}); - update_item_controls(new_fieldset, target_region_id); - }); - var insert_before = $("<input>").attr("type", "button").addClass("button").attr("value", feincms_gettext('Before')).click(function(){ - var modvar = select_content.val(); - var modname = select_content.find("option:selected").html(); - var new_fieldset = create_new_fieldset_from_module(modvar, modname); - add_fieldset(target_region_id, new_fieldset, {where:'insertBefore', relative_to:item, animate:true}); - update_item_controls(new_fieldset, target_region_id); - }); - insert_control.append("<span>" + feincms_gettext('Insert new:') + "</span>").append(" ").append(select_content).append(" ").append(insert_before).append(insert_after); - control_units.append(insert_control); - - // Move control unit - if (REGION_MAP.length > 1) { - var wrp = []; - wrp.push('<div class="item-control-unit move-control"><span>'+feincms_gettext('Move to')+': </span><select name="item-move-select">'); - - for (var i=0; i < REGION_MAP.length; i++) { - if (i != target_region_id) { // Do not put the target region in the list - wrp.push('<option value="'+REGION_MAP[i]+'">'+REGION_NAMES[i]+'</option>'); - } - } - wrp.push('</select><input type="button" class="button" value="'+feincms_gettext('Move')+'" /></div>'); - - var move_control = $(wrp.join("")); - move_control.find(".button").click(function(){ - var move_to = $(this).prev().val(); - move_item(REGION_MAP.indexOf(move_to), item); - }); - control_units.append(move_control); // Add new one + wrp.push( + `<span class="handle"></span> <span class="modname">${modname}</span></h2>`, + ) + wrp.push('<div class="item-content"></div>') + fieldset.append(wrp.join("")) + + fieldset.children(".item-content").append(form) //relocates, not clone + + $("<div>").addClass("item-controls").appendTo(fieldset) + + return fieldset + } + + const SELECTS = {} + function save_content_type_selects() { + $("#feincmsmain>.panel").each(function () { + SELECTS[this.id.replace(/_body$/, "")] = $( + "select[name=order-machine-add-select]", + this, + ) + .clone() + .removeAttr("name") + }) + } + + function update_item_controls(item, target_region_id) { + const item_controls = item.find(".item-controls") + item_controls.empty() + + // Insert control unit + const insert_control = $("<div>").addClass("item-control-unit") + const select_content = SELECTS[REGION_MAP[target_region_id]].clone() + + select_content.change(() => { + const modvar = select_content.val() + const modname = select_content.find("option:selected").html() + const new_fieldset = create_new_fieldset_from_module(modvar, modname) + add_fieldset(target_region_id, new_fieldset, { + where: "insertBefore", + relative_to: item, + animate: true, + }) + update_item_controls(new_fieldset, target_region_id) + + select_content.val("") + }) + insert_control.append(select_content) + item_controls.append(insert_control) + + // Move control unit + if (REGION_MAP.length > 1) { + const wrp = [] + wrp.push( + '<div class="item-control-unit move-control"><select name="item-move-select">', + ) + wrp.push( + `<option disabled selected>${feincms_gettext( + "MOVE_TO_REGION", + )}</option>`, + ) + + for (let i = 0; i < REGION_MAP.length; i++) { + if (i !== target_region_id) { + // Do not put the target region in the list + wrp.push( + `<option value="${REGION_MAP[i]}">${REGION_NAMES[i]}</option>`, + ) } - - // Controls animations - item_controls.find("*").hide(); - var is_hidden = true; - var mouseenter_timeout; - var mouseleave_timeout; - function hide_controls() { - item_controls.find("*").fadeOut(800); - is_hidden = true; - } - function show_controls() { - item_controls.find("*").fadeIn(800); - is_hidden = false; - } - item_controls.unbind('mouseleave'); // Unbind in case it's already been bound. - item_controls.mouseleave(function() { - clearTimeout(mouseenter_timeout); - mouseleave_timeout = setTimeout(hide_controls, 1000); - }); - item_controls.unbind('mouseenter'); // Unbind in case it's already been bound. - item_controls.mouseenter(function() { - clearTimeout(mouseleave_timeout); - if (is_hidden) mouseenter_timeout = setTimeout(show_controls, 200); // To prevent the control bar to appear when mouse accidentally enters the zone. - }); + } + wrp.push("</select>") + + const move_control = $(wrp.join("")) + move_control.find("select").change(function () { + const move_to = $(this).val() + move_item(REGION_MAP.indexOf(move_to), item) + }) + item_controls.append(move_control) // Add new one } + } + function create_new_fieldset_from_module(modvar, modname) { + const new_form = create_new_spare_form(modvar) + return create_new_item_from_form(new_form, modname, modvar) + } - function create_new_fieldset_from_module(modvar, modname) { - var new_form = create_new_spare_form(modvar); - return create_new_item_from_form(new_form, modname, modvar); - } - - function add_fieldset(region_id, item, how){ - /* `how` should be an object. + function add_fieldset(region_id, item, how) { + /* `how` should be an object. `how.where` should be one of: - 'append' -- last region - 'prepend' -- first region - 'insertBefore' -- insert before relative_to - 'insertAfter' -- insert after relative_to */ - // Default parameters - if (how) $.extend({ - where: 'append', - relative_to: undefined, - animate: false - }, how); - - item.hide(); - if(how.where == 'append' || how.where == 'prepend'){ - $("#"+ REGION_MAP[region_id] +"_body").children("div.order-machine")[how.where](item); - } - else if(how.where == 'insertBefore' || how.where == 'insertAfter'){ - if(how.relative_to){ - item[how.where](how.relative_to); - } - else{ - window.alert('DEBUG: invalid add_fieldset usage'); - return; - } - } - else{ - window.alert('DEBUG: invalid add_fieldset usage'); - return; - } - set_item_field_value(item, "region-choice-field", region_id); - init_contentblocks(); - - if (how.animate) { - item.fadeIn(800); - } - else { - item.show(); - } - } - - function create_new_spare_form(modvar) { - var old_form_count = parseInt($('#id_'+modvar+'_set-TOTAL_FORMS').val()); - // **** UGLY CODE WARNING, avert your gaze! **** - // for some unknown reason, the add-button click handler function - // fails on the first triggerHandler call in some rare cases; - // we can detect this here and retry: - for(var i = 0; i < 2; i++){ - // Use Django's built-in inline spawing mechanism (Django 1.2+) - // must use django.jQuery since the bound function lives there: - var returned = django.jQuery('#'+modvar+'_set-group').find( - 'div.add-row > a').triggerHandler('click'); - if(returned==false) break; // correct return value - } - var new_form_count = parseInt($('#id_'+modvar+'_set-TOTAL_FORMS').val()); - if(new_form_count > old_form_count){ - return $('#'+modvar+'_set-'+(new_form_count-1)); - } - // TODO: add fallback for older versions by manually cloning - // empty fieldset (provided using extra=1) - } - - function set_item_field_value(item, field, value) { - // item: DOM object for the item's fieldset. - // field: "order-field" | "delete-field" | "region-choice-field" - if (field=="delete-field") - item.find("."+field).attr("checked",value); - else if (field=="region-choice-field") { - var old_region_id = REGION_MAP.indexOf(item.find("."+field).val()); - item.find("."+field).val(REGION_MAP[value]); - - old_region_item = $("#"+REGION_MAP[old_region_id]+"_body"); - if (old_region_item.children("div.order-machine").children().length == 0) - old_region_item.children("div.empty-machine-msg").show(); - else - old_region_item.children("div.empty-machine-msg").hide(); - - new_region_item = $("#"+REGION_MAP[value]+"_body"); - new_region_item.children("div.empty-machine-msg").hide(); - } - else - item.find("."+field).val(value); - } - - function move_item(region_id, item) { - poorify_rich(item); - item.fadeOut(800, function() { - add_fieldset(region_id, item, {where:'append'}); - richify_poor(item); - update_item_controls(item, region_id); - item.show(); - }); - } - - function poorify_rich(item){ - item.children(".item-content").hide(); - - for (var i=0; i<contentblock_move_handlers.poorify.length; i++) - contentblock_move_handlers.poorify[i](item); + // Default parameters + if (how) + $.extend( + { + where: "append", + relative_to: undefined, + animate: false, + }, + how, + ) + + item.hide() + if (how.where === "append" || how.where === "prepend") { + $(`#${REGION_MAP[region_id]}_body`) + .children("div.order-machine") + [how.where](item) + } else if (how.where === "insertBefore" || how.where === "insertAfter") { + if (how.relative_to) { + item[how.where](how.relative_to) + } else { + window.alert("DEBUG: invalid add_fieldset usage") + return + } + } else { + window.alert("DEBUG: invalid add_fieldset usage") + return } + set_item_field_value(item, "region-choice-field", region_id) + init_contentblocks() - function richify_poor(item){ - item.children(".item-content").show(); - - for (var i=0; i<contentblock_move_handlers.richify.length; i++) - contentblock_move_handlers.richify[i](item); + if (how.animate) { + item.fadeIn(800) + } else { + item.show() } - - function sort_by_ordering(e1, e2) { - var v1 = parseInt($('.order-field', e1).val()) || 0; - var v2 = parseInt($('.order-field', e2).val()) || 0; - return v1 > v2 ? 1 : -1; - }; - - function give_ordering_to_content_types() { - for (var i=0; i<REGION_MAP.length;i++) { - var container = $("#"+REGION_MAP[i]+"_body div.order-machine"); - for (var j=0; j<container.children().length; j++) { - set_item_field_value(container.find("fieldset.order-item:eq("+j+")"), "order-field", j); - } + } + + function create_new_spare_form(modvar) { + const old_form_count = parseInt( + $(`#id_${modvar}_set-TOTAL_FORMS`).val(), + 10, + ) + // **** UGLY CODE WARNING, avert your gaze! **** + // for some unknown reason, the add-button click handler function + // fails on the first triggerHandler call in some rare cases; + // we can detect this here and retry: + for (let i = 0; i < 2; i++) { + // Use Django's built-in inline spawing mechanism (Django 1.2+) + // must use django.jQuery since the bound function lives there: + django + .jQuery(`#${modvar}_set-group`) + .find(".add-row a") + .triggerHandler("click") + const new_form_count = parseInt( + $(`#id_${modvar}_set-TOTAL_FORMS`).val(), + 10, + ) + if (new_form_count > old_form_count) { + return $(`#${modvar}_set-${new_form_count - 1}`) } } - - function order_content_types_in_regions() { - for (var i=0; i<REGION_MAP.length;i++) { - var container = $("#"+REGION_MAP[i]+"_body div.order-machine"); - container.children().sort(sort_by_ordering).each(function() { - container.append(this); - }); + } + + function set_item_field_value(item, field, value) { + // item: DOM object for the item's fieldset. + // field: "order-field" | "delete-field" | "region-choice-field" + if (field === "delete-field") item.find(`.${field}`).attr("checked", value) + else if (field === "region-choice-field") { + const old_region_id = REGION_MAP.indexOf(item.find(`.${field}`).val()) + item.find(`.${field}`).val(REGION_MAP[value]) + + // show/hide the empty machine message in the source and + // target region. + const old_region_item = $(`#${REGION_MAP[old_region_id]}_body`) + if (old_region_item.children("div.order-machine").children().length === 0) + old_region_item.children("div.empty-machine-msg").show() + else old_region_item.children("div.empty-machine-msg").hide() + + const new_region_item = $(`#${REGION_MAP[value]}_body`) + new_region_item.children("div.empty-machine-msg").hide() + } else item.find(`.${field}`).val(value) + } + + function move_item(region_id, item) { + poorify_rich(item) + item.fadeOut(800, () => { + add_fieldset(region_id, item, { where: "append" }) + richify_poor(item) + update_item_controls(item, region_id) + item.show() + }) + } + + function poorify_rich(item) { + item.children(".item-content").hide() + + for (let i = 0; i < contentblock_move_handlers.poorify.length; i++) + contentblock_move_handlers.poorify[i](item) + } + + function richify_poor(item) { + item.children(".item-content").show() + + for (let i = 0; i < contentblock_move_handlers.richify.length; i++) + contentblock_move_handlers.richify[i](item) + } + + function sort_by_ordering(e1, e2) { + const v1 = parseInt($(".order-field", e1).val(), 10) || 0 + const v2 = parseInt($(".order-field", e2).val(), 10) || 0 + return v1 > v2 ? 1 : -1 + } + + function give_ordering_to_content_types() { + for (let i = 0; i < REGION_MAP.length; i++) { + const container = $(`#${REGION_MAP[i]}_body div.order-machine`) + for (let j = 0; j < container.children().length; j++) { + set_item_field_value( + container.find(`fieldset.order-item:eq(${j})`), + "order-field", + j, + ) } } - - function init_contentblocks() { - for(var i=0; i<contentblock_init_handlers.length; i++) - contentblock_init_handlers[i](); + } + + function order_content_types_in_regions() { + for (let i = 0; i < REGION_MAP.length; i++) { + const container = $(`#${REGION_MAP[i]}_body div.order-machine`) + container + .children() + .sort(sort_by_ordering) + .each(function () { + container.append(this) + }) } + } - function hide_form_rows_with_hidden_widgets(){ - /* This is not normally done in django -- the fields are shown + function init_contentblocks() { + for (let i = 0; i < contentblock_init_handlers.length; i++) + contentblock_init_handlers[i]() + } + + function hide_form_rows_with_hidden_widgets() { + /* This is not normally done in django -- the fields are shown with visible labels and invisible widgets, but FeinCMS used to use custom form rendering to hide rows for hidden fields. This is an attempt to preserve that behaviour. */ - $('div.feincms_inline div.form-row').each(function(){ - var child_count = $(this).find('*').length; - var invisible_types = 'div, label, input[type=hidden], p.help'; - var invisible_count = $(this).find(invisible_types).length; - if(invisible_count == child_count){ - $(this).addClass('hidden-form-row'); - } - }); - } - - function init_content_type_buttons() { - $('#main > .panel').each(function() { - var $select = $('select[name=order-machine-add-select]', this), - to_remove = []; - - for (var i=0; i<CONTENT_TYPE_BUTTONS.length; i++) { - var c = CONTENT_TYPE_BUTTONS[i], - $option = $select.find('option[value=' + c.type + ']'); - - if (!$option.length) - continue; - - var $button = $('<a href="#" class="actionbutton" />'); - - $button.addClass(c.cssclass ? c.cssclass : c.type).bind('click', (function(c) { - return function() { - var fieldset = ItemEditor.add_content_to_current(c.type); - if (c.raw_id_picker) { - var id = fieldset.find('.related-lookup, span.mediafile').attr('id'); - - if (id) { - window.open(c.raw_id_picker, - id_to_windowname(id.replace(/^lookup_/, '')), - 'height=500,width=800,resizable=yes,scrollbars=yes').focus(); - } - } - if (c.after) - c.after.call(null, fieldset); - return false; - }; - })(c)); - - $select.before($button); - - if (!c.keep) - to_remove.push($option); + $("div.feincms_inline div.form-row").each(function () { + const child_count = $(this).find("*").length + const invisible_types = "div, label, input[type=hidden], p.help" + const invisible_count = $(this).find(invisible_types).length + if (invisible_count === child_count) { + $(this).addClass("hidden-form-row") + } + }) + } + + function init_content_type_buttons() { + $("#feincmsmain > .panel").each(function () { + const $select = $("select[name=order-machine-add-select]", this), + to_remove = [] + + $select.change(() => { + const modvar = $select.val() + // bail out early if no content type selected + if (!modvar) return + + const modname = $select.find("option:selected").html() + const new_fieldset = create_new_fieldset_from_module(modvar, modname) + add_fieldset(window.ACTIVE_REGION, new_fieldset, { + where: "append", + animate: true, + }) + update_item_controls(new_fieldset, window.ACTIVE_REGION) + + $select.val("") + }) + + for (let i = 0; i < CONTENT_TYPE_BUTTONS.length; i++) { + const c = CONTENT_TYPE_BUTTONS[i], + $option = $select.find(`option[value=${c.type}]`) + + if (!$option.length) continue + + const $button = $('<a href="#" class="actionbutton" />') + $button.attr("title", CONTENT_NAMES[c.type]) + + $button.addClass(c.cssclass ? c.cssclass : c.type).bind( + "click", + ((c) => () => { + const fieldset = ItemEditor.add_content_to_current(c.type) + if (c.raw_id_picker) { + const id = fieldset + .find(".related-lookup, span.mediafile") + .attr("id") + + if (id) { + window + .open( + c.raw_id_picker, + id_to_windowname(id.replace(/^lookup_/, "")), + "height=500,width=800,resizable=yes,scrollbars=yes", + ) + .focus() + } } + if (c.after) c.after.call(null, fieldset) + return false + })(c), + ) - for (var i=0; i<to_remove.length; i++) - to_remove[i].remove(); + $select.parent().append($button) - if ($select.find('option').length == 0) { - // hide the content type select box and the add button if - // the dropdown is empty now - $select.hide().next().hide(); - } - }); - } + if (!c.keep) to_remove.push($option) + } - // global variable holding the current template key - var current_template; - - $(document).ready(function($){ - hide_form_rows_with_hidden_widgets(); - - $("#main_wrapper > .navi_tab").click(function(){ - var elem = $(this); - $("#main_wrapper > .navi_tab").removeClass("tab_active"); - elem.addClass("tab_active"); - $("#main > div:visible, #main > fieldset:visible").hide(); - - var tab_str = elem.attr("id").substr(0, elem.attr("id").length-4); - $('#'+tab_str+'_body').show(); - ACTIVE_REGION = REGION_MAP.indexOf(tab_str); - - // make it possible to open current tab on page reload - window.location.replace('#tab_'+tab_str); - }); - - // save content type selects for later use - save_content_type_selects(); - - $("input.order-machine-add-button").click(function(){ - var select_content = $(this).prev(); - var modvar = select_content.val(); - - // bail out early if no content type selected - if (!modvar) - return; - - var modname = select_content.find("option:selected").html(); - var new_fieldset = create_new_fieldset_from_module(modvar, modname); - add_fieldset(ACTIVE_REGION, new_fieldset, {where:'append', animate:true}); - update_item_controls(new_fieldset, ACTIVE_REGION); - }); - - $("h2 img.item-delete").live('click', function(){ - var popup_bg = '<div class="popup_bg"></div>'; - $("body").append(popup_bg); - var item = $(this).parents(".order-item"); - jConfirm(DELETE_MESSAGES[0], DELETE_MESSAGES[1], function(r) { - if (r==true) { - var in_database = item.find(".delete-field").length; - if(in_database==0){ // remove on client-side only - // decrement TOTAL_FORMS: - var id = item.find(".item-content > div").attr('id'); - var modvar = id.replace(/_set-\d+$/, ''); - var count = $('#id_'+modvar+'_set-TOTAL_FORMS').val(); - $('#id_'+modvar+'_set-TOTAL_FORMS').val(count-1); - // remove form: - item.find(".item-content").remove(); - - // could trigger django's deletion handler, which would - // handle reindexing other inlines, etc, but seems to - // cause errors, and is apparently unnecessary... - // django.jQuery('#'+id).find('a.inline-deletelink') - // .triggerHandler('click'); - } - else{ // saved on server, don't remove form - set_item_field_value(item,"delete-field","checked"); - } - item.fadeOut(200); - } - $(".popup_bg").remove(); - }); - }); - - $('h2 span.collapse').live('click', function(){ - var node = this; - $(this.parentNode.parentNode).children('.item-content').slideToggle(function(){ - $(node).text(feincms_gettext($(this).is(':visible') ? 'Hide' : 'Show')); - }); - return false; - }); - - current_template = $('input[name=template_key][checked], select[name=template_key]').val(); - - function on_template_key_changed(){ - var input_element = this; - var new_template = this.value; - - if(current_template==new_template) - // Selected template did not change - return false; - - var current_regions = template_regions[current_template]; - var new_regions = template_regions[new_template]; - - var not_in_new = []; - for(var i=0; i<current_regions.length; i++) - if(new_regions.indexOf(current_regions[i])==-1) - not_in_new.push(current_regions[i]); - - var popup_bg = '<div id="popup_bg"></div>'; - $("body").append(popup_bg); - - var msg = CHANGE_TEMPLATE_MESSAGES[1]; - - if(not_in_new.length) { - msg = interpolate(CHANGE_TEMPLATE_MESSAGES[2], { - 'source_regions': not_in_new, - 'target_region': new_regions[0] - }, true); - } + for (let i = 0; i < to_remove.length; i++) to_remove[i].remove() - jConfirm(msg, CHANGE_TEMPLATE_MESSAGES[0], function(ret) { - if(ret) { - for(var i=0; i<not_in_new.length; i++) { - var items = $('#'+not_in_new[i]+'_body div.order-machine').children(); - // FIXME: this moves all soon-to-be-homeless items - // to the first region, but that region is quite likely - // not in the new template. - move_item(0, items); - } - - input_element.checked = true; - - $('form').append('<input type="hidden" name="_continue" value="1" />').submit(); - } else { - $("div#popup_bg").remove(); - $(input_element).val($(input_element).data('original_value')); // Restore original value - } - }); - - return false; + if ($select.find("option").length === 0) { + // hide the content type select box and the add button if + // the dropdown is empty now + $select.hide().next().hide() + } + }) + } + + function create_tabbed(_tab_selector, _main_selector, _switch_cb) { + const tab_selector = _tab_selector, + main_selector = _main_selector, + switch_cb = _switch_cb + + $(tab_selector).addClass("clearfix") + + $(`${tab_selector} > .navi_tab`).on("click", function () { + const elem = $(this), + tab_str = elem.attr("id").substr(0, elem.attr("id").length - 4) + + if ( + elem.hasClass("tab_active") && + tab_str.indexOf("extension_option") !== -1 + ) { + elem.removeClass("tab_active") + $(`#${tab_str}_body`).hide() + } else { + $(`${tab_selector} > .navi_tab`).removeClass("tab_active") + elem.addClass("tab_active") + $( + `${main_selector} > div:visible, ${main_selector} > fieldset:visible`, + ).hide() + + $(`#${tab_str}_body`).show() + + if (switch_cb) { + switch_cb(tab_str) } + } + }) + } + + // global variable holding the current template key + let current_template + + $(document).ready(($) => { + hide_form_rows_with_hidden_widgets() + + create_tabbed("#feincmsmain_wrapper", "#feincmsmain", (tab_str) => { + window.ACTIVE_REGION = REGION_MAP.indexOf(tab_str) + // make it possible to open current tab on page reload + window.location.replace(`#tab_${tab_str}`) + }) + + /* Rearrange the options fieldsets so we can wrap them into a tab bar */ + const options_fieldsets = $("fieldset.collapse") + options_fieldsets.wrapAll('<div id="extension_options_wrapper" />') + const option_wrapper = $("#extension_options_wrapper") + const panels = [] + + options_fieldsets.each((idx, elem) => { + let option_title = $("h2", $(elem)).text() + const id_base = `extension_option_${idx}` + + const paren = option_title.indexOf(" (") + if (paren > 0) option_title = option_title.substr(0, paren) + + option_wrapper.append( + `<div class="navi_tab" id="${id_base}_tab">${option_title}</div>`, + ) + const panel = $( + `<fieldset class="module aligned" style="clear: both; display: none" id="${id_base}_body"></fieldset>`, + ) + + /* Extract the panel from each fieldset and move it to the panel area */ + const $elem = $(elem) + const details = $elem.children("details") + let divs + if (details.length === 1) { + /* dj 5.1: ModelAdmin.fieldsets and InlineModelAdmin.fieldsets now includes <details> and <summary> elements */ + divs = $(details).children("div") + } else { + /* pre dj 5.1 */ + divs = $elem.children("div") + } - // The template key's widget could either be a radio button or a drop-down select. - var template_key_radio = $('input[type=radio][name=template_key]'); - template_key_radio.click(on_template_key_changed); - var template_key_select = $('select[name=template_key]'); - template_key_select.change(on_template_key_changed); - - // Save template key's original value for easy restore if the user cancels the change. - template_key_radio.data('original_value', template_key_radio.val()); - template_key_select.data('original_value', template_key_select.val()); - - $('form').submit(function(){ - give_ordering_to_content_types(); - var form = $(this); - form.attr('action', form.attr('action')+window.location.hash); - return true; - }); - - // move contents into their corresponding regions and do some simple formatting - $("div.feincms_inline div.inline-related").each(function(){ - var elem = $(this); - if (elem.find("span.delete input").attr("checked")) { - // ignore all inlines that are set to be deleted by reversion - return; - } - - elem.find("input[name$=-region]").addClass("region-choice-field"); - elem.find("input[name$=-DELETE]").addClass("delete-field"); - elem.find("input[name$=-ordering]").addClass("order-field"); - - if (!elem.hasClass("empty-form")){ - var region_id = REGION_MAP.indexOf( - elem.find(".region-choice-field").val()); - if (REGION_MAP[region_id] != undefined) { - var content_type = elem.attr("id").substr( - 0, elem.attr("id").indexOf("_")); - var item = create_new_item_from_form( - elem, CONTENT_NAMES[content_type], content_type); - add_fieldset(region_id, item, {where:'append'}); - update_item_controls(item, region_id); - } - } - }); - // register regions as sortable for drag N drop - $(".order-machine").sortable({ - handle: '.handle', - helper: function(event, ui){ - var h2 = $("<h2>").html($(ui.item).find('span.modname').html()); - return $("<fieldset>").addClass("helper module").append(h2); - }, - placeholder: 'highlight', - start: function(event, ui) { - poorify_rich($(ui.item)); - }, - stop: function(event, ui) { - richify_poor($(ui.item)); - } - }); - - order_content_types_in_regions(); - - // hide now-empty formsets - $('div.feincms_inline').hide(); - - // add quick buttons to order machine control - init_content_type_buttons(); - - // DRY object-tools addition - $(".extra-object-tools li").appendTo("ul.object-tools"); - $(".extra-object-tools").remove(); - - /* handle Cmd-S and Cmd-Shift-S as save-and-continue and save respectively */ - $(document.documentElement).keydown(function(event) { - if(event.which == 83 && event.metaKey) { - sel = event.shiftKey ? 'form:first input[name=_continue]' : - 'form:first input[name=_save]'; - $(sel).click(); - return false; - } - }); - - - var errors = $('#main div.errors'); - - if(errors.length) { - var id = errors.parents('fieldset[id$=_body], div[id$=_body]').attr('id'); - $('#'+id.replace('_body', '_tab')).trigger('click'); + panel.append(divs) + $elem.remove() // Remove the rest + panels.push(panel) + }) + + option_wrapper.append('<div id="extension_options" />') + $("#extension_options").append(panels) + + create_tabbed("#extension_options_wrapper", "#extension_options") + /* Done morphing extension options into tabs */ + + // save content type selects for later use + save_content_type_selects() + + $(document.body).on("click", "h2 img.item-delete", function () { + const item = $(this).parents(".order-item") + if (confirm(feincms_gettext("DELETE_MESSAGE"))) { + const in_database = item.find(".delete-field").length + if (in_database === 0) { + // remove on client-side only + const id = item.find(".item-content > div").attr("id") + + // poorify all contents + const items = item.parents(".order-machine").find(".order-item") + items.each(function () { + poorify_rich($(this)) + }) + + // remove content + django + .jQuery(`#${id}`) + .find("a.inline-deletelink") + .triggerHandler("click") + + // richify all contents again + items.each(function () { + richify_poor($(this)) + }) } else { - if(window.location.hash) { - var tab = $('#'+window.location.hash.substr(5)+'_tab'); + // saved on server, don't remove form + set_item_field_value(item, "delete-field", "checked") + } + item.fadeOut(200, () => { + const region_item = $(`#${REGION_MAP[window.ACTIVE_REGION]}_body`) + if ( + region_item.children("div.order-machine").children(":visible") + .length === 0 + ) { + region_item.children("div.empty-machine-msg").show() + } + }) + } + }) + + current_template = $( + "input[name=template_key][checked], select[name=template_key]", + ).val() + + function on_template_key_changed() { + const new_template = this.value + const form_element = $(this).parents("form") + + if (current_template === new_template) + // Selected template did not change + return false + + const current_regions = template_regions[current_template] + const new_regions = template_regions[new_template] + + const not_in_new = [] + for (let i = 0; i < current_regions.length; i++) + if (new_regions.indexOf(current_regions[i]) === -1) + not_in_new.push(current_regions[i]) + + let msg = feincms_gettext("CHANGE_TEMPLATE") + + if (not_in_new.length) { + msg = interpolate( + feincms_gettext("CHANGE_TEMPLATE_WITH_MOVE"), + { + source_regions: not_in_new, + target_region: new_regions[0], + }, + true, + ) + } - if(tab.length) { - tab.trigger('click'); - return; - } - } + if (confirm(msg)) { + for (let i = 0; i < not_in_new.length; i++) { + const body = $(`#${not_in_new[i]}_body`), + machine = body.find(".order-machine"), + inputs = machine.find("input[name$=region]") - $('#main_wrapper>div.navi_tab:first-child').trigger('click'); + inputs.val(new_regions[0]) } - }); - $(window).load(function(){init_contentblocks()}); + this.checked = true + + form_element.append( + '<input type="hidden" name="_continue" value="1" />', + ) + /* Simulate a click on the save button instead of form.submit(), so + that the submit handlers from FilteredSelectMultiple get + invoked. See Issue #372 */ + form_element.find("[type=submit][name=_save]").click() + } else { + // Restore original value + form_element.val($(this).data("original_value")) + } - // externally accessible helpers - window.ItemEditor = { - add_content: function(type, region) { - var new_fieldset = create_new_fieldset_from_module(type, CONTENT_NAMES[type]); - add_fieldset(region, new_fieldset, {where: 'append', animate: 'true'}); - update_item_controls(new_fieldset, region); - return new_fieldset; - }, + return false + } + + // The template key's widget could either be a radio button or a drop-down select. + const template_key_radio = $("input[type=radio][name=template_key]") + template_key_radio.click(on_template_key_changed) + const template_key_select = $("select[name=template_key]") + template_key_select.change(on_template_key_changed) + + // Save template key's original value for easy restore if the user cancels the change. + template_key_radio.data("original_value", template_key_radio.val()) + template_key_select.data("original_value", template_key_select.val()) + + $("form").submit(function () { + give_ordering_to_content_types() + const form = $(this) + const action = form.attr("action") || "" + form.attr("action", action + window.location.hash) + return true + }) + + // move contents into their corresponding regions and do some simple formatting + $("div.feincms_inline div.inline-related").each(function () { + const elem = $(this) + if (elem.find("span.delete input").attr("checked")) { + // ignore all inlines that are set to be deleted by reversion + return + } - add_content_to_current: function(type) { - return ItemEditor.add_content(type, ACTIVE_REGION); + elem.find("input[name$=-region]").addClass("region-choice-field") + elem.find("input[name$=-DELETE]").addClass("delete-field") + elem.find("input[name$=-ordering]").addClass("order-field") + + if (!elem.hasClass("empty-form")) { + const region_id = REGION_MAP.indexOf( + elem.find(".region-choice-field").val(), + ) + if (REGION_MAP[region_id] !== undefined) { + const content_type = elem + .attr("id") + .substr(0, elem.attr("id").lastIndexOf("_")) + const item = create_new_item_from_form( + elem, + CONTENT_NAMES[content_type], + content_type, + ) + add_fieldset(region_id, item, { where: "append" }) + update_item_controls(item, region_id) } - }; + } + }) + // register regions as sortable for drag N drop + $(".order-machine").sortable({ + handle: ".handle", + helper(_event, ui) { + const h2 = $("<h2>").html($(ui).find("span.modname").html()) + return $("<fieldset>").addClass("helper module").append(h2) + }, + placeholder: "highlight", + start(_event, ui) { + poorify_rich($(ui.item)) + }, + stop(_event, ui) { + richify_poor($(ui.item)) + }, + }) + + order_content_types_in_regions() + + // hide now-empty formsets + $("div.feincms_inline").hide() + + // add quick buttons to order machine control + init_content_type_buttons() + + // DRY object-tools addition + $(".extra-object-tools li").appendTo("ul.object-tools") + $(".extra-object-tools").remove() + + /* handle Cmd-S and Cmd-Shift-S as save-and-continue and save respectively */ + $(document.documentElement).keydown((event) => { + if (event.which === 83 && event.metaKey) { + const sel = event.shiftKey + ? "form:first input[name=_continue]" + : "form:first input[name=_save]" + $(sel).click() + return false + } + }) + + const errors = $("#feincmsmain div.errors") + + if (errors.length) { + const id = errors + .parents("fieldset[id$=_body], div[id$=_body]") + .attr("id") + $(`#${id.replace("_body", "_tab")}`).trigger("click") + } else { + if (window.location.hash) { + const tab = $(`#${window.location.hash.substr(5)}_tab`) + + if (tab.length) { + tab.trigger("click") + return + } + } -})(feincms.jQuery); + $("#feincmsmain_wrapper>div.navi_tab:first-child").trigger("click") + } + }) + + $(window).load(init_contentblocks) + + // externally accessible helpers + window.ItemEditor = { + add_content(type, region) { + const new_fieldset = create_new_fieldset_from_module( + type, + CONTENT_NAMES[type], + ) + add_fieldset(region, new_fieldset, { where: "append", animate: "true" }) + update_item_controls(new_fieldset, region) + return new_fieldset + }, + + add_content_to_current(type) { + return ItemEditor.add_content(type, window.ACTIVE_REGION) + }, + } +})(feincms.jQuery) diff --git a/feincms/static/feincms/jquery-1.11.3.min.js b/feincms/static/feincms/jquery-1.11.3.min.js new file mode 100644 index 000000000..0f60b7bd0 --- /dev/null +++ b/feincms/static/feincms/jquery-1.11.3.min.js @@ -0,0 +1,5 @@ +/*! jQuery v1.11.3 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.3",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b="length"in a&&a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\f]' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function qa(){}qa.prototype=d.filters=d.pseudos,d.setFilters=new qa,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function ra(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1; + +return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function aa(){return!0}function ba(){return!1}function ca(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},fix:function(a){if(a[m.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=Z.test(e)?this.mouseHooks:Y.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new m.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=f.srcElement||y),3===a.target.nodeType&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,g.filter?g.filter(a,f):a},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button,g=b.fromElement;return null==a.pageX&&null!=b.clientX&&(d=a.target.ownerDocument||y,e=d.documentElement,c=d.body,a.pageX=b.clientX+(e&&e.scrollLeft||c&&c.scrollLeft||0)-(e&&e.clientLeft||c&&c.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||c&&c.scrollTop||0)-(e&&e.clientTop||c&&c.clientTop||0)),!a.relatedTarget&&g&&(a.relatedTarget=g===a.target?b.toElement:g),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==ca()&&this.focus)try{return this.focus(),!1}catch(a){}},delegateType:"focusin"},blur:{trigger:function(){return this===ca()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return m.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):void 0},_default:function(a){return m.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=m.extend(new m.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?m.event.trigger(e,null,b):m.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},m.removeEvent=y.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]===K&&(a[d]=null),a.detachEvent(d,c))},m.Event=function(a,b){return this instanceof m.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?aa:ba):this.type=a,b&&m.extend(this,b),this.timeStamp=a&&a.timeStamp||m.now(),void(this[m.expando]=!0)):new m.Event(a,b)},m.Event.prototype={isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=aa,a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=aa,a&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=aa,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},m.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){m.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!m.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.submitBubbles||(m.event.special.submit={setup:function(){return m.nodeName(this,"form")?!1:void m.event.add(this,"click._submit keypress._submit",function(a){var b=a.target,c=m.nodeName(b,"input")||m.nodeName(b,"button")?b.form:void 0;c&&!m._data(c,"submitBubbles")&&(m.event.add(c,"submit._submit",function(a){a._submit_bubble=!0}),m._data(c,"submitBubbles",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&m.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){return m.nodeName(this,"form")?!1:void m.event.remove(this,"._submit")}}),k.changeBubbles||(m.event.special.change={setup:function(){return X.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(m.event.add(this,"propertychange._change",function(a){"checked"===a.originalEvent.propertyName&&(this._just_changed=!0)}),m.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),m.event.simulate("change",this,a,!0)})),!1):void m.event.add(this,"beforeactivate._change",function(a){var b=a.target;X.test(b.nodeName)&&!m._data(b,"changeBubbles")&&(m.event.add(b,"change._change",function(a){!this.parentNode||a.isSimulated||a.isTrigger||m.event.simulate("change",this.parentNode,a,!0)}),m._data(b,"changeBubbles",!0))})},handle:function(a){var b=a.target;return this!==b||a.isSimulated||a.isTrigger||"radio"!==b.type&&"checkbox"!==b.type?a.handleObj.handler.apply(this,arguments):void 0},teardown:function(){return m.event.remove(this,"._change"),!X.test(this.nodeName)}}),k.focusinBubbles||m.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){m.event.simulate(b,a.target,m.event.fix(a),!0)};m.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=m._data(d,b);e||d.addEventListener(a,c,!0),m._data(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=m._data(d,b)-1;e?m._data(d,b,e):(d.removeEventListener(a,c,!0),m._removeData(d,b))}}}),m.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(f in a)this.on(f,b,c,a[f],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=ba;else if(!d)return this;return 1===e&&(g=d,d=function(a){return m().off(a),g.apply(this,arguments)},d.guid=g.guid||(g.guid=m.guid++)),this.each(function(){m.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,m(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=ba),this.each(function(){m.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){m.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?m.event.trigger(a,b,c,!0):void 0}});function da(a){var b=ea.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}var ea="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",fa=/ jQuery\d+="(?:null|\d+)"/g,ga=new RegExp("<(?:"+ea+")[\\s/>]","i"),ha=/^\s+/,ia=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ja=/<([\w:]+)/,ka=/<tbody/i,la=/<|&#?\w+;/,ma=/<(?:script|style|link)/i,na=/checked\s*(?:[^=]|=\s*.checked.)/i,oa=/^$|\/(?:java|ecma)script/i,pa=/^true\/(.*)/,qa=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ra={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:k.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},sa=da(y),ta=sa.appendChild(y.createElement("div"));ra.optgroup=ra.option,ra.tbody=ra.tfoot=ra.colgroup=ra.caption=ra.thead,ra.th=ra.td;function ua(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ua(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function va(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wa(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xa(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function ya(a){var b=pa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function za(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Aa(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Ba(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xa(b).text=a.text,ya(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!ga.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ta.innerHTML=a.outerHTML,ta.removeChild(f=ta.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ua(f),h=ua(a),g=0;null!=(e=h[g]);++g)d[g]&&Ba(e,d[g]);if(b)if(c)for(h=h||ua(a),d=d||ua(f),g=0;null!=(e=h[g]);g++)Aa(e,d[g]);else Aa(a,f);return d=ua(f,"script"),d.length>0&&za(d,!i&&ua(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=da(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(la.test(f)){h=h||o.appendChild(b.createElement("div")),i=(ja.exec(f)||["",""])[1].toLowerCase(),l=ra[i]||ra._default,h.innerHTML=l[1]+f.replace(ia,"<$1></$2>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&ha.test(f)&&p.push(b.createTextNode(ha.exec(f)[0])),!k.tbody){f="table"!==i||ka.test(f)?"<table>"!==l[1]||ka.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ua(p,"input"),va),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ua(o.appendChild(f),"script"),g&&za(h),c)){e=0;while(f=h[e++])oa.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ua(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&za(ua(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ua(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fa,""):void 0;if(!("string"!=typeof a||ma.test(a)||!k.htmlSerialize&&ga.test(a)||!k.leadingWhitespace&&ha.test(a)||ra[(ja.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ia,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ua(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ua(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&na.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ua(i,"script"),xa),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ua(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,ya),j=0;f>j;j++)d=g[j],oa.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qa,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Ca,Da={};function Ea(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fa(a){var b=y,c=Da[a];return c||(c=Ea(a,b),"none"!==c&&c||(Ca=(Ca||m("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=(Ca[0].contentWindow||Ca[0].contentDocument).document,b.write(),b.close(),c=Ea(a,b),Ca.detach()),Da[a]=c),c}!function(){var a;k.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,d;return c=y.getElementsByTagName("body")[0],c&&c.style?(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(y.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(d),a):void 0}}();var Ga=/^margin/,Ha=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ia,Ja,Ka=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ia=function(b){return b.ownerDocument.defaultView.opener?b.ownerDocument.defaultView.getComputedStyle(b,null):a.getComputedStyle(b,null)},Ja=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ia(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||m.contains(a.ownerDocument,a)||(g=m.style(a,b)),Ha.test(g)&&Ga.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):y.documentElement.currentStyle&&(Ia=function(a){return a.currentStyle},Ja=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ia(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Ha.test(g)&&!Ka.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function La(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h;if(b=y.createElement("div"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=d&&d.style){c.cssText="float:left;opacity:.5",k.opacity="0.5"===c.opacity,k.cssFloat=!!c.cssFloat,b.style.backgroundClip="content-box",b.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===b.style.backgroundClip,k.boxSizing=""===c.boxSizing||""===c.MozBoxSizing||""===c.WebkitBoxSizing,m.extend(k,{reliableHiddenOffsets:function(){return null==g&&i(),g},boxSizingReliable:function(){return null==f&&i(),f},pixelPosition:function(){return null==e&&i(),e},reliableMarginRight:function(){return null==h&&i(),h}});function i(){var b,c,d,i;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),b.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",e=f=!1,h=!0,a.getComputedStyle&&(e="1%"!==(a.getComputedStyle(b,null)||{}).top,f="4px"===(a.getComputedStyle(b,null)||{width:"4px"}).width,i=b.appendChild(y.createElement("div")),i.style.cssText=b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",b.style.width="1px",h=!parseFloat((a.getComputedStyle(i,null)||{}).marginRight),b.removeChild(i)),b.innerHTML="<table><tr><td></td><td>t</td></tr></table>",i=b.getElementsByTagName("td"),i[0].style.cssText="margin:0;border:0;padding:0;display:none",g=0===i[0].offsetHeight,g&&(i[0].style.display="",i[1].style.display="none",g=0===i[0].offsetHeight),c.removeChild(d))}}}(),m.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Ma=/alpha\([^)]*\)/i,Na=/opacity\s*=\s*([^)]*)/,Oa=/^(none|table(?!-c[ea]).+)/,Pa=new RegExp("^("+S+")(.*)$","i"),Qa=new RegExp("^([+-])=("+S+")","i"),Ra={position:"absolute",visibility:"hidden",display:"block"},Sa={letterSpacing:"0",fontWeight:"400"},Ta=["Webkit","O","Moz","ms"];function Ua(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Ta.length;while(e--)if(b=Ta[e]+c,b in a)return b;return d}function Va(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=m._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&U(d)&&(f[g]=m._data(d,"olddisplay",Fa(d.nodeName)))):(e=U(d),(c&&"none"!==c||!e)&&m._data(d,"olddisplay",e?c:m.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Wa(a,b,c){var d=Pa.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Xa(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=m.css(a,c+T[f],!0,e)),d?("content"===c&&(g-=m.css(a,"padding"+T[f],!0,e)),"margin"!==c&&(g-=m.css(a,"border"+T[f]+"Width",!0,e))):(g+=m.css(a,"padding"+T[f],!0,e),"padding"!==c&&(g+=m.css(a,"border"+T[f]+"Width",!0,e)));return g}function Ya(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ia(a),g=k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Ja(a,b,f),(0>e||null==e)&&(e=a.style[b]),Ha.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Xa(a,b,c||(g?"border":"content"),d,f)+"px"}m.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Ja(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":k.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=m.camelCase(b),i=a.style;if(b=m.cssProps[h]||(m.cssProps[h]=Ua(i,h)),g=m.cssHooks[b]||m.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Qa.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(m.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||m.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=m.camelCase(b);return b=m.cssProps[h]||(m.cssProps[h]=Ua(a.style,h)),g=m.cssHooks[b]||m.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Ja(a,b,d)),"normal"===f&&b in Sa&&(f=Sa[b]),""===c||c?(e=parseFloat(f),c===!0||m.isNumeric(e)?e||0:f):f}}),m.each(["height","width"],function(a,b){m.cssHooks[b]={get:function(a,c,d){return c?Oa.test(m.css(a,"display"))&&0===a.offsetWidth?m.swap(a,Ra,function(){return Ya(a,b,d)}):Ya(a,b,d):void 0},set:function(a,c,d){var e=d&&Ia(a);return Wa(a,c,d?Xa(a,b,d,k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,e),e):0)}}}),k.opacity||(m.cssHooks.opacity={get:function(a,b){return Na.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=m.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===m.trim(f.replace(Ma,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Ma.test(f)?f.replace(Ma,e):f+" "+e)}}),m.cssHooks.marginRight=La(k.reliableMarginRight,function(a,b){return b?m.swap(a,{display:"inline-block"},Ja,[a,"marginRight"]):void 0}),m.each({margin:"",padding:"",border:"Width"},function(a,b){m.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+T[d]+b]=f[d]||f[d-2]||f[0];return e}},Ga.test(a)||(m.cssHooks[a+b].set=Wa)}),m.fn.extend({css:function(a,b){return V(this,function(a,b,c){var d,e,f={},g=0;if(m.isArray(b)){for(d=Ia(a),e=b.length;e>g;g++)f[b[g]]=m.css(a,b[g],!1,d);return f}return void 0!==c?m.style(a,b,c):m.css(a,b)},a,b,arguments.length>1)},show:function(){return Va(this,!0)},hide:function(){return Va(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){U(this)?m(this).show():m(this).hide()})}});function Za(a,b,c,d,e){ +return new Za.prototype.init(a,b,c,d,e)}m.Tween=Za,Za.prototype={constructor:Za,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")},cur:function(){var a=Za.propHooks[this.prop];return a&&a.get?a.get(this):Za.propHooks._default.get(this)},run:function(a){var b,c=Za.propHooks[this.prop];return this.options.duration?this.pos=b=m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Za.propHooks._default.set(this),this}},Za.prototype.init.prototype=Za.prototype,Za.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Za.propHooks.scrollTop=Za.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Za.prototype.init,m.fx.step={};var $a,_a,ab=/^(?:toggle|show|hide)$/,bb=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cb=/queueHooks$/,db=[ib],eb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bb.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bb.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fb(){return setTimeout(function(){$a=void 0}),$a=m.now()}function gb(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hb(a,b,c){for(var d,e=(eb[b]||[]).concat(eb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ib(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fa(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fa(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ab.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fa(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hb(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jb(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kb(a,b,c){var d,e,f=0,g=db.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$a||fb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$a||fb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jb(k,j.opts.specialEasing);g>f;f++)if(d=db[f].call(j,a,k,j.opts))return d;return m.map(k,hb,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kb,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],eb[c]=eb[c]||[],eb[c].unshift(b)},prefilter:function(a,b){b?db.unshift(a):db.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in m.fx.speeds?m.fx.speeds[d.duration]:m.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){m.isFunction(d.old)&&d.old.call(this),d.queue&&m.dequeue(this,d.queue)},d},m.fn.extend({fadeTo:function(a,b,c,d){return this.filter(U).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=m.isEmptyObject(a),f=m.speed(b,c,d),g=function(){var b=kb(this,m.extend({},a),f);(e||m._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=m.timers,g=m._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&cb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&m.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=m._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=m.timers,g=d?d.length:0;for(c.finish=!0,m.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),m.each(["toggle","show","hide"],function(a,b){var c=m.fn[b];m.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gb(b,!0),a,d,e)}}),m.each({slideDown:gb("show"),slideUp:gb("hide"),slideToggle:gb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){m.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),m.timers=[],m.fx.tick=function(){var a,b=m.timers,c=0;for($a=m.now();c<b.length;c++)a=b[c],a()||b[c]!==a||b.splice(c--,1);b.length||m.fx.stop(),$a=void 0},m.fx.timer=function(a){m.timers.push(a),a()?m.fx.start():m.timers.pop()},m.fx.interval=13,m.fx.start=function(){_a||(_a=setInterval(m.fx.tick,m.fx.interval))},m.fx.stop=function(){clearInterval(_a),_a=null},m.fx.speeds={slow:600,fast:200,_default:400},m.fn.delay=function(a,b){return a=m.fx?m.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a,b,c,d,e;b=y.createElement("div"),b.setAttribute("className","t"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=y.createElement("select"),e=c.appendChild(y.createElement("option")),a=b.getElementsByTagName("input")[0],d.style.cssText="top:1px",k.getSetAttribute="t"!==b.className,k.style=/top/.test(d.getAttribute("style")),k.hrefNormalized="/a"===d.getAttribute("href"),k.checkOn=!!a.value,k.optSelected=e.selected,k.enctype=!!y.createElement("form").enctype,c.disabled=!0,k.optDisabled=!e.disabled,a=y.createElement("input"),a.setAttribute("value",""),k.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),k.radioValue="t"===a.value}();var lb=/\r/g;m.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=m.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,m(this).val()):a,null==e?e="":"number"==typeof e?e+="":m.isArray(e)&&(e=m.map(e,function(a){return null==a?"":a+""})),b=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=m.valHooks[e.type]||m.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(lb,""):null==c?"":c)}}}),m.extend({valHooks:{option:{get:function(a){var b=m.find.attr(a,"value");return null!=b?b:m.trim(m.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&m.nodeName(c.parentNode,"optgroup"))){if(b=m(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=m.makeArray(b),g=e.length;while(g--)if(d=e[g],m.inArray(m.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(a,b){return m.isArray(b)?a.checked=m.inArray(m(a).val(),b)>=0:void 0}},k.checkOn||(m.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var mb,nb,ob=m.expr.attrHandle,pb=/^(?:checked|selected)$/i,qb=k.getSetAttribute,rb=k.input;m.fn.extend({attr:function(a,b){return V(this,m.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){m.removeAttr(this,a)})}}),m.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===K?m.prop(a,b,c):(1===f&&m.isXMLDoc(a)||(b=b.toLowerCase(),d=m.attrHooks[b]||(m.expr.match.bool.test(b)?nb:mb)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=m.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void m.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=m.propFix[c]||c,m.expr.match.bool.test(c)?rb&&qb||!pb.test(c)?a[d]=!1:a[m.camelCase("default-"+c)]=a[d]=!1:m.attr(a,c,""),a.removeAttribute(qb?c:d)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&m.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),nb={set:function(a,b,c){return b===!1?m.removeAttr(a,c):rb&&qb||!pb.test(c)?a.setAttribute(!qb&&m.propFix[c]||c,c):a[m.camelCase("default-"+c)]=a[c]=!0,c}},m.each(m.expr.match.bool.source.match(/\w+/g),function(a,b){var c=ob[b]||m.find.attr;ob[b]=rb&&qb||!pb.test(b)?function(a,b,d){var e,f;return d||(f=ob[b],ob[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,ob[b]=f),e}:function(a,b,c){return c?void 0:a[m.camelCase("default-"+b)]?b.toLowerCase():null}}),rb&&qb||(m.attrHooks.value={set:function(a,b,c){return m.nodeName(a,"input")?void(a.defaultValue=b):mb&&mb.set(a,b,c)}}),qb||(mb={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},ob.id=ob.name=ob.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},m.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:mb.set},m.attrHooks.contenteditable={set:function(a,b,c){mb.set(a,""===b?!1:b,c)}},m.each(["width","height"],function(a,b){m.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),k.style||(m.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var sb=/^(?:input|select|textarea|button|object)$/i,tb=/^(?:a|area)$/i;m.fn.extend({prop:function(a,b){return V(this,m.prop,a,b,arguments.length>1)},removeProp:function(a){return a=m.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),m.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!m.isXMLDoc(a),f&&(b=m.propFix[b]||b,e=m.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=m.find.attr(a,"tabindex");return b?parseInt(b,10):sb.test(a.nodeName)||tb.test(a.nodeName)&&a.href?0:-1}}}}),k.hrefNormalized||m.each(["href","src"],function(a,b){m.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),k.optSelected||(m.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),k.enctype||(m.propFix.enctype="encoding");var ub=/[\t\r\n\f]/g;m.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ub," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ub," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?m.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(m.isFunction(a)?function(c){m(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=m(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===K||"boolean"===c)&&(this.className&&m._data(this,"__className__",this.className),this.className=this.className||a===!1?"":m._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ub," ").indexOf(b)>=0)return!0;return!1}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){m.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),m.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var vb=m.now(),wb=/\?/,xb=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;m.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=m.trim(b+"");return e&&!m.trim(e.replace(xb,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():m.error("Invalid JSON: "+b)},m.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+b),c};var yb,zb,Ab=/#.*$/,Bb=/([?&])_=[^&]*/,Cb=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Db=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Eb=/^(?:GET|HEAD)$/,Fb=/^\/\//,Gb=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hb={},Ib={},Jb="*/".concat("*");try{zb=location.href}catch(Kb){zb=y.createElement("a"),zb.href="",zb=zb.href}yb=Gb.exec(zb.toLowerCase())||[];function Lb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(m.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Mb(a,b,c,d){var e={},f=a===Ib;function g(h){var i;return e[h]=!0,m.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Nb(a,b){var c,d,e=m.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&m.extend(!0,a,c),a}function Ob(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Pb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:zb,type:"GET",isLocal:Db.test(yb[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Jb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Nb(Nb(a,m.ajaxSettings),b):Nb(m.ajaxSettings,a)},ajaxPrefilter:Lb(Hb),ajaxTransport:Lb(Ib),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=m.ajaxSetup({},b),l=k.context||k,n=k.context&&(l.nodeType||l.jquery)?m(l):m.event,o=m.Deferred(),p=m.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Cb.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||zb)+"").replace(Ab,"").replace(Fb,yb[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=m.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(c=Gb.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===yb[1]&&c[2]===yb[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(yb[3]||("http:"===yb[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=m.param(k.data,k.traditional)),Mb(Hb,k,b,v),2===t)return v;h=m.event&&k.global,h&&0===m.active++&&m.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Eb.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(wb.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Bb.test(e)?e.replace(Bb,"$1_="+vb++):e+(wb.test(e)?"&":"?")+"_="+vb++)),k.ifModified&&(m.lastModified[e]&&v.setRequestHeader("If-Modified-Since",m.lastModified[e]),m.etag[e]&&v.setRequestHeader("If-None-Match",m.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Jb+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Mb(Ib,k,b,v)){v.readyState=1,h&&n.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Ob(k,v,c)),u=Pb(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(m.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(m.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&n.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(n.trigger("ajaxComplete",[v,k]),--m.active||m.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return m.get(a,b,c,"json")},getScript:function(a,b){return m.get(a,void 0,b,"script")}}),m.each(["get","post"],function(a,b){m[b]=function(a,c,d,e){return m.isFunction(c)&&(e=e||d,d=c,c=void 0),m.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),m._evalUrl=function(a){return m.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},m.fn.extend({wrapAll:function(a){if(m.isFunction(a))return this.each(function(b){m(this).wrapAll(a.call(this,b))});if(this[0]){var b=m(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(m.isFunction(a)?function(b){m(this).wrapInner(a.call(this,b))}:function(){var b=m(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=m.isFunction(a);return this.each(function(c){m(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!k.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||m.css(a,"display"))},m.expr.filters.visible=function(a){return!m.expr.filters.hidden(a)};var Qb=/%20/g,Rb=/\[\]$/,Sb=/\r?\n/g,Tb=/^(?:submit|button|image|reset|file)$/i,Ub=/^(?:input|select|textarea|keygen)/i;function Vb(a,b,c,d){var e;if(m.isArray(b))m.each(b,function(b,e){c||Rb.test(a)?d(a,e):Vb(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==m.type(b))d(a,b);else for(e in b)Vb(a+"["+e+"]",b[e],c,d)}m.param=function(a,b){var c,d=[],e=function(a,b){b=m.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(a)||a.jquery&&!m.isPlainObject(a))m.each(a,function(){e(this.name,this.value)});else for(c in a)Vb(c,a[c],b,e);return d.join("&").replace(Qb,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=m.prop(this,"elements");return a?m.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!m(this).is(":disabled")&&Ub.test(this.nodeName)&&!Tb.test(a)&&(this.checked||!W.test(a))}).map(function(a,b){var c=m(this).val();return null==c?null:m.isArray(c)?m.map(c,function(a){return{name:b.name,value:a.replace(Sb,"\r\n")}}):{name:b.name,value:c.replace(Sb,"\r\n")}}).get()}}),m.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zb()||$b()}:Zb;var Wb=0,Xb={},Yb=m.ajaxSettings.xhr();a.attachEvent&&a.attachEvent("onunload",function(){for(var a in Xb)Xb[a](void 0,!0)}),k.cors=!!Yb&&"withCredentials"in Yb,Yb=k.ajax=!!Yb,Yb&&m.ajaxTransport(function(a){if(!a.crossDomain||k.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Wb;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Xb[g],b=void 0,f.onreadystatechange=m.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Xb[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function Zb(){try{return new a.XMLHttpRequest}catch(b){}}function $b(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return m.globalEval(a),a}}}),m.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),m.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=y.head||m("head")[0]||y.documentElement;return{send:function(d,e){b=y.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var _b=[],ac=/(=)\?(?=&|$)|\?\?/;m.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=_b.pop()||m.expando+"_"+vb++;return this[a]=!0,a}}),m.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(ac.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&ac.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=m.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(ac,"$1"+e):b.jsonp!==!1&&(b.url+=(wb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||m.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,_b.push(e)),g&&m.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),m.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||y;var d=u.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=m.buildFragment([a],b,e),e&&e.length&&m(e).remove(),m.merge([],d.childNodes))};var bc=m.fn.load;m.fn.load=function(a,b,c){if("string"!=typeof a&&bc)return bc.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=m.trim(a.slice(h,a.length)),a=a.slice(0,h)),m.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&m.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?m("<div>").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cc=a.document.documentElement;function dc(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dc(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cc;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cc})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dc(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=La(k.pixelPosition,function(a,c){return c?(c=Ja(a,b),Ha.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ec=a.jQuery,fc=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fc),b&&a.jQuery===m&&(a.jQuery=ec),m},typeof b===K&&(a.jQuery=a.$=m),m}); diff --git a/feincms/static/feincms/jquery-1.5.1.js b/feincms/static/feincms/jquery-1.5.1.js deleted file mode 100644 index 78fcfa469..000000000 --- a/feincms/static/feincms/jquery-1.5.1.js +++ /dev/null @@ -1,8316 +0,0 @@ -/*! - * jQuery JavaScript Library v1.5.1 - * http://jquery.com/ - * - * Copyright 2011, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Wed Feb 23 13:55:29 2011 -0500 - */ -(function( window, undefined ) { - -// Use the correct document accordingly with window argument (sandbox) -var document = window.document; -var jQuery = (function() { - -// Define a local copy of jQuery -var jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context, rootjQuery ); - }, - - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - - // Map over the $ in case of overwrite - _$ = window.$, - - // A central reference to the root jQuery(document) - rootjQuery, - - // A simple way to check for HTML strings or ID strings - // (both of which we optimize for) - quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/, - - // Check if a string has a non-whitespace character in it - rnotwhite = /\S/, - - // Used for trimming whitespace - trimLeft = /^\s+/, - trimRight = /\s+$/, - - // Check for digits - rdigit = /\d/, - - // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, - - // JSON RegExp - rvalidchars = /^[\],:{}\s]*$/, - rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, - rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, - rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, - - // Useragent RegExp - rwebkit = /(webkit)[ \/]([\w.]+)/, - ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, - rmsie = /(msie) ([\w.]+)/, - rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, - - // Keep a UserAgent string for use with jQuery.browser - userAgent = navigator.userAgent, - - // For matching the engine and version of the browser - browserMatch, - - // Has the ready events already been bound? - readyBound = false, - - // The deferred used on DOM ready - readyList, - - // Promise methods - promiseMethods = "then done fail isResolved isRejected promise".split( " " ), - - // The ready event handler - DOMContentLoaded, - - // Save a reference to some core methods - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - push = Array.prototype.push, - slice = Array.prototype.slice, - trim = String.prototype.trim, - indexOf = Array.prototype.indexOf, - - // [[Class]] -> type pairs - class2type = {}; - -jQuery.fn = jQuery.prototype = { - constructor: jQuery, - init: function( selector, context, rootjQuery ) { - var match, elem, ret, doc; - - // Handle $(""), $(null), or $(undefined) - if ( !selector ) { - return this; - } - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - } - - // The body element only exists once, optimize finding it - if ( selector === "body" && !context && document.body ) { - this.context = document; - this[0] = document.body; - this.selector = "body"; - this.length = 1; - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - // Are we dealing with HTML string or an ID? - match = quickExpr.exec( selector ); - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - context = context instanceof jQuery ? context[0] : context; - doc = (context ? context.ownerDocument || context : document); - - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - ret = rsingleTag.exec( selector ); - - if ( ret ) { - if ( jQuery.isPlainObject( context ) ) { - selector = [ document.createElement( ret[1] ) ]; - jQuery.fn.attr.call( selector, context, true ); - - } else { - selector = [ doc.createElement( ret[1] ) ]; - } - - } else { - ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); - selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; - } - - return jQuery.merge( this, selector ); - - // HANDLE: $("#id") - } else { - elem = document.getElementById( match[2] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id !== match[2] ) { - return rootjQuery.find( selector ); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return (context || rootjQuery).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return rootjQuery.ready( selector ); - } - - if (selector.selector !== undefined) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }, - - // Start with an empty selector - selector: "", - - // The current version of jQuery being used - jquery: "1.5.1", - - // The default length of a jQuery object is 0 - length: 0, - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - toArray: function() { - return slice.call( this, 0 ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == null ? - - // Return a 'clean' array - this.toArray() : - - // Return just the object - ( num < 0 ? this[ this.length + num ] : this[ num ] ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems, name, selector ) { - // Build a new jQuery matched element set - var ret = this.constructor(); - - if ( jQuery.isArray( elems ) ) { - push.apply( ret, elems ); - - } else { - jQuery.merge( ret, elems ); - } - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - ret.context = this.context; - - if ( name === "find" ) { - ret.selector = this.selector + (this.selector ? " " : "") + selector; - } else if ( name ) { - ret.selector = this.selector + "." + name + "(" + selector + ")"; - } - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - ready: function( fn ) { - // Attach the listeners - jQuery.bindReady(); - - // Add the callback - readyList.done( fn ); - - return this; - }, - - eq: function( i ) { - return i === -1 ? - this.slice( i ) : - this.slice( i, +i + 1 ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ), - "slice", slice.call(arguments).join(",") ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function( elem, i ) { - return callback.call( elem, i, elem ); - })); - }, - - end: function() { - return this.prevObject || this.constructor(null); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: [].sort, - splice: [].splice -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if ( length === i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) { - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) { - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend({ - noConflict: function( deep ) { - window.$ = _$; - - if ( deep ) { - window.jQuery = _jQuery; - } - - return jQuery; - }, - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - // A third-party is pushing the ready event forwards - if ( wait === true ) { - jQuery.readyWait--; - } - - // Make sure that the DOM is not already loaded - if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( !document.body ) { - return setTimeout( jQuery.ready, 1 ); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger( "ready" ).unbind( "ready" ); - } - } - }, - - bindReady: function() { - if ( readyBound ) { - return; - } - - readyBound = true; - - // Catch cases where $(document).ready() is called after the - // browser event has already occurred. - if ( document.readyState === "complete" ) { - // Handle it asynchronously to allow scripts the opportunity to delay ready - return setTimeout( jQuery.ready, 1 ); - } - - // Mozilla, Opera and webkit nightlies currently support this event - if ( document.addEventListener ) { - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", jQuery.ready, false ); - - // If IE event model is used - } else if ( document.attachEvent ) { - // ensure firing before onload, - // maybe late but safe also for iframes - document.attachEvent("onreadystatechange", DOMContentLoaded); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", jQuery.ready ); - - // If IE and not a frame - // continually check to see if the document is ready - var toplevel = false; - - try { - toplevel = window.frameElement == null; - } catch(e) {} - - if ( document.documentElement.doScroll && toplevel ) { - doScrollCheck(); - } - } - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return jQuery.type(obj) === "function"; - }, - - isArray: Array.isArray || function( obj ) { - return jQuery.type(obj) === "array"; - }, - - // A crude way of determining if an object is a window - isWindow: function( obj ) { - return obj && typeof obj === "object" && "setInterval" in obj; - }, - - isNaN: function( obj ) { - return obj == null || !rdigit.test( obj ) || isNaN( obj ); - }, - - type: function( obj ) { - return obj == null ? - String( obj ) : - class2type[ toString.call(obj) ] || "object"; - }, - - isPlainObject: function( obj ) { - // Must be an Object. - // Because of IE, we also have to check the presence of the constructor property. - // Make sure that DOM nodes and window objects don't pass through, as well - if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { - return false; - } - - // Not own constructor property must be Object - if ( obj.constructor && - !hasOwn.call(obj, "constructor") && - !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { - return false; - } - - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own. - - var key; - for ( key in obj ) {} - - return key === undefined || hasOwn.call( obj, key ); - }, - - isEmptyObject: function( obj ) { - for ( var name in obj ) { - return false; - } - return true; - }, - - error: function( msg ) { - throw msg; - }, - - parseJSON: function( data ) { - if ( typeof data !== "string" || !data ) { - return null; - } - - // Make sure leading/trailing whitespace is removed (IE can't handle it) - data = jQuery.trim( data ); - - // Make sure the incoming data is actual JSON - // Logic borrowed from http://json.org/json2.js - if ( rvalidchars.test(data.replace(rvalidescape, "@") - .replace(rvalidtokens, "]") - .replace(rvalidbraces, "")) ) { - - // Try to use the native JSON parser first - return window.JSON && window.JSON.parse ? - window.JSON.parse( data ) : - (new Function("return " + data))(); - - } else { - jQuery.error( "Invalid JSON: " + data ); - } - }, - - // Cross-browser xml parsing - // (xml & tmp used internally) - parseXML: function( data , xml , tmp ) { - - if ( window.DOMParser ) { // Standard - tmp = new DOMParser(); - xml = tmp.parseFromString( data , "text/xml" ); - } else { // IE - xml = new ActiveXObject( "Microsoft.XMLDOM" ); - xml.async = "false"; - xml.loadXML( data ); - } - - tmp = xml.documentElement; - - if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { - jQuery.error( "Invalid XML: " + data ); - } - - return xml; - }, - - noop: function() {}, - - // Evalulates a script in a global context - globalEval: function( data ) { - if ( data && rnotwhite.test(data) ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement, - script = document.createElement( "script" ); - - if ( jQuery.support.scriptEval() ) { - script.appendChild( document.createTextNode( data ) ); - } else { - script.text = data; - } - - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709). - head.insertBefore( script, head.firstChild ); - head.removeChild( script ); - } - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); - }, - - // args is for internal usage only - each: function( object, callback, args ) { - var name, i = 0, - length = object.length, - isObj = length === undefined || jQuery.isFunction(object); - - if ( args ) { - if ( isObj ) { - for ( name in object ) { - if ( callback.apply( object[ name ], args ) === false ) { - break; - } - } - } else { - for ( ; i < length; ) { - if ( callback.apply( object[ i++ ], args ) === false ) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if ( isObj ) { - for ( name in object ) { - if ( callback.call( object[ name ], name, object[ name ] ) === false ) { - break; - } - } - } else { - for ( var value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} - } - } - - return object; - }, - - // Use native String.trim function wherever possible - trim: trim ? - function( text ) { - return text == null ? - "" : - trim.call( text ); - } : - - // Otherwise use our own trimming functionality - function( text ) { - return text == null ? - "" : - text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); - }, - - // results is for internal usage only - makeArray: function( array, results ) { - var ret = results || []; - - if ( array != null ) { - // The window, strings (and functions) also have 'length' - // The extra typeof function check is to prevent crashes - // in Safari 2 (See: #3039) - // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 - var type = jQuery.type(array); - - if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { - push.call( ret, array ); - } else { - jQuery.merge( ret, array ); - } - } - - return ret; - }, - - inArray: function( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; - }, - - merge: function( first, second ) { - var i = first.length, - j = 0; - - if ( typeof second.length === "number" ) { - for ( var l = second.length; j < l; j++ ) { - first[ i++ ] = second[ j ]; - } - - } else { - while ( second[j] !== undefined ) { - first[ i++ ] = second[ j++ ]; - } - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, inv ) { - var ret = [], retVal; - inv = !!inv; - - // Go through the array, only saving the items - // that pass the validator function - for ( var i = 0, length = elems.length; i < length; i++ ) { - retVal = !!callback( elems[ i ], i ); - if ( inv !== retVal ) { - ret.push( elems[ i ] ); - } - } - - return ret; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var ret = [], value; - - // Go through the array, translating each of the items to their - // new value (or values). - for ( var i = 0, length = elems.length; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - - // Flatten any nested arrays - return ret.concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - proxy: function( fn, proxy, thisObject ) { - if ( arguments.length === 2 ) { - if ( typeof proxy === "string" ) { - thisObject = fn; - fn = thisObject[ proxy ]; - proxy = undefined; - - } else if ( proxy && !jQuery.isFunction( proxy ) ) { - thisObject = proxy; - proxy = undefined; - } - } - - if ( !proxy && fn ) { - proxy = function() { - return fn.apply( thisObject || this, arguments ); - }; - } - - // Set the guid of unique handler to the same of original handler, so it can be removed - if ( fn ) { - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - } - - // So proxy can be declared as an argument - return proxy; - }, - - // Mutifunctional method to get and set values to a collection - // The value/s can be optionally by executed if its a function - access: function( elems, key, value, exec, fn, pass ) { - var length = elems.length; - - // Setting many attributes - if ( typeof key === "object" ) { - for ( var k in key ) { - jQuery.access( elems, k, key[k], exec, fn, value ); - } - return elems; - } - - // Setting one attribute - if ( value !== undefined ) { - // Optionally, function values get executed if exec is true - exec = !pass && exec && jQuery.isFunction(value); - - for ( var i = 0; i < length; i++ ) { - fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); - } - - return elems; - } - - // Getting an attribute - return length ? fn( elems[0], key ) : undefined; - }, - - now: function() { - return (new Date()).getTime(); - }, - - // Create a simple deferred (one callbacks list) - _Deferred: function() { - var // callbacks list - callbacks = [], - // stored [ context , args ] - fired, - // to avoid firing when already doing so - firing, - // flag to know if the deferred has been cancelled - cancelled, - // the deferred itself - deferred = { - - // done( f1, f2, ...) - done: function() { - if ( !cancelled ) { - var args = arguments, - i, - length, - elem, - type, - _fired; - if ( fired ) { - _fired = fired; - fired = 0; - } - for ( i = 0, length = args.length; i < length; i++ ) { - elem = args[ i ]; - type = jQuery.type( elem ); - if ( type === "array" ) { - deferred.done.apply( deferred, elem ); - } else if ( type === "function" ) { - callbacks.push( elem ); - } - } - if ( _fired ) { - deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); - } - } - return this; - }, - - // resolve with given context and args - resolveWith: function( context, args ) { - if ( !cancelled && !fired && !firing ) { - firing = 1; - try { - while( callbacks[ 0 ] ) { - callbacks.shift().apply( context, args ); - } - } - // We have to add a catch block for - // IE prior to 8 or else the finally - // block will never get executed - catch (e) { - throw e; - } - finally { - fired = [ context, args ]; - firing = 0; - } - } - return this; - }, - - // resolve with this as context and given arguments - resolve: function() { - deferred.resolveWith( jQuery.isFunction( this.promise ) ? this.promise() : this, arguments ); - return this; - }, - - // Has this deferred been resolved? - isResolved: function() { - return !!( firing || fired ); - }, - - // Cancel - cancel: function() { - cancelled = 1; - callbacks = []; - return this; - } - }; - - return deferred; - }, - - // Full fledged deferred (two callbacks list) - Deferred: function( func ) { - var deferred = jQuery._Deferred(), - failDeferred = jQuery._Deferred(), - promise; - // Add errorDeferred methods, then and promise - jQuery.extend( deferred, { - then: function( doneCallbacks, failCallbacks ) { - deferred.done( doneCallbacks ).fail( failCallbacks ); - return this; - }, - fail: failDeferred.done, - rejectWith: failDeferred.resolveWith, - reject: failDeferred.resolve, - isRejected: failDeferred.isResolved, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - if ( obj == null ) { - if ( promise ) { - return promise; - } - promise = obj = {}; - } - var i = promiseMethods.length; - while( i-- ) { - obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ]; - } - return obj; - } - } ); - // Make sure only one callback list will be used - deferred.done( failDeferred.cancel ).fail( deferred.cancel ); - // Unexpose cancel - delete deferred.cancel; - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - return deferred; - }, - - // Deferred helper - when: function( object ) { - var lastIndex = arguments.length, - deferred = lastIndex <= 1 && object && jQuery.isFunction( object.promise ) ? - object : - jQuery.Deferred(), - promise = deferred.promise(); - - if ( lastIndex > 1 ) { - var array = slice.call( arguments, 0 ), - count = lastIndex, - iCallback = function( index ) { - return function( value ) { - array[ index ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value; - if ( !( --count ) ) { - deferred.resolveWith( promise, array ); - } - }; - }; - while( ( lastIndex-- ) ) { - object = array[ lastIndex ]; - if ( object && jQuery.isFunction( object.promise ) ) { - object.promise().then( iCallback(lastIndex), deferred.reject ); - } else { - --count; - } - } - if ( !count ) { - deferred.resolveWith( promise, array ); - } - } else if ( deferred !== object ) { - deferred.resolve( object ); - } - return promise; - }, - - // Use of jQuery.browser is frowned upon. - // More details: http://docs.jquery.com/Utilities/jQuery.browser - uaMatch: function( ua ) { - ua = ua.toLowerCase(); - - var match = rwebkit.exec( ua ) || - ropera.exec( ua ) || - rmsie.exec( ua ) || - ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || - []; - - return { browser: match[1] || "", version: match[2] || "0" }; - }, - - sub: function() { - function jQuerySubclass( selector, context ) { - return new jQuerySubclass.fn.init( selector, context ); - } - jQuery.extend( true, jQuerySubclass, this ); - jQuerySubclass.superclass = this; - jQuerySubclass.fn = jQuerySubclass.prototype = this(); - jQuerySubclass.fn.constructor = jQuerySubclass; - jQuerySubclass.subclass = this.subclass; - jQuerySubclass.fn.init = function init( selector, context ) { - if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) { - context = jQuerySubclass(context); - } - - return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass ); - }; - jQuerySubclass.fn.init.prototype = jQuerySubclass.fn; - var rootjQuerySubclass = jQuerySubclass(document); - return jQuerySubclass; - }, - - browser: {} -}); - -// Create readyList deferred -readyList = jQuery._Deferred(); - -// Populate the class2type map -jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -}); - -browserMatch = jQuery.uaMatch( userAgent ); -if ( browserMatch.browser ) { - jQuery.browser[ browserMatch.browser ] = true; - jQuery.browser.version = browserMatch.version; -} - -// Deprecated, use jQuery.browser.webkit instead -if ( jQuery.browser.webkit ) { - jQuery.browser.safari = true; -} - -if ( indexOf ) { - jQuery.inArray = function( elem, array ) { - return indexOf.call( array, elem ); - }; -} - -// IE doesn't match non-breaking spaces with \s -if ( rnotwhite.test( "\xA0" ) ) { - trimLeft = /^[\s\xA0]+/; - trimRight = /[\s\xA0]+$/; -} - -// All jQuery objects should point back to these -rootjQuery = jQuery(document); - -// Cleanup functions for the document ready method -if ( document.addEventListener ) { - DOMContentLoaded = function() { - document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - jQuery.ready(); - }; - -} else if ( document.attachEvent ) { - DOMContentLoaded = function() { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( document.readyState === "complete" ) { - document.detachEvent( "onreadystatechange", DOMContentLoaded ); - jQuery.ready(); - } - }; -} - -// The DOM ready check for Internet Explorer -function doScrollCheck() { - if ( jQuery.isReady ) { - return; - } - - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - document.documentElement.doScroll("left"); - } catch(e) { - setTimeout( doScrollCheck, 1 ); - return; - } - - // and execute any waiting functions - jQuery.ready(); -} - -// Expose jQuery to the global object -return jQuery; - -})(); - - -(function() { - - jQuery.support = {}; - - var div = document.createElement("div"); - - div.style.display = "none"; - div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; - - var all = div.getElementsByTagName("*"), - a = div.getElementsByTagName("a")[0], - select = document.createElement("select"), - opt = select.appendChild( document.createElement("option") ), - input = div.getElementsByTagName("input")[0]; - - // Can't get basic test support - if ( !all || !all.length || !a ) { - return; - } - - jQuery.support = { - // IE strips leading whitespace when .innerHTML is used - leadingWhitespace: div.firstChild.nodeType === 3, - - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - tbody: !div.getElementsByTagName("tbody").length, - - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - htmlSerialize: !!div.getElementsByTagName("link").length, - - // Get the style information from getAttribute - // (IE uses .cssText insted) - style: /red/.test( a.getAttribute("style") ), - - // Make sure that URLs aren't manipulated - // (IE normalizes it by default) - hrefNormalized: a.getAttribute("href") === "/a", - - // Make sure that element opacity exists - // (IE uses filter instead) - // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.55$/.test( a.style.opacity ), - - // Verify style float existence - // (IE uses styleFloat instead of cssFloat) - cssFloat: !!a.style.cssFloat, - - // Make sure that if no value is specified for a checkbox - // that it defaults to "on". - // (WebKit defaults to "" instead) - checkOn: input.value === "on", - - // Make sure that a selected-by-default option has a working selected property. - // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) - optSelected: opt.selected, - - // Will be defined later - deleteExpando: true, - optDisabled: false, - checkClone: false, - noCloneEvent: true, - noCloneChecked: true, - boxModel: null, - inlineBlockNeedsLayout: false, - shrinkWrapBlocks: false, - reliableHiddenOffsets: true - }; - - input.checked = true; - jQuery.support.noCloneChecked = input.cloneNode( true ).checked; - - // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as diabled) - select.disabled = true; - jQuery.support.optDisabled = !opt.disabled; - - var _scriptEval = null; - jQuery.support.scriptEval = function() { - if ( _scriptEval === null ) { - var root = document.documentElement, - script = document.createElement("script"), - id = "script" + jQuery.now(); - - try { - script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); - } catch(e) {} - - root.insertBefore( script, root.firstChild ); - - // Make sure that the execution of code works by injecting a script - // tag with appendChild/createTextNode - // (IE doesn't support this, fails, and uses .text instead) - if ( window[ id ] ) { - _scriptEval = true; - delete window[ id ]; - } else { - _scriptEval = false; - } - - root.removeChild( script ); - // release memory in IE - root = script = id = null; - } - - return _scriptEval; - }; - - // Test to see if it's possible to delete an expando from an element - // Fails in Internet Explorer - try { - delete div.test; - - } catch(e) { - jQuery.support.deleteExpando = false; - } - - if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { - div.attachEvent("onclick", function click() { - // Cloning a node shouldn't copy over any - // bound event handlers (IE does this) - jQuery.support.noCloneEvent = false; - div.detachEvent("onclick", click); - }); - div.cloneNode(true).fireEvent("onclick"); - } - - div = document.createElement("div"); - div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>"; - - var fragment = document.createDocumentFragment(); - fragment.appendChild( div.firstChild ); - - // WebKit doesn't clone checked state correctly in fragments - jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; - - // Figure out if the W3C box model works as expected - // document.body must exist before we can do this - jQuery(function() { - var div = document.createElement("div"), - body = document.getElementsByTagName("body")[0]; - - // Frameset documents with no body should not run this code - if ( !body ) { - return; - } - - div.style.width = div.style.paddingLeft = "1px"; - body.appendChild( div ); - jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; - - if ( "zoom" in div.style ) { - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - // (IE < 8 does this) - div.style.display = "inline"; - div.style.zoom = 1; - jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2; - - // Check if elements with layout shrink-wrap their children - // (IE 6 does this) - div.style.display = ""; - div.innerHTML = "<div style='width:4px;'></div>"; - jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; - } - - div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; - var tds = div.getElementsByTagName("td"); - - // Check if table cells still have offsetWidth/Height when they are set - // to display:none and there are still other visible table cells in a - // table row; if so, offsetWidth/Height are not reliable for use when - // determining if an element has been hidden directly using - // display:none (it is still safe to use offsets if a parent element is - // hidden; don safety goggles and see bug #4512 for more information). - // (only IE 8 fails this test) - jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0; - - tds[0].style.display = ""; - tds[1].style.display = "none"; - - // Check if empty table cells still have offsetWidth/Height - // (IE < 8 fail this test) - jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0; - div.innerHTML = ""; - - body.removeChild( div ).style.display = "none"; - div = tds = null; - }); - - // Technique from Juriy Zaytsev - // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ - var eventSupported = function( eventName ) { - var el = document.createElement("div"); - eventName = "on" + eventName; - - // We only care about the case where non-standard event systems - // are used, namely in IE. Short-circuiting here helps us to - // avoid an eval call (in setAttribute) which can cause CSP - // to go haywire. See: https://developer.mozilla.org/en/Security/CSP - if ( !el.attachEvent ) { - return true; - } - - var isSupported = (eventName in el); - if ( !isSupported ) { - el.setAttribute(eventName, "return;"); - isSupported = typeof el[eventName] === "function"; - } - el = null; - - return isSupported; - }; - - jQuery.support.submitBubbles = eventSupported("submit"); - jQuery.support.changeBubbles = eventSupported("change"); - - // release memory in IE - div = all = a = null; -})(); - - - -var rbrace = /^(?:\{.*\}|\[.*\])$/; - -jQuery.extend({ - cache: {}, - - // Please use with caution - uuid: 0, - - // Unique for each copy of jQuery on the page - // Non-digits removed to match rinlinejQuery - expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), - - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true - }, - - hasData: function( elem ) { - elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; - - return !!elem && !isEmptyDataObject( elem ); - }, - - data: function( elem, name, data, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, - - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; - - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) { - return; - } - - if ( !id ) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - elem[ jQuery.expando ] = id = ++jQuery.uuid; - } else { - id = jQuery.expando; - } - } - - if ( !cache[ id ] ) { - cache[ id ] = {}; - - // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery - // metadata on plain JS objects when the object is serialized using - // JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } - } - - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if ( typeof name === "object" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); - } else { - cache[ id ] = jQuery.extend(cache[ id ], name); - } - } - - thisCache = cache[ id ]; - - // Internal jQuery data is stored in a separate object inside the object's data - // cache in order to avoid key collisions between internal data and user-defined - // data - if ( pvt ) { - if ( !thisCache[ internalKey ] ) { - thisCache[ internalKey ] = {}; - } - - thisCache = thisCache[ internalKey ]; - } - - if ( data !== undefined ) { - thisCache[ name ] = data; - } - - // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should - // not attempt to inspect the internal events object using jQuery.data, as this - // internal data object is undocumented and subject to change. - if ( name === "events" && !thisCache[name] ) { - return thisCache[ internalKey ] && thisCache[ internalKey ].events; - } - - return getByName ? thisCache[ name ] : thisCache; - }, - - removeData: function( elem, name, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var internalKey = jQuery.expando, isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - - // See jQuery.data for more information - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } - - if ( name ) { - var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; - - if ( thisCache ) { - delete thisCache[ name ]; - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( !isEmptyDataObject(thisCache) ) { - return; - } - } - } - - // See jQuery.data for more information - if ( pvt ) { - delete cache[ id ][ internalKey ]; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject(cache[ id ]) ) { - return; - } - } - - var internalCache = cache[ id ][ internalKey ]; - - // Browsers that fail expando deletion also refuse to delete expandos on - // the window, but it will allow it on all other JS objects; other browsers - // don't care - if ( jQuery.support.deleteExpando || cache != window ) { - delete cache[ id ]; - } else { - cache[ id ] = null; - } - - // We destroyed the entire user cache at once because it's faster than - // iterating through each key, but we need to continue to persist internal - // data if it existed - if ( internalCache ) { - cache[ id ] = {}; - // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery - // metadata on plain JS objects when the object is serialized using - // JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } - - cache[ id ][ internalKey ] = internalCache; - - // Otherwise, we need to eliminate the expando on the node to avoid - // false lookups in the cache for entries that no longer exist - } else if ( isNode ) { - // IE does not allow us to delete expando properties from nodes, - // nor does it have a removeAttribute function on Document nodes; - // we must handle all of these cases - if ( jQuery.support.deleteExpando ) { - delete elem[ jQuery.expando ]; - } else if ( elem.removeAttribute ) { - elem.removeAttribute( jQuery.expando ); - } else { - elem[ jQuery.expando ] = null; - } - } - }, - - // For internal use only. - _data: function( elem, name, data ) { - return jQuery.data( elem, name, data, true ); - }, - - // A method for determining if a DOM node can handle the data expando - acceptData: function( elem ) { - if ( elem.nodeName ) { - var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; - - if ( match ) { - return !(match === true || elem.getAttribute("classid") !== match); - } - } - - return true; - } -}); - -jQuery.fn.extend({ - data: function( key, value ) { - var data = null; - - if ( typeof key === "undefined" ) { - if ( this.length ) { - data = jQuery.data( this[0] ); - - if ( this[0].nodeType === 1 ) { - var attr = this[0].attributes, name; - for ( var i = 0, l = attr.length; i < l; i++ ) { - name = attr[i].name; - - if ( name.indexOf( "data-" ) === 0 ) { - name = name.substr( 5 ); - dataAttr( this[0], name, data[ name ] ); - } - } - } - } - - return data; - - } else if ( typeof key === "object" ) { - return this.each(function() { - jQuery.data( this, key ); - }); - } - - var parts = key.split("."); - parts[1] = parts[1] ? "." + parts[1] : ""; - - if ( value === undefined ) { - data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); - - // Try to fetch any internally stored data first - if ( data === undefined && this.length ) { - data = jQuery.data( this[0], key ); - data = dataAttr( this[0], key, data ); - } - - return data === undefined && parts[1] ? - this.data( parts[0] ) : - data; - - } else { - return this.each(function() { - var $this = jQuery( this ), - args = [ parts[0], value ]; - - $this.triggerHandler( "setData" + parts[1] + "!", args ); - jQuery.data( this, key, value ); - $this.triggerHandler( "changeData" + parts[1] + "!", args ); - }); - } - }, - - removeData: function( key ) { - return this.each(function() { - jQuery.removeData( this, key ); - }); - } -}); - -function dataAttr( elem, key, data ) { - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - data = elem.getAttribute( "data-" + key ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - !jQuery.isNaN( data ) ? parseFloat( data ) : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - // Make sure we set the data so it isn't changed later - jQuery.data( elem, key, data ); - - } else { - data = undefined; - } - } - - return data; -} - -// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON -// property to be considered empty objects; this property always exists in -// order to make sure JSON.stringify does not expose internal metadata -function isEmptyDataObject( obj ) { - for ( var name in obj ) { - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} - - - - -jQuery.extend({ - queue: function( elem, type, data ) { - if ( !elem ) { - return; - } - - type = (type || "fx") + "queue"; - var q = jQuery._data( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( !data ) { - return q || []; - } - - if ( !q || jQuery.isArray(data) ) { - q = jQuery._data( elem, type, jQuery.makeArray(data) ); - - } else { - q.push( data ); - } - - return q; - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - fn = queue.shift(); - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - } - - if ( fn ) { - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift("inprogress"); - } - - fn.call(elem, function() { - jQuery.dequeue(elem, type); - }); - } - - if ( !queue.length ) { - jQuery.removeData( elem, type + "queue", true ); - } - } -}); - -jQuery.fn.extend({ - queue: function( type, data ) { - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - } - - if ( data === undefined ) { - return jQuery.queue( this[0], type ); - } - return this.each(function( i ) { - var queue = jQuery.queue( this, type, data ); - - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); - }, - dequeue: function( type ) { - return this.each(function() { - jQuery.dequeue( this, type ); - }); - }, - - // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ - delay: function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; - type = type || "fx"; - - return this.queue( type, function() { - var elem = this; - setTimeout(function() { - jQuery.dequeue( elem, type ); - }, time ); - }); - }, - - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - } -}); - - - - -var rclass = /[\n\t\r]/g, - rspaces = /\s+/, - rreturn = /\r/g, - rspecialurl = /^(?:href|src|style)$/, - rtype = /^(?:button|input)$/i, - rfocusable = /^(?:button|input|object|select|textarea)$/i, - rclickable = /^a(?:rea)?$/i, - rradiocheck = /^(?:radio|checkbox)$/i; - -jQuery.props = { - "for": "htmlFor", - "class": "className", - readonly: "readOnly", - maxlength: "maxLength", - cellspacing: "cellSpacing", - rowspan: "rowSpan", - colspan: "colSpan", - tabindex: "tabIndex", - usemap: "useMap", - frameborder: "frameBorder" -}; - -jQuery.fn.extend({ - attr: function( name, value ) { - return jQuery.access( this, name, value, true, jQuery.attr ); - }, - - removeAttr: function( name, fn ) { - return this.each(function(){ - jQuery.attr( this, name, "" ); - if ( this.nodeType === 1 ) { - this.removeAttribute( name ); - } - }); - }, - - addClass: function( value ) { - if ( jQuery.isFunction(value) ) { - return this.each(function(i) { - var self = jQuery(this); - self.addClass( value.call(this, i, self.attr("class")) ); - }); - } - - if ( value && typeof value === "string" ) { - var classNames = (value || "").split( rspaces ); - - for ( var i = 0, l = this.length; i < l; i++ ) { - var elem = this[i]; - - if ( elem.nodeType === 1 ) { - if ( !elem.className ) { - elem.className = value; - - } else { - var className = " " + elem.className + " ", - setClass = elem.className; - - for ( var c = 0, cl = classNames.length; c < cl; c++ ) { - if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { - setClass += " " + classNames[c]; - } - } - elem.className = jQuery.trim( setClass ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - if ( jQuery.isFunction(value) ) { - return this.each(function(i) { - var self = jQuery(this); - self.removeClass( value.call(this, i, self.attr("class")) ); - }); - } - - if ( (value && typeof value === "string") || value === undefined ) { - var classNames = (value || "").split( rspaces ); - - for ( var i = 0, l = this.length; i < l; i++ ) { - var elem = this[i]; - - if ( elem.nodeType === 1 && elem.className ) { - if ( value ) { - var className = (" " + elem.className + " ").replace(rclass, " "); - for ( var c = 0, cl = classNames.length; c < cl; c++ ) { - className = className.replace(" " + classNames[c] + " ", " "); - } - elem.className = jQuery.trim( className ); - - } else { - elem.className = ""; - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isBool = typeof stateVal === "boolean"; - - if ( jQuery.isFunction( value ) ) { - return this.each(function(i) { - var self = jQuery(this); - self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); - }); - } - - return this.each(function() { - if ( type === "string" ) { - // toggle individual class names - var className, - i = 0, - self = jQuery( this ), - state = stateVal, - classNames = value.split( rspaces ); - - while ( (className = classNames[ i++ ]) ) { - // check each className given, space seperated list - state = isBool ? state : !self.hasClass( className ); - self[ state ? "addClass" : "removeClass" ]( className ); - } - - } else if ( type === "undefined" || type === "boolean" ) { - if ( this.className ) { - // store className if set - jQuery._data( this, "__className__", this.className ); - } - - // toggle whole className - this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; - } - }); - }, - - hasClass: function( selector ) { - var className = " " + selector + " "; - for ( var i = 0, l = this.length; i < l; i++ ) { - if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { - return true; - } - } - - return false; - }, - - val: function( value ) { - if ( !arguments.length ) { - var elem = this[0]; - - if ( elem ) { - if ( jQuery.nodeName( elem, "option" ) ) { - // attributes.value is undefined in Blackberry 4.7 but - // uses .value. See #6932 - var val = elem.attributes.value; - return !val || val.specified ? elem.value : elem.text; - } - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type === "select-one"; - - // Nothing was selected - if ( index < 0 ) { - return null; - } - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - // Don't return options that are disabled or in a disabled optgroup - if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && - (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { - - // Get the specific value for the option - value = jQuery(option).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - // Fixes Bug #2551 -- select.val() broken in IE after form.reset() - if ( one && !values.length && options.length ) { - return jQuery( options[ index ] ).val(); - } - - return values; - } - - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { - return elem.getAttribute("value") === null ? "on" : elem.value; - } - - // Everything else, we just grab the value - return (elem.value || "").replace(rreturn, ""); - - } - - return undefined; - } - - var isFunction = jQuery.isFunction(value); - - return this.each(function(i) { - var self = jQuery(this), val = value; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call(this, i, self.val()); - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - } else if ( typeof val === "number" ) { - val += ""; - } else if ( jQuery.isArray(val) ) { - val = jQuery.map(val, function (value) { - return value == null ? "" : value + ""; - }); - } - - if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { - this.checked = jQuery.inArray( self.val(), val ) >= 0; - - } else if ( jQuery.nodeName( this, "select" ) ) { - var values = jQuery.makeArray(val); - - jQuery( "option", this ).each(function() { - this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; - }); - - if ( !values.length ) { - this.selectedIndex = -1; - } - - } else { - this.value = val; - } - }); - } -}); - -jQuery.extend({ - attrFn: { - val: true, - css: true, - html: true, - text: true, - data: true, - width: true, - height: true, - offset: true - }, - - attr: function( elem, name, value, pass ) { - // don't get/set attributes on text, comment and attribute nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) { - return undefined; - } - - if ( pass && name in jQuery.attrFn ) { - return jQuery(elem)[name](value); - } - - var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), - // Whether we are setting (or getting) - set = value !== undefined; - - // Try to normalize/fix the name - name = notxml && jQuery.props[ name ] || name; - - // Only do all the following if this is a node (faster for style) - if ( elem.nodeType === 1 ) { - // These attributes require special treatment - var special = rspecialurl.test( name ); - - // Safari mis-reports the default selected property of an option - // Accessing the parent's selectedIndex property fixes it - if ( name === "selected" && !jQuery.support.optSelected ) { - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - - // If applicable, access the attribute via the DOM 0 way - // 'in' checks fail in Blackberry 4.7 #6931 - if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { - if ( set ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } - - if ( value === null ) { - if ( elem.nodeType === 1 ) { - elem.removeAttribute( name ); - } - - } else { - elem[ name ] = value; - } - } - - // browsers index elements by id/name on forms, give priority to attributes. - if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { - return elem.getAttributeNode( name ).nodeValue; - } - - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - if ( name === "tabIndex" ) { - var attributeNode = elem.getAttributeNode( "tabIndex" ); - - return attributeNode && attributeNode.specified ? - attributeNode.value : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; - } - - return elem[ name ]; - } - - if ( !jQuery.support.style && notxml && name === "style" ) { - if ( set ) { - elem.style.cssText = "" + value; - } - - return elem.style.cssText; - } - - if ( set ) { - // convert the value to a string (all browsers do this but IE) see #1070 - elem.setAttribute( name, "" + value ); - } - - // Ensure that missing attributes return undefined - // Blackberry 4.7 returns "" from getAttribute #6938 - if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { - return undefined; - } - - var attr = !jQuery.support.hrefNormalized && notxml && special ? - // Some attributes require a special call on IE - elem.getAttribute( name, 2 ) : - elem.getAttribute( name ); - - // Non-existent attributes return null, we normalize to undefined - return attr === null ? undefined : attr; - } - // Handle everything which isn't a DOM element node - if ( set ) { - elem[ name ] = value; - } - return elem[ name ]; - } -}); - - - - -var rnamespaces = /\.(.*)$/, - rformElems = /^(?:textarea|input|select)$/i, - rperiod = /\./g, - rspace = / /g, - rescape = /[^\w\s.|`]/g, - fcleanup = function( nm ) { - return nm.replace(rescape, "\\$&"); - }; - -/* - * A number of helper functions used for managing events. - * Many of the ideas behind this code originated from - * Dean Edwards' addEvent library. - */ -jQuery.event = { - - // Bind an event to an element - // Original by Dean Edwards - add: function( elem, types, handler, data ) { - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // TODO :: Use a try/catch until it's safe to pull this out (likely 1.6) - // Minor release fix for bug #8018 - try { - // For whatever reason, IE has trouble passing the window object - // around, causing it to be cloned in the process - if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) { - elem = window; - } - } - catch ( e ) {} - - if ( handler === false ) { - handler = returnFalse; - } else if ( !handler ) { - // Fixes bug #7229. Fix recommended by jdalton - return; - } - - var handleObjIn, handleObj; - - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - } - - // Make sure that the function being executed has a unique ID - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure - var elemData = jQuery._data( elem ); - - // If no elemData is found then we must be trying to bind to one of the - // banned noData elements - if ( !elemData ) { - return; - } - - var events = elemData.events, - eventHandle = elemData.handle; - - if ( !events ) { - elemData.events = events = {}; - } - - if ( !eventHandle ) { - elemData.handle = eventHandle = function() { - // Handle the second event of a trigger and when - // an event is called after a page has unloaded - return typeof jQuery !== "undefined" && !jQuery.event.triggered ? - jQuery.event.handle.apply( eventHandle.elem, arguments ) : - undefined; - }; - } - - // Add elem as a property of the handle function - // This is to prevent a memory leak with non-native events in IE. - eventHandle.elem = elem; - - // Handle multiple events separated by a space - // jQuery(...).bind("mouseover mouseout", fn); - types = types.split(" "); - - var type, i = 0, namespaces; - - while ( (type = types[ i++ ]) ) { - handleObj = handleObjIn ? - jQuery.extend({}, handleObjIn) : - { handler: handler, data: data }; - - // Namespaced event handlers - if ( type.indexOf(".") > -1 ) { - namespaces = type.split("."); - type = namespaces.shift(); - handleObj.namespace = namespaces.slice(0).sort().join("."); - - } else { - namespaces = []; - handleObj.namespace = ""; - } - - handleObj.type = type; - if ( !handleObj.guid ) { - handleObj.guid = handler.guid; - } - - // Get the current list of functions bound to this event - var handlers = events[ type ], - special = jQuery.event.special[ type ] || {}; - - // Init the event handler queue - if ( !handlers ) { - handlers = events[ type ] = []; - - // Check for a special event handler - // Only use addEventListener/attachEvent if the special - // events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - // Bind the global event handler to the element - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add the function to the element's handler list - handlers.push( handleObj ); - - // Keep track of which events have been used, for global triggering - jQuery.event.global[ type ] = true; - } - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - global: {}, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, pos ) { - // don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - if ( handler === false ) { - handler = returnFalse; - } - - var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, - elemData = jQuery.hasData( elem ) && jQuery._data( elem ), - events = elemData && elemData.events; - - if ( !elemData || !events ) { - return; - } - - // types is actually an event object here - if ( types && types.type ) { - handler = types.handler; - types = types.type; - } - - // Unbind all events for the element - if ( !types || typeof types === "string" && types.charAt(0) === "." ) { - types = types || ""; - - for ( type in events ) { - jQuery.event.remove( elem, type + types ); - } - - return; - } - - // Handle multiple events separated by a space - // jQuery(...).unbind("mouseover mouseout", fn); - types = types.split(" "); - - while ( (type = types[ i++ ]) ) { - origType = type; - handleObj = null; - all = type.indexOf(".") < 0; - namespaces = []; - - if ( !all ) { - // Namespaced event handlers - namespaces = type.split("."); - type = namespaces.shift(); - - namespace = new RegExp("(^|\\.)" + - jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - eventType = events[ type ]; - - if ( !eventType ) { - continue; - } - - if ( !handler ) { - for ( j = 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; - - if ( all || namespace.test( handleObj.namespace ) ) { - jQuery.event.remove( elem, origType, handleObj.handler, j ); - eventType.splice( j--, 1 ); - } - } - - continue; - } - - special = jQuery.event.special[ type ] || {}; - - for ( j = pos || 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; - - if ( handler.guid === handleObj.guid ) { - // remove the given handler for the given type - if ( all || namespace.test( handleObj.namespace ) ) { - if ( pos == null ) { - eventType.splice( j--, 1 ); - } - - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - - if ( pos != null ) { - break; - } - } - } - - // remove generic event handler if no more handlers exist - if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { - if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { - jQuery.removeEvent( elem, type, elemData.handle ); - } - - ret = null; - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - var handle = elemData.handle; - if ( handle ) { - handle.elem = null; - } - - delete elemData.events; - delete elemData.handle; - - if ( jQuery.isEmptyObject( elemData ) ) { - jQuery.removeData( elem, undefined, true ); - } - } - }, - - // bubbling is internal - trigger: function( event, data, elem /*, bubbling */ ) { - // Event object or event type - var type = event.type || event, - bubbling = arguments[3]; - - if ( !bubbling ) { - event = typeof event === "object" ? - // jQuery.Event object - event[ jQuery.expando ] ? event : - // Object literal - jQuery.extend( jQuery.Event(type), event ) : - // Just the event type (string) - jQuery.Event(type); - - if ( type.indexOf("!") >= 0 ) { - event.type = type = type.slice(0, -1); - event.exclusive = true; - } - - // Handle a global trigger - if ( !elem ) { - // Don't bubble custom events when global (to avoid too much overhead) - event.stopPropagation(); - - // Only trigger if we've ever bound an event for it - if ( jQuery.event.global[ type ] ) { - // XXX This code smells terrible. event.js should not be directly - // inspecting the data cache - jQuery.each( jQuery.cache, function() { - // internalKey variable is just used to make it easier to find - // and potentially change this stuff later; currently it just - // points to jQuery.expando - var internalKey = jQuery.expando, - internalCache = this[ internalKey ]; - if ( internalCache && internalCache.events && internalCache.events[ type ] ) { - jQuery.event.trigger( event, data, internalCache.handle.elem ); - } - }); - } - } - - // Handle triggering a single element - - // don't do events on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { - return undefined; - } - - // Clean up in case it is reused - event.result = undefined; - event.target = elem; - - // Clone the incoming data, if any - data = jQuery.makeArray( data ); - data.unshift( event ); - } - - event.currentTarget = elem; - - // Trigger the event, it is assumed that "handle" is a function - var handle = jQuery._data( elem, "handle" ); - - if ( handle ) { - handle.apply( elem, data ); - } - - var parent = elem.parentNode || elem.ownerDocument; - - // Trigger an inline bound script - try { - if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { - if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { - event.result = false; - event.preventDefault(); - } - } - - // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (inlineError) {} - - if ( !event.isPropagationStopped() && parent ) { - jQuery.event.trigger( event, data, parent, true ); - - } else if ( !event.isDefaultPrevented() ) { - var old, - target = event.target, - targetType = type.replace( rnamespaces, "" ), - isClick = jQuery.nodeName( target, "a" ) && targetType === "click", - special = jQuery.event.special[ targetType ] || {}; - - if ( (!special._default || special._default.call( elem, event ) === false) && - !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { - - try { - if ( target[ targetType ] ) { - // Make sure that we don't accidentally re-trigger the onFOO events - old = target[ "on" + targetType ]; - - if ( old ) { - target[ "on" + targetType ] = null; - } - - jQuery.event.triggered = true; - target[ targetType ](); - } - - // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (triggerError) {} - - if ( old ) { - target[ "on" + targetType ] = old; - } - - jQuery.event.triggered = false; - } - } - }, - - handle: function( event ) { - var all, handlers, namespaces, namespace_re, events, - namespace_sort = [], - args = jQuery.makeArray( arguments ); - - event = args[0] = jQuery.event.fix( event || window.event ); - event.currentTarget = this; - - // Namespaced event handlers - all = event.type.indexOf(".") < 0 && !event.exclusive; - - if ( !all ) { - namespaces = event.type.split("."); - event.type = namespaces.shift(); - namespace_sort = namespaces.slice(0).sort(); - namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - event.namespace = event.namespace || namespace_sort.join("."); - - events = jQuery._data(this, "events"); - - handlers = (events || {})[ event.type ]; - - if ( events && handlers ) { - // Clone the handlers to prevent manipulation - handlers = handlers.slice(0); - - for ( var j = 0, l = handlers.length; j < l; j++ ) { - var handleObj = handlers[ j ]; - - // Filter the functions by class - if ( all || namespace_re.test( handleObj.namespace ) ) { - // Pass in a reference to the handler function itself - // So that we can later remove it - event.handler = handleObj.handler; - event.data = handleObj.data; - event.handleObj = handleObj; - - var ret = handleObj.handler.apply( this, args ); - - if ( ret !== undefined ) { - event.result = ret; - if ( ret === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - - if ( event.isImmediatePropagationStopped() ) { - break; - } - } - } - } - - return event.result; - }, - - props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // store a copy of the original event object - // and "clone" to set read-only properties - var originalEvent = event; - event = jQuery.Event( originalEvent ); - - for ( var i = this.props.length, prop; i; ) { - prop = this.props[ --i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Fix target property, if necessary - if ( !event.target ) { - // Fixes #1925 where srcElement might not be defined either - event.target = event.srcElement || document; - } - - // check if target is a textnode (safari) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && event.fromElement ) { - event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; - } - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && event.clientX != null ) { - var doc = document.documentElement, - body = document.body; - - event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); - event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); - } - - // Add which for key events - if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { - event.which = event.charCode != null ? event.charCode : event.keyCode; - } - - // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) - if ( !event.metaKey && event.ctrlKey ) { - event.metaKey = event.ctrlKey; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && event.button !== undefined ) { - event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); - } - - return event; - }, - - // Deprecated, use jQuery.guid instead - guid: 1E8, - - // Deprecated, use jQuery.proxy instead - proxy: jQuery.proxy, - - special: { - ready: { - // Make sure the ready event is setup - setup: jQuery.bindReady, - teardown: jQuery.noop - }, - - live: { - add: function( handleObj ) { - jQuery.event.add( this, - liveConvert( handleObj.origType, handleObj.selector ), - jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); - }, - - remove: function( handleObj ) { - jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); - } - }, - - beforeunload: { - setup: function( data, namespaces, eventHandle ) { - // We only want to do this special case on windows - if ( jQuery.isWindow( this ) ) { - this.onbeforeunload = eventHandle; - } - }, - - teardown: function( namespaces, eventHandle ) { - if ( this.onbeforeunload === eventHandle ) { - this.onbeforeunload = null; - } - } - } - } -}; - -jQuery.removeEvent = document.removeEventListener ? - function( elem, type, handle ) { - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle, false ); - } - } : - function( elem, type, handle ) { - if ( elem.detachEvent ) { - elem.detachEvent( "on" + type, handle ); - } - }; - -jQuery.Event = function( src ) { - // Allow instantiation without the 'new' keyword - if ( !this.preventDefault ) { - return new jQuery.Event( src ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || - src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; - - // Event type - } else { - this.type = src; - } - - // timeStamp is buggy for some events on Firefox(#3843) - // So we won't rely on the native value - this.timeStamp = jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -function returnFalse() { - return false; -} -function returnTrue() { - return true; -} - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - preventDefault: function() { - this.isDefaultPrevented = returnTrue; - - var e = this.originalEvent; - if ( !e ) { - return; - } - - // if preventDefault exists run it on the original event - if ( e.preventDefault ) { - e.preventDefault(); - - // otherwise set the returnValue property of the original event to false (IE) - } else { - e.returnValue = false; - } - }, - stopPropagation: function() { - this.isPropagationStopped = returnTrue; - - var e = this.originalEvent; - if ( !e ) { - return; - } - // if stopPropagation exists run it on the original event - if ( e.stopPropagation ) { - e.stopPropagation(); - } - // otherwise set the cancelBubble property of the original event to true (IE) - e.cancelBubble = true; - }, - stopImmediatePropagation: function() { - this.isImmediatePropagationStopped = returnTrue; - this.stopPropagation(); - }, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse -}; - -// Checks if an event happened on an element within another element -// Used in jQuery.event.special.mouseenter and mouseleave handlers -var withinElement = function( event ) { - // Check if mouse(over|out) are still within the same parent element - var parent = event.relatedTarget; - - // Firefox sometimes assigns relatedTarget a XUL element - // which we cannot access the parentNode property of - try { - - // Chrome does something similar, the parentNode property - // can be accessed but is null. - if ( parent !== document && !parent.parentNode ) { - return; - } - // Traverse up the tree - while ( parent && parent !== this ) { - parent = parent.parentNode; - } - - if ( parent !== this ) { - // set the correct event type - event.type = event.data; - - // handle event if we actually just moused on to a non sub-element - jQuery.event.handle.apply( this, arguments ); - } - - // assuming we've left the element since we most likely mousedover a xul element - } catch(e) { } -}, - -// In case of event delegation, we only need to rename the event.type, -// liveHandler will take care of the rest. -delegate = function( event ) { - event.type = event.data; - jQuery.event.handle.apply( this, arguments ); -}; - -// Create mouseenter and mouseleave events -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - setup: function( data ) { - jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); - }, - teardown: function( data ) { - jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); - } - }; -}); - -// submit delegation -if ( !jQuery.support.submitBubbles ) { - - jQuery.event.special.submit = { - setup: function( data, namespaces ) { - if ( this.nodeName && this.nodeName.toLowerCase() !== "form" ) { - jQuery.event.add(this, "click.specialSubmit", function( e ) { - var elem = e.target, - type = elem.type; - - if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { - trigger( "submit", this, arguments ); - } - }); - - jQuery.event.add(this, "keypress.specialSubmit", function( e ) { - var elem = e.target, - type = elem.type; - - if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { - trigger( "submit", this, arguments ); - } - }); - - } else { - return false; - } - }, - - teardown: function( namespaces ) { - jQuery.event.remove( this, ".specialSubmit" ); - } - }; - -} - -// change delegation, happens here so we have bind. -if ( !jQuery.support.changeBubbles ) { - - var changeFilters, - - getVal = function( elem ) { - var type = elem.type, val = elem.value; - - if ( type === "radio" || type === "checkbox" ) { - val = elem.checked; - - } else if ( type === "select-multiple" ) { - val = elem.selectedIndex > -1 ? - jQuery.map( elem.options, function( elem ) { - return elem.selected; - }).join("-") : - ""; - - } else if ( elem.nodeName.toLowerCase() === "select" ) { - val = elem.selectedIndex; - } - - return val; - }, - - testChange = function testChange( e ) { - var elem = e.target, data, val; - - if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { - return; - } - - data = jQuery._data( elem, "_change_data" ); - val = getVal(elem); - - // the current data will be also retrieved by beforeactivate - if ( e.type !== "focusout" || elem.type !== "radio" ) { - jQuery._data( elem, "_change_data", val ); - } - - if ( data === undefined || val === data ) { - return; - } - - if ( data != null || val ) { - e.type = "change"; - e.liveFired = undefined; - jQuery.event.trigger( e, arguments[1], elem ); - } - }; - - jQuery.event.special.change = { - filters: { - focusout: testChange, - - beforedeactivate: testChange, - - click: function( e ) { - var elem = e.target, type = elem.type; - - if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { - testChange.call( this, e ); - } - }, - - // Change has to be called before submit - // Keydown will be called before keypress, which is used in submit-event delegation - keydown: function( e ) { - var elem = e.target, type = elem.type; - - if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || - (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || - type === "select-multiple" ) { - testChange.call( this, e ); - } - }, - - // Beforeactivate happens also before the previous element is blurred - // with this event you can't trigger a change event, but you can store - // information - beforeactivate: function( e ) { - var elem = e.target; - jQuery._data( elem, "_change_data", getVal(elem) ); - } - }, - - setup: function( data, namespaces ) { - if ( this.type === "file" ) { - return false; - } - - for ( var type in changeFilters ) { - jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); - } - - return rformElems.test( this.nodeName ); - }, - - teardown: function( namespaces ) { - jQuery.event.remove( this, ".specialChange" ); - - return rformElems.test( this.nodeName ); - } - }; - - changeFilters = jQuery.event.special.change.filters; - - // Handle when the input is .focus()'d - changeFilters.focus = changeFilters.beforeactivate; -} - -function trigger( type, elem, args ) { - // Piggyback on a donor event to simulate a different one. - // Fake originalEvent to avoid donor's stopPropagation, but if the - // simulated event prevents default then we do the same on the donor. - // Don't pass args or remember liveFired; they apply to the donor event. - var event = jQuery.extend( {}, args[ 0 ] ); - event.type = type; - event.originalEvent = {}; - event.liveFired = undefined; - jQuery.event.handle.call( elem, event ); - if ( event.isDefaultPrevented() ) { - args[ 0 ].preventDefault(); - } -} - -// Create "bubbling" focus and blur events -if ( document.addEventListener ) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - jQuery.event.special[ fix ] = { - setup: function() { - this.addEventListener( orig, handler, true ); - }, - teardown: function() { - this.removeEventListener( orig, handler, true ); - } - }; - - function handler( e ) { - e = jQuery.event.fix( e ); - e.type = fix; - return jQuery.event.handle.call( this, e ); - } - }); -} - -jQuery.each(["bind", "one"], function( i, name ) { - jQuery.fn[ name ] = function( type, data, fn ) { - // Handle object literals - if ( typeof type === "object" ) { - for ( var key in type ) { - this[ name ](key, data, type[key], fn); - } - return this; - } - - if ( jQuery.isFunction( data ) || data === false ) { - fn = data; - data = undefined; - } - - var handler = name === "one" ? jQuery.proxy( fn, function( event ) { - jQuery( this ).unbind( event, handler ); - return fn.apply( this, arguments ); - }) : fn; - - if ( type === "unload" && name !== "one" ) { - this.one( type, data, fn ); - - } else { - for ( var i = 0, l = this.length; i < l; i++ ) { - jQuery.event.add( this[i], type, handler, data ); - } - } - - return this; - }; -}); - -jQuery.fn.extend({ - unbind: function( type, fn ) { - // Handle object literals - if ( typeof type === "object" && !type.preventDefault ) { - for ( var key in type ) { - this.unbind(key, type[key]); - } - - } else { - for ( var i = 0, l = this.length; i < l; i++ ) { - jQuery.event.remove( this[i], type, fn ); - } - } - - return this; - }, - - delegate: function( selector, types, data, fn ) { - return this.live( types, data, fn, selector ); - }, - - undelegate: function( selector, types, fn ) { - if ( arguments.length === 0 ) { - return this.unbind( "live" ); - - } else { - return this.die( types, null, fn, selector ); - } - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - - triggerHandler: function( type, data ) { - if ( this[0] ) { - var event = jQuery.Event( type ); - event.preventDefault(); - event.stopPropagation(); - jQuery.event.trigger( event, data, this[0] ); - return event.result; - } - }, - - toggle: function( fn ) { - // Save reference to arguments for access in closure - var args = arguments, - i = 1; - - // link all the functions, so any of them can unbind this click handler - while ( i < args.length ) { - jQuery.proxy( fn, args[ i++ ] ); - } - - return this.click( jQuery.proxy( fn, function( event ) { - // Figure out which function to execute - var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; - jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[ lastToggle ].apply( this, arguments ) || false; - })); - }, - - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -}); - -var liveMap = { - focus: "focusin", - blur: "focusout", - mouseenter: "mouseover", - mouseleave: "mouseout" -}; - -jQuery.each(["live", "die"], function( i, name ) { - jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { - var type, i = 0, match, namespaces, preType, - selector = origSelector || this.selector, - context = origSelector ? this : jQuery( this.context ); - - if ( typeof types === "object" && !types.preventDefault ) { - for ( var key in types ) { - context[ name ]( key, data, types[key], selector ); - } - - return this; - } - - if ( jQuery.isFunction( data ) ) { - fn = data; - data = undefined; - } - - types = (types || "").split(" "); - - while ( (type = types[ i++ ]) != null ) { - match = rnamespaces.exec( type ); - namespaces = ""; - - if ( match ) { - namespaces = match[0]; - type = type.replace( rnamespaces, "" ); - } - - if ( type === "hover" ) { - types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); - continue; - } - - preType = type; - - if ( type === "focus" || type === "blur" ) { - types.push( liveMap[ type ] + namespaces ); - type = type + namespaces; - - } else { - type = (liveMap[ type ] || type) + namespaces; - } - - if ( name === "live" ) { - // bind live handler - for ( var j = 0, l = context.length; j < l; j++ ) { - jQuery.event.add( context[j], "live." + liveConvert( type, selector ), - { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); - } - - } else { - // unbind live handler - context.unbind( "live." + liveConvert( type, selector ), fn ); - } - } - - return this; - }; -}); - -function liveHandler( event ) { - var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, - elems = [], - selectors = [], - events = jQuery._data( this, "events" ); - - // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) - if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { - return; - } - - if ( event.namespace ) { - namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - event.liveFired = this; - - var live = events.live.slice(0); - - for ( j = 0; j < live.length; j++ ) { - handleObj = live[j]; - - if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { - selectors.push( handleObj.selector ); - - } else { - live.splice( j--, 1 ); - } - } - - match = jQuery( event.target ).closest( selectors, event.currentTarget ); - - for ( i = 0, l = match.length; i < l; i++ ) { - close = match[i]; - - for ( j = 0; j < live.length; j++ ) { - handleObj = live[j]; - - if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) { - elem = close.elem; - related = null; - - // Those two events require additional checking - if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { - event.type = handleObj.preType; - related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; - } - - if ( !related || related !== elem ) { - elems.push({ elem: elem, handleObj: handleObj, level: close.level }); - } - } - } - } - - for ( i = 0, l = elems.length; i < l; i++ ) { - match = elems[i]; - - if ( maxLevel && match.level > maxLevel ) { - break; - } - - event.currentTarget = match.elem; - event.data = match.handleObj.data; - event.handleObj = match.handleObj; - - ret = match.handleObj.origHandler.apply( match.elem, arguments ); - - if ( ret === false || event.isPropagationStopped() ) { - maxLevel = match.level; - - if ( ret === false ) { - stop = false; - } - if ( event.isImmediatePropagationStopped() ) { - break; - } - } - } - - return stop; -} - -function liveConvert( type, selector ) { - return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&"); -} - -jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup error").split(" "), function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - if ( fn == null ) { - fn = data; - data = null; - } - - return arguments.length > 0 ? - this.bind( name, data, fn ) : - this.trigger( name ); - }; - - if ( jQuery.attrFn ) { - jQuery.attrFn[ name ] = true; - } -}); - - -/*! - * Sizzle CSS Selector Engine - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ - -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true, - rBackslash = /\\/g, - rNonWord = /\W/; - -// Here we check if the JavaScript engine is using some sort of -// optimization where it does not always call our comparision -// function. If that is the case, discard the hasDuplicate value. -// Thus far that includes Google Chrome. -[0, 0].sort(function() { - baseHasDuplicate = false; - return 0; -}); - -var Sizzle = function( selector, context, results, seed ) { - results = results || []; - context = context || document; - - var origContext = context; - - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - var m, set, checkSet, extra, ret, cur, pop, i, - prune = true, - contextXML = Sizzle.isXML( context ), - parts = [], - soFar = selector; - - // Reset the position of the chunker regexp (start from head) - do { - chunker.exec( "" ); - m = chunker.exec( soFar ); - - if ( m ) { - soFar = m[3]; - - parts.push( m[1] ); - - if ( m[2] ) { - extra = m[3]; - break; - } - } - } while ( m ); - - if ( parts.length > 1 && origPOS.exec( selector ) ) { - - if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context ); - - } else { - set = Expr.relative[ parts[0] ] ? - [ context ] : - Sizzle( parts.shift(), context ); - - while ( parts.length ) { - selector = parts.shift(); - - if ( Expr.relative[ selector ] ) { - selector += parts.shift(); - } - - set = posProcess( selector, set ); - } - } - - } else { - // Take a shortcut and set the context if the root selector is an ID - // (but not if it'll be faster if the inner selector is an ID) - if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - - ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? - Sizzle.filter( ret.expr, ret.set )[0] : - ret.set[0]; - } - - if ( context ) { - ret = seed ? - { expr: parts.pop(), set: makeArray(seed) } : - Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - - set = ret.expr ? - Sizzle.filter( ret.expr, ret.set ) : - ret.set; - - if ( parts.length > 0 ) { - checkSet = makeArray( set ); - - } else { - prune = false; - } - - while ( parts.length ) { - cur = parts.pop(); - pop = cur; - - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } - - if ( pop == null ) { - pop = context; - } - - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - - } else { - checkSet = parts = []; - } - } - - if ( !checkSet ) { - checkSet = set; - } - - if ( !checkSet ) { - Sizzle.error( cur || selector ); - } - - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - - } else if ( context && context.nodeType === 1 ) { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - - } else { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } - } - } - - } else { - makeArray( checkSet, results ); - } - - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } - - return results; -}; - -Sizzle.uniqueSort = function( results ) { - if ( sortOrder ) { - hasDuplicate = baseHasDuplicate; - results.sort( sortOrder ); - - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[ i - 1 ] ) { - results.splice( i--, 1 ); - } - } - } - } - - return results; -}; - -Sizzle.matches = function( expr, set ) { - return Sizzle( expr, null, null, set ); -}; - -Sizzle.matchesSelector = function( node, expr ) { - return Sizzle( expr, null, null, [node] ).length > 0; -}; - -Sizzle.find = function( expr, context, isXML ) { - var set; - - if ( !expr ) { - return []; - } - - for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var match, - type = Expr.order[i]; - - if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - var left = match[1]; - match.splice( 1, 1 ); - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace( rBackslash, "" ); - set = Expr.find[ type ]( match, context, isXML ); - - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } - - if ( !set ) { - set = typeof context.getElementsByTagName !== "undefined" ? - context.getElementsByTagName( "*" ) : - []; - } - - return { set: set, expr: expr }; -}; - -Sizzle.filter = function( expr, set, inplace, not ) { - var match, anyFound, - old = expr, - result = [], - curLoop = set, - isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); - - while ( expr && set.length ) { - for ( var type in Expr.filter ) { - if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - var found, item, - filter = Expr.filter[ type ], - left = match[1]; - - anyFound = false; - - match.splice(1,1); - - if ( left.substr( left.length - 1 ) === "\\" ) { - continue; - } - - if ( curLoop === result ) { - result = []; - } - - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); - - if ( !match ) { - anyFound = found = true; - - } else if ( match === true ) { - continue; - } - } - - if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; - - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - - } else { - curLoop[i] = false; - } - - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } - - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; - } - - expr = expr.replace( Expr.match[ type ], "" ); - - if ( !anyFound ) { - return []; - } - - break; - } - } - } - - // Improper expression - if ( expr === old ) { - if ( anyFound == null ) { - Sizzle.error( expr ); - - } else { - break; - } - } - - old = expr; - } - - return curLoop; -}; - -Sizzle.error = function( msg ) { - throw "Syntax error, unrecognized expression: " + msg; -}; - -var Expr = Sizzle.selectors = { - order: [ "ID", "NAME", "TAG" ], - - match: { - ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ - }, - - leftMatch: {}, - - attrMap: { - "class": "className", - "for": "htmlFor" - }, - - attrHandle: { - href: function( elem ) { - return elem.getAttribute( "href" ); - }, - type: function( elem ) { - return elem.getAttribute( "type" ); - } - }, - - relative: { - "+": function(checkSet, part){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !rNonWord.test( part ), - isPartStrNotTag = isPartStr && !isTag; - - if ( isTag ) { - part = part.toLowerCase(); - } - - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - - checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? - elem || false : - elem === part; - } - } - - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); - } - }, - - ">": function( checkSet, part ) { - var elem, - isPartStr = typeof part === "string", - i = 0, - l = checkSet.length; - - if ( isPartStr && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - - for ( ; i < l; i++ ) { - elem = checkSet[i]; - - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; - } - } - - } else { - for ( ; i < l; i++ ) { - elem = checkSet[i]; - - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } - } - }, - - "": function(checkSet, part, isXML){ - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if ( typeof part === "string" && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); - }, - - "~": function( checkSet, part, isXML ) { - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if ( typeof part === "string" && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); - } - }, - - find: { - ID: function( match, context, isXML ) { - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }, - - NAME: function( match, context ) { - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], - results = context.getElementsByName( match[1] ); - - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } - - return ret.length === 0 ? null : ret; - } - }, - - TAG: function( match, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( match[1] ); - } - } - }, - preFilter: { - CLASS: function( match, curLoop, inplace, result, not, isXML ) { - match = " " + match[1].replace( rBackslash, "" ) + " "; - - if ( isXML ) { - return match; - } - - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { - if ( !inplace ) { - result.push( elem ); - } - - } else if ( inplace ) { - curLoop[i] = false; - } - } - } - - return false; - }, - - ID: function( match ) { - return match[1].replace( rBackslash, "" ); - }, - - TAG: function( match, curLoop ) { - return match[1].replace( rBackslash, "" ).toLowerCase(); - }, - - CHILD: function( match ) { - if ( match[1] === "nth" ) { - if ( !match[2] ) { - Sizzle.error( match[0] ); - } - - match[2] = match[2].replace(/^\+|\s*/g, ''); - - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( - match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - - // calculate the numbers (first)n+(last) including if they are negative - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - else if ( match[2] ) { - Sizzle.error( match[0] ); - } - - // TODO: Move to normal caching system - match[0] = done++; - - return match; - }, - - ATTR: function( match, curLoop, inplace, result, not, isXML ) { - var name = match[1] = match[1].replace( rBackslash, "" ); - - if ( !isXML && Expr.attrMap[name] ) { - match[1] = Expr.attrMap[name]; - } - - // Handle if an un-quoted value was used - match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); - - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; - } - - return match; - }, - - PSEUDO: function( match, curLoop, inplace, result, not ) { - if ( match[1] === "not" ) { - // If we're dealing with a complex expression, or a simple one - if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { - match[3] = Sizzle(match[3], null, null, curLoop); - - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - - if ( !inplace ) { - result.push.apply( result, ret ); - } - - return false; - } - - } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { - return true; - } - - return match; - }, - - POS: function( match ) { - match.unshift( true ); - - return match; - } - }, - - filters: { - enabled: function( elem ) { - return elem.disabled === false && elem.type !== "hidden"; - }, - - disabled: function( elem ) { - return elem.disabled === true; - }, - - checked: function( elem ) { - return elem.checked === true; - }, - - selected: function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - parent: function( elem ) { - return !!elem.firstChild; - }, - - empty: function( elem ) { - return !elem.firstChild; - }, - - has: function( elem, i, match ) { - return !!Sizzle( match[3], elem ).length; - }, - - header: function( elem ) { - return (/h\d/i).test( elem.nodeName ); - }, - - text: function( elem ) { - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return "text" === elem.getAttribute( 'type' ); - }, - radio: function( elem ) { - return "radio" === elem.type; - }, - - checkbox: function( elem ) { - return "checkbox" === elem.type; - }, - - file: function( elem ) { - return "file" === elem.type; - }, - password: function( elem ) { - return "password" === elem.type; - }, - - submit: function( elem ) { - return "submit" === elem.type; - }, - - image: function( elem ) { - return "image" === elem.type; - }, - - reset: function( elem ) { - return "reset" === elem.type; - }, - - button: function( elem ) { - return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; - }, - - input: function( elem ) { - return (/input|select|textarea|button/i).test( elem.nodeName ); - } - }, - setFilters: { - first: function( elem, i ) { - return i === 0; - }, - - last: function( elem, i, match, array ) { - return i === array.length - 1; - }, - - even: function( elem, i ) { - return i % 2 === 0; - }, - - odd: function( elem, i ) { - return i % 2 === 1; - }, - - lt: function( elem, i, match ) { - return i < match[3] - 0; - }, - - gt: function( elem, i, match ) { - return i > match[3] - 0; - }, - - nth: function( elem, i, match ) { - return match[3] - 0 === i; - }, - - eq: function( elem, i, match ) { - return match[3] - 0 === i; - } - }, - filter: { - PSEUDO: function( elem, match, i, array ) { - var name = match[1], - filter = Expr.filters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; - - } else if ( name === "not" ) { - var not = match[3]; - - for ( var j = 0, l = not.length; j < l; j++ ) { - if ( not[j] === elem ) { - return false; - } - } - - return true; - - } else { - Sizzle.error( name ); - } - }, - - CHILD: function( elem, match ) { - var type = match[1], - node = elem; - - switch ( type ) { - case "only": - case "first": - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - if ( type === "first" ) { - return true; - } - - node = elem; - - case "last": - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - return true; - - case "nth": - var first = match[2], - last = match[3]; - - if ( first === 1 && last === 0 ) { - return true; - } - - var doneName = match[0], - parent = elem.parentNode; - - if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { - var count = 0; - - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - - parent.sizcache = doneName; - } - - var diff = elem.nodeIndex - last; - - if ( first === 0 ) { - return diff === 0; - - } else { - return ( diff % first === 0 && diff / first >= 0 ); - } - } - }, - - ID: function( elem, match ) { - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - - TAG: function( elem, match ) { - return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; - }, - - CLASS: function( elem, match ) { - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - - ATTR: function( elem, match ) { - var name = match[1], - result = Expr.attrHandle[ name ] ? - Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - - return result == null ? - type === "!=" : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value !== check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - - POS: function( elem, match, i, array ) { - var name = match[2], - filter = Expr.setFilters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } - } - } -}; - -var origPOS = Expr.match.POS, - fescape = function(all, num){ - return "\\" + (num - 0 + 1); - }; - -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); - Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); -} - -var makeArray = function( array, results ) { - array = Array.prototype.slice.call( array, 0 ); - - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; - -// Perform a simple check to determine if the browser is capable of -// converting a NodeList to an array using builtin methods. -// Also verifies that the returned array holds DOM nodes -// (which is not the case in the Blackberry browser) -try { - Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; - -// Provide a fallback method if it does not work -} catch( e ) { - makeArray = function( array, results ) { - var i = 0, - ret = results || []; - - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - - } else { - if ( typeof array.length === "number" ) { - for ( var l = array.length; i < l; i++ ) { - ret.push( array[i] ); - } - - } else { - for ( ; array[i]; i++ ) { - ret.push( array[i] ); - } - } - } - - return ret; - }; -} - -var sortOrder, siblingCheck; - -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { - return a.compareDocumentPosition ? -1 : 1; - } - - return a.compareDocumentPosition(b) & 4 ? -1 : 1; - }; - -} else { - sortOrder = function( a, b ) { - var al, bl, - ap = [], - bp = [], - aup = a.parentNode, - bup = b.parentNode, - cur = aup; - - // The nodes are identical, we can exit early - if ( a === b ) { - hasDuplicate = true; - return 0; - - // If the nodes are siblings (or identical) we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - - // If no parents were found then the nodes are disconnected - } else if ( !aup ) { - return -1; - - } else if ( !bup ) { - return 1; - } - - // Otherwise they're somewhere else in the tree so we need - // to build up a full list of the parentNodes for comparison - while ( cur ) { - ap.unshift( cur ); - cur = cur.parentNode; - } - - cur = bup; - - while ( cur ) { - bp.unshift( cur ); - cur = cur.parentNode; - } - - al = ap.length; - bl = bp.length; - - // Start walking down the tree looking for a discrepancy - for ( var i = 0; i < al && i < bl; i++ ) { - if ( ap[i] !== bp[i] ) { - return siblingCheck( ap[i], bp[i] ); - } - } - - // We ended someplace up the tree so do a sibling check - return i === al ? - siblingCheck( a, bp[i], -1 ) : - siblingCheck( ap[i], b, 1 ); - }; - - siblingCheck = function( a, b, ret ) { - if ( a === b ) { - return ret; - } - - var cur = a.nextSibling; - - while ( cur ) { - if ( cur === b ) { - return -1; - } - - cur = cur.nextSibling; - } - - return 1; - }; -} - -// Utility function for retreiving the text value of an array of DOM nodes -Sizzle.getText = function( elems ) { - var ret = "", elem; - - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += Sizzle.getText( elem.childNodes ); - } - } - - return ret; -}; - -// Check to see if the browser returns elements by name when -// querying by getElementById (and provide a workaround) -(function(){ - // We're going to inject a fake input element with a specified name - var form = document.createElement("div"), - id = "script" + (new Date()).getTime(), - root = document.documentElement; - - form.innerHTML = "<a name='" + id + "'/>"; - - // Inject it into the root element, check its status, and remove it quickly - root.insertBefore( form, root.firstChild ); - - // The workaround has to do additional checks after a getElementById - // Which slows things down for other browsers (hence the branching) - if ( document.getElementById( id ) ) { - Expr.find.ID = function( match, context, isXML ) { - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - - return m ? - m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? - [m] : - undefined : - []; - } - }; - - Expr.filter.ID = function( elem, match ) { - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - - root.removeChild( form ); - - // release memory in IE - root = form = null; -})(); - -(function(){ - // Check to see if the browser returns only elements - // when doing getElementsByTagName("*") - - // Create a fake element - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); - - // Make sure no comments are found - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function( match, context ) { - var results = context.getElementsByTagName( match[1] ); - - // Filter out possible comments - if ( match[1] === "*" ) { - var tmp = []; - - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } - - results = tmp; - } - - return results; - }; - } - - // Check to see if an attribute returns normalized href attributes - div.innerHTML = "<a href='#'></a>"; - - if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#" ) { - - Expr.attrHandle.href = function( elem ) { - return elem.getAttribute( "href", 2 ); - }; - } - - // release memory in IE - div = null; -})(); - -if ( document.querySelectorAll ) { - (function(){ - var oldSizzle = Sizzle, - div = document.createElement("div"), - id = "__sizzle__"; - - div.innerHTML = "<p class='TEST'></p>"; - - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function( query, context, extra, seed ) { - context = context || document; - - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && !Sizzle.isXML(context) ) { - // See if we find a selector to speed up - var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); - - if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { - // Speed-up: Sizzle("TAG") - if ( match[1] ) { - return makeArray( context.getElementsByTagName( query ), extra ); - - // Speed-up: Sizzle(".CLASS") - } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { - return makeArray( context.getElementsByClassName( match[2] ), extra ); - } - } - - if ( context.nodeType === 9 ) { - // Speed-up: Sizzle("body") - // The body element only exists once, optimize finding it - if ( query === "body" && context.body ) { - return makeArray( [ context.body ], extra ); - - // Speed-up: Sizzle("#ID") - } else if ( match && match[3] ) { - var elem = context.getElementById( match[3] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id === match[3] ) { - return makeArray( [ elem ], extra ); - } - - } else { - return makeArray( [], extra ); - } - } - - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(qsaError) {} - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - var oldContext = context, - old = context.getAttribute( "id" ), - nid = old || id, - hasParent = context.parentNode, - relativeHierarchySelector = /^\s*[+~]/.test( query ); - - if ( !old ) { - context.setAttribute( "id", nid ); - } else { - nid = nid.replace( /'/g, "\\$&" ); - } - if ( relativeHierarchySelector && hasParent ) { - context = context.parentNode; - } - - try { - if ( !relativeHierarchySelector || hasParent ) { - return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); - } - - } catch(pseudoError) { - } finally { - if ( !old ) { - oldContext.removeAttribute( "id" ); - } - } - } - } - - return oldSizzle(query, context, extra, seed); - }; - - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } - - // release memory in IE - div = null; - })(); -} - -(function(){ - var html = document.documentElement, - matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector, - pseudoWorks = false; - - try { - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( document.documentElement, "[test!='']:sizzle" ); - - } catch( pseudoError ) { - pseudoWorks = true; - } - - if ( matches ) { - Sizzle.matchesSelector = function( node, expr ) { - // Make sure that attribute selectors are quoted - expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); - - if ( !Sizzle.isXML( node ) ) { - try { - if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { - return matches.call( node, expr ); - } - } catch(e) {} - } - - return Sizzle(expr, null, null, [node]).length > 0; - }; - } -})(); - -(function(){ - var div = document.createElement("div"); - - div.innerHTML = "<div class='test e'></div><div class='test'></div>"; - - // Opera can't find a second classname (in 9.6) - // Also, make sure that getElementsByClassName actually exists - if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { - return; - } - - // Safari caches class attributes, doesn't catch changes (in 3.2) - div.lastChild.className = "e"; - - if ( div.getElementsByClassName("e").length === 1 ) { - return; - } - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function( match, context, isXML ) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; - - // release memory in IE - div = null; -})(); - -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - - if ( elem ) { - var match = false; - - elem = elem[dir]; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 && !isXML ){ - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( elem.nodeName.toLowerCase() === cur ) { - match = elem; - break; - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - - if ( elem ) { - var match = false; - - elem = elem[dir]; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } - - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -if ( document.documentElement.contains ) { - Sizzle.contains = function( a, b ) { - return a !== b && (a.contains ? a.contains(b) : true); - }; - -} else if ( document.documentElement.compareDocumentPosition ) { - Sizzle.contains = function( a, b ) { - return !!(a.compareDocumentPosition(b) & 16); - }; - -} else { - Sizzle.contains = function() { - return false; - }; -} - -Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; - - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -var posProcess = function( selector, context ) { - var match, - tmpSet = [], - later = "", - root = context.nodeType ? [context] : context; - - // Position selectors must be done after the filter - // And so must :not(positional) so we move all PSEUDOs to the end - while ( (match = Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace( Expr.match.PSEUDO, "" ); - } - - selector = Expr.relative[selector] ? selector + "*" : selector; - - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet ); - } - - return Sizzle.filter( later, tmpSet ); -}; - -// EXPOSE -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.filters; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - -})(); - - -var runtil = /Until$/, - rparentsprev = /^(?:parents|prevUntil|prevAll)/, - // Note: This RegExp should be improved, or likely pulled from Sizzle - rmultiselector = /,/, - isSimple = /^.[^:#\[\.,]*$/, - slice = Array.prototype.slice, - POS = jQuery.expr.match.POS, - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend({ - find: function( selector ) { - var ret = this.pushStack( "", "find", selector ), - length = 0; - - for ( var i = 0, l = this.length; i < l; i++ ) { - length = ret.length; - jQuery.find( selector, this[i], ret ); - - if ( i > 0 ) { - // Make sure that the results are unique - for ( var n = length; n < ret.length; n++ ) { - for ( var r = 0; r < length; r++ ) { - if ( ret[r] === ret[n] ) { - ret.splice(n--, 1); - break; - } - } - } - } - } - - return ret; - }, - - has: function( target ) { - var targets = jQuery( target ); - return this.filter(function() { - for ( var i = 0, l = targets.length; i < l; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - not: function( selector ) { - return this.pushStack( winnow(this, selector, false), "not", selector); - }, - - filter: function( selector ) { - return this.pushStack( winnow(this, selector, true), "filter", selector ); - }, - - is: function( selector ) { - return !!selector && jQuery.filter( selector, this ).length > 0; - }, - - closest: function( selectors, context ) { - var ret = [], i, l, cur = this[0]; - - if ( jQuery.isArray( selectors ) ) { - var match, selector, - matches = {}, - level = 1; - - if ( cur && selectors.length ) { - for ( i = 0, l = selectors.length; i < l; i++ ) { - selector = selectors[i]; - - if ( !matches[selector] ) { - matches[selector] = jQuery.expr.match.POS.test( selector ) ? - jQuery( selector, context || this.context ) : - selector; - } - } - - while ( cur && cur.ownerDocument && cur !== context ) { - for ( selector in matches ) { - match = matches[selector]; - - if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { - ret.push({ selector: selector, elem: cur, level: level }); - } - } - - cur = cur.parentNode; - level++; - } - } - - return ret; - } - - var pos = POS.test( selectors ) ? - jQuery( selectors, context || this.context ) : null; - - for ( i = 0, l = this.length; i < l; i++ ) { - cur = this[i]; - - while ( cur ) { - if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { - ret.push( cur ); - break; - - } else { - cur = cur.parentNode; - if ( !cur || !cur.ownerDocument || cur === context ) { - break; - } - } - } - } - - ret = ret.length > 1 ? jQuery.unique(ret) : ret; - - return this.pushStack( ret, "closest", selectors ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - if ( !elem || typeof elem === "string" ) { - return jQuery.inArray( this[0], - // If it receives a string, the selector is used - // If it receives nothing, the siblings are used - elem ? jQuery( elem ) : this.parent().children() ); - } - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[0] : elem, this ); - }, - - add: function( selector, context ) { - var set = typeof selector === "string" ? - jQuery( selector, context ) : - jQuery.makeArray( selector ), - all = jQuery.merge( this.get(), set ); - - return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? - all : - jQuery.unique( all ) ); - }, - - andSelf: function() { - return this.add( this.prevObject ); - } -}); - -// A painfully simple check to see if an element is disconnected -// from a document (should be improved, where feasible). -function isDisconnected( node ) { - return !node || !node.parentNode || node.parentNode.nodeType === 11; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return jQuery.nth( elem, 2, "nextSibling" ); - }, - prev: function( elem ) { - return jQuery.nth( elem, 2, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( elem.parentNode.firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return jQuery.nodeName( elem, "iframe" ) ? - elem.contentDocument || elem.contentWindow.document : - jQuery.makeArray( elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ), - // The variable 'args' was introduced in - // https://github.com/jquery/jquery/commit/52a0238 - // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. - // http://code.google.com/p/v8/issues/detail?id=1050 - args = slice.call(arguments); - - if ( !runtil.test( name ) ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - ret = jQuery.filter( selector, ret ); - } - - ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; - - if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { - ret = ret.reverse(); - } - - return this.pushStack( ret, name, args.join(",") ); - }; -}); - -jQuery.extend({ - filter: function( expr, elems, not ) { - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 ? - jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : - jQuery.find.matches(expr, elems); - }, - - dir: function( elem, dir, until ) { - var matched = [], - cur = elem[ dir ]; - - while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { - if ( cur.nodeType === 1 ) { - matched.push( cur ); - } - cur = cur[dir]; - } - return matched; - }, - - nth: function( cur, result, dir, elem ) { - result = result || 1; - var num = 0; - - for ( ; cur; cur = cur[dir] ) { - if ( cur.nodeType === 1 && ++num === result ) { - break; - } - } - - return cur; - }, - - sibling: function( n, elem ) { - var r = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - r.push( n ); - } - } - - return r; - } -}); - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, keep ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep(elements, function( elem, i ) { - var retVal = !!qualifier.call( elem, i, elem ); - return retVal === keep; - }); - - } else if ( qualifier.nodeType ) { - return jQuery.grep(elements, function( elem, i ) { - return (elem === qualifier) === keep; - }); - - } else if ( typeof qualifier === "string" ) { - var filtered = jQuery.grep(elements, function( elem ) { - return elem.nodeType === 1; - }); - - if ( isSimple.test( qualifier ) ) { - return jQuery.filter(qualifier, filtered, !keep); - } else { - qualifier = jQuery.filter( qualifier, filtered ); - } - } - - return jQuery.grep(elements, function( elem, i ) { - return (jQuery.inArray( elem, qualifier ) >= 0) === keep; - }); -} - - - - -var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, - rleadingWhitespace = /^\s+/, - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, - rtagName = /<([\w:]+)/, - rtbody = /<tbody/i, - rhtml = /<|&#?\w+;/, - rnocache = /<(?:script|object|embed|option|style)/i, - // checked="checked" or checked - rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - wrapMap = { - option: [ 1, "<select multiple='multiple'>", "</select>" ], - legend: [ 1, "<fieldset>", "</fieldset>" ], - thead: [ 1, "<table>", "</table>" ], - tr: [ 2, "<table><tbody>", "</tbody></table>" ], - td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], - col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], - area: [ 1, "<map>", "</map>" ], - _default: [ 0, "", "" ] - }; - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// IE can't serialize <link> and <script> tags normally -if ( !jQuery.support.htmlSerialize ) { - wrapMap._default = [ 1, "div<div>", "</div>" ]; -} - -jQuery.fn.extend({ - text: function( text ) { - if ( jQuery.isFunction(text) ) { - return this.each(function(i) { - var self = jQuery( this ); - - self.text( text.call(this, i, self.text()) ); - }); - } - - if ( typeof text !== "object" && text !== undefined ) { - return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); - } - - return jQuery.text( this ); - }, - - wrapAll: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each(function(i) { - jQuery(this).wrapAll( html.call(this, i) ); - }); - } - - if ( this[0] ) { - // The elements to wrap the target around - var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); - - if ( this[0].parentNode ) { - wrap.insertBefore( this[0] ); - } - - wrap.map(function() { - var elem = this; - - while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { - elem = elem.firstChild; - } - - return elem; - }).append(this); - } - - return this; - }, - - wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each(function(i) { - jQuery(this).wrapInner( html.call(this, i) ); - }); - } - - return this.each(function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - }); - }, - - wrap: function( html ) { - return this.each(function() { - jQuery( this ).wrapAll( html ); - }); - }, - - unwrap: function() { - return this.parent().each(function() { - if ( !jQuery.nodeName( this, "body" ) ) { - jQuery( this ).replaceWith( this.childNodes ); - } - }).end(); - }, - - append: function() { - return this.domManip(arguments, true, function( elem ) { - if ( this.nodeType === 1 ) { - this.appendChild( elem ); - } - }); - }, - - prepend: function() { - return this.domManip(arguments, true, function( elem ) { - if ( this.nodeType === 1 ) { - this.insertBefore( elem, this.firstChild ); - } - }); - }, - - before: function() { - if ( this[0] && this[0].parentNode ) { - return this.domManip(arguments, false, function( elem ) { - this.parentNode.insertBefore( elem, this ); - }); - } else if ( arguments.length ) { - var set = jQuery(arguments[0]); - set.push.apply( set, this.toArray() ); - return this.pushStack( set, "before", arguments ); - } - }, - - after: function() { - if ( this[0] && this[0].parentNode ) { - return this.domManip(arguments, false, function( elem ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - }); - } else if ( arguments.length ) { - var set = this.pushStack( this, "after", arguments ); - set.push.apply( set, jQuery(arguments[0]).toArray() ); - return set; - } - }, - - // keepData is for internal use only--do not document - remove: function( selector, keepData ) { - for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { - if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { - if ( !keepData && elem.nodeType === 1 ) { - jQuery.cleanData( elem.getElementsByTagName("*") ); - jQuery.cleanData( [ elem ] ); - } - - if ( elem.parentNode ) { - elem.parentNode.removeChild( elem ); - } - } - } - - return this; - }, - - empty: function() { - for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( elem.getElementsByTagName("*") ); - } - - // Remove any remaining nodes - while ( elem.firstChild ) { - elem.removeChild( elem.firstChild ); - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function () { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - }); - }, - - html: function( value ) { - if ( value === undefined ) { - return this[0] && this[0].nodeType === 1 ? - this[0].innerHTML.replace(rinlinejQuery, "") : - null; - - // See if we can take a shortcut and just use innerHTML - } else if ( typeof value === "string" && !rnocache.test( value ) && - (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && - !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { - - value = value.replace(rxhtmlTag, "<$1></$2>"); - - try { - for ( var i = 0, l = this.length; i < l; i++ ) { - // Remove element nodes and prevent memory leaks - if ( this[i].nodeType === 1 ) { - jQuery.cleanData( this[i].getElementsByTagName("*") ); - this[i].innerHTML = value; - } - } - - // If using innerHTML throws an exception, use the fallback method - } catch(e) { - this.empty().append( value ); - } - - } else if ( jQuery.isFunction( value ) ) { - this.each(function(i){ - var self = jQuery( this ); - - self.html( value.call(this, i, self.html()) ); - }); - - } else { - this.empty().append( value ); - } - - return this; - }, - - replaceWith: function( value ) { - if ( this[0] && this[0].parentNode ) { - // Make sure that the elements are removed from the DOM before they are inserted - // this can help fix replacing a parent with child elements - if ( jQuery.isFunction( value ) ) { - return this.each(function(i) { - var self = jQuery(this), old = self.html(); - self.replaceWith( value.call( this, i, old ) ); - }); - } - - if ( typeof value !== "string" ) { - value = jQuery( value ).detach(); - } - - return this.each(function() { - var next = this.nextSibling, - parent = this.parentNode; - - jQuery( this ).remove(); - - if ( next ) { - jQuery(next).before( value ); - } else { - jQuery(parent).append( value ); - } - }); - } else { - return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ); - } - }, - - detach: function( selector ) { - return this.remove( selector, true ); - }, - - domManip: function( args, table, callback ) { - var results, first, fragment, parent, - value = args[0], - scripts = []; - - // We can't cloneNode fragments that contain checked, in WebKit - if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { - return this.each(function() { - jQuery(this).domManip( args, table, callback, true ); - }); - } - - if ( jQuery.isFunction(value) ) { - return this.each(function(i) { - var self = jQuery(this); - args[0] = value.call(this, i, table ? self.html() : undefined); - self.domManip( args, table, callback ); - }); - } - - if ( this[0] ) { - parent = value && value.parentNode; - - // If we're in a fragment, just use that instead of building a new one - if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { - results = { fragment: parent }; - - } else { - results = jQuery.buildFragment( args, this, scripts ); - } - - fragment = results.fragment; - - if ( fragment.childNodes.length === 1 ) { - first = fragment = fragment.firstChild; - } else { - first = fragment.firstChild; - } - - if ( first ) { - table = table && jQuery.nodeName( first, "tr" ); - - for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) { - callback.call( - table ? - root(this[i], first) : - this[i], - // Make sure that we do not leak memory by inadvertently discarding - // the original fragment (which might have attached data) instead of - // using it; in addition, use the original fragment object for the last - // item instead of first because it can end up being emptied incorrectly - // in certain situations (Bug #8070). - // Fragments from the fragment cache must always be cloned and never used - // in place. - results.cacheable || (l > 1 && i < lastIndex) ? - jQuery.clone( fragment, true, true ) : - fragment - ); - } - } - - if ( scripts.length ) { - jQuery.each( scripts, evalScript ); - } - } - - return this; - } -}); - -function root( elem, cur ) { - return jQuery.nodeName(elem, "table") ? - (elem.getElementsByTagName("tbody")[0] || - elem.appendChild(elem.ownerDocument.createElement("tbody"))) : - elem; -} - -function cloneCopyEvent( src, dest ) { - - if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { - return; - } - - var internalKey = jQuery.expando, - oldData = jQuery.data( src ), - curData = jQuery.data( dest, oldData ); - - // Switch to use the internal data object, if it exists, for the next - // stage of data copying - if ( (oldData = oldData[ internalKey ]) ) { - var events = oldData.events; - curData = curData[ internalKey ] = jQuery.extend({}, oldData); - - if ( events ) { - delete curData.handle; - curData.events = {}; - - for ( var type in events ) { - for ( var i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data ); - } - } - } - } -} - -function cloneFixAttributes(src, dest) { - // We do not need to do anything for non-Elements - if ( dest.nodeType !== 1 ) { - return; - } - - var nodeName = dest.nodeName.toLowerCase(); - - // clearAttributes removes the attributes, which we don't want, - // but also removes the attachEvent events, which we *do* want - dest.clearAttributes(); - - // mergeAttributes, in contrast, only merges back on the - // original attributes, not the events - dest.mergeAttributes(src); - - // IE6-8 fail to clone children inside object elements that use - // the proprietary classid attribute value (rather than the type - // attribute) to identify the type of content to display - if ( nodeName === "object" ) { - dest.outerHTML = src.outerHTML; - - } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { - // IE6-8 fails to persist the checked state of a cloned checkbox - // or radio button. Worse, IE6-7 fail to give the cloned element - // a checked appearance if the defaultChecked value isn't also set - if ( src.checked ) { - dest.defaultChecked = dest.checked = src.checked; - } - - // IE6-7 get confused and end up setting the value of a cloned - // checkbox/radio button to an empty string instead of "on" - if ( dest.value !== src.value ) { - dest.value = src.value; - } - - // IE6-8 fails to return the selected option to the default selected - // state when cloning options - } else if ( nodeName === "option" ) { - dest.selected = src.defaultSelected; - - // IE6-8 fails to set the defaultValue to the correct value when - // cloning other types of input fields - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } - - // Event data gets referenced instead of copied if the expando - // gets copied too - dest.removeAttribute( jQuery.expando ); -} - -jQuery.buildFragment = function( args, nodes, scripts ) { - var fragment, cacheable, cacheresults, - doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document); - - // Only cache "small" (1/2 KB) HTML strings that are associated with the main document - // Cloning options loses the selected state, so don't cache them - // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment - // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache - if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && - args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { - - cacheable = true; - cacheresults = jQuery.fragments[ args[0] ]; - if ( cacheresults ) { - if ( cacheresults !== 1 ) { - fragment = cacheresults; - } - } - } - - if ( !fragment ) { - fragment = doc.createDocumentFragment(); - jQuery.clean( args, doc, fragment, scripts ); - } - - if ( cacheable ) { - jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1; - } - - return { fragment: fragment, cacheable: cacheable }; -}; - -jQuery.fragments = {}; - -jQuery.each({ - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var ret = [], - insert = jQuery( selector ), - parent = this.length === 1 && this[0].parentNode; - - if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { - insert[ original ]( this[0] ); - return this; - - } else { - for ( var i = 0, l = insert.length; i < l; i++ ) { - var elems = (i > 0 ? this.clone(true) : this).get(); - jQuery( insert[i] )[ original ]( elems ); - ret = ret.concat( elems ); - } - - return this.pushStack( ret, name, insert.selector ); - } - }; -}); - -function getAll( elem ) { - if ( "getElementsByTagName" in elem ) { - return elem.getElementsByTagName( "*" ); - - } else if ( "querySelectorAll" in elem ) { - return elem.querySelectorAll( "*" ); - - } else { - return []; - } -} - -jQuery.extend({ - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var clone = elem.cloneNode(true), - srcElements, - destElements, - i; - - if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && - (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { - // IE copies events bound via attachEvent when using cloneNode. - // Calling detachEvent on the clone will also remove the events - // from the original. In order to get around this, we use some - // proprietary methods to clear the events. Thanks to MooTools - // guys for this hotness. - - cloneFixAttributes( elem, clone ); - - // Using Sizzle here is crazy slow, so we use getElementsByTagName - // instead - srcElements = getAll( elem ); - destElements = getAll( clone ); - - // Weird iteration because IE will replace the length property - // with an element if you are cloning the body and one of the - // elements on the page has a name or id of "length" - for ( i = 0; srcElements[i]; ++i ) { - cloneFixAttributes( srcElements[i], destElements[i] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - cloneCopyEvent( elem, clone ); - - if ( deepDataAndEvents ) { - srcElements = getAll( elem ); - destElements = getAll( clone ); - - for ( i = 0; srcElements[i]; ++i ) { - cloneCopyEvent( srcElements[i], destElements[i] ); - } - } - } - - // Return the cloned set - return clone; -}, - clean: function( elems, context, fragment, scripts ) { - context = context || document; - - // !context.createElement fails in IE with an error but returns typeof 'object' - if ( typeof context.createElement === "undefined" ) { - context = context.ownerDocument || context[0] && context[0].ownerDocument || document; - } - - var ret = []; - - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { - if ( typeof elem === "number" ) { - elem += ""; - } - - if ( !elem ) { - continue; - } - - // Convert html string into DOM nodes - if ( typeof elem === "string" && !rhtml.test( elem ) ) { - elem = context.createTextNode( elem ); - - } else if ( typeof elem === "string" ) { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(rxhtmlTag, "<$1></$2>"); - - // Trim whitespace, otherwise indexOf won't work as expected - var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), - wrap = wrapMap[ tag ] || wrapMap._default, - depth = wrap[0], - div = context.createElement("div"); - - // Go to html and back, then peel off extra wrappers - div.innerHTML = wrap[1] + elem + wrap[2]; - - // Move to the right depth - while ( depth-- ) { - div = div.lastChild; - } - - // Remove IE's autoinserted <tbody> from table fragments - if ( !jQuery.support.tbody ) { - - // String was a <table>, *may* have spurious <tbody> - var hasBody = rtbody.test(elem), - tbody = tag === "table" && !hasBody ? - div.firstChild && div.firstChild.childNodes : - - // String was a bare <thead> or <tfoot> - wrap[1] === "<table>" && !hasBody ? - div.childNodes : - []; - - for ( var j = tbody.length - 1; j >= 0 ; --j ) { - if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { - tbody[ j ].parentNode.removeChild( tbody[ j ] ); - } - } - - } - - // IE completely kills leading whitespace when innerHTML is used - if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { - div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); - } - - elem = div.childNodes; - } - - if ( elem.nodeType ) { - ret.push( elem ); - } else { - ret = jQuery.merge( ret, elem ); - } - } - - if ( fragment ) { - for ( i = 0; ret[i]; i++ ) { - if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { - scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); - - } else { - if ( ret[i].nodeType === 1 ) { - ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); - } - fragment.appendChild( ret[i] ); - } - } - } - - return ret; - }, - - cleanData: function( elems ) { - var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special, - deleteExpando = jQuery.support.deleteExpando; - - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { - if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { - continue; - } - - id = elem[ jQuery.expando ]; - - if ( id ) { - data = cache[ id ] && cache[ id ][ internalKey ]; - - if ( data && data.events ) { - for ( var type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - - // Null the DOM reference to avoid IE6/7/8 leak (#7054) - if ( data.handle ) { - data.handle.elem = null; - } - } - - if ( deleteExpando ) { - delete elem[ jQuery.expando ]; - - } else if ( elem.removeAttribute ) { - elem.removeAttribute( jQuery.expando ); - } - - delete cache[ id ]; - } - } - } -}); - -function evalScript( i, elem ) { - if ( elem.src ) { - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - } else { - jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); - } - - if ( elem.parentNode ) { - elem.parentNode.removeChild( elem ); - } -} - - - - -var ralpha = /alpha\([^)]*\)/i, - ropacity = /opacity=([^)]*)/, - rdashAlpha = /-([a-z])/ig, - rupper = /([A-Z])/g, - rnumpx = /^-?\d+(?:px)?$/i, - rnum = /^-?\d/, - - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssWidth = [ "Left", "Right" ], - cssHeight = [ "Top", "Bottom" ], - curCSS, - - getComputedStyle, - currentStyle, - - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; - -jQuery.fn.css = function( name, value ) { - // Setting 'undefined' is a no-op - if ( arguments.length === 2 && value === undefined ) { - return this; - } - - return jQuery.access( this, name, value, true, function( elem, name, value ) { - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }); -}; - -jQuery.extend({ - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity", "opacity" ); - return ret === "" ? "1" : ret; - - } else { - return elem.style.opacity; - } - } - } - }, - - // Exclude the following css properties to add px - cssNumber: { - "zIndex": true, - "fontWeight": true, - "opacity": true, - "zoom": true, - "lineHeight": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: { - // normalize float css property - "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" - }, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, origName = jQuery.camelCase( name ), - style = elem.style, hooks = jQuery.cssHooks[ origName ]; - - name = jQuery.cssProps[ origName ] || origName; - - // Check if we're setting a value - if ( value !== undefined ) { - // Make sure that NaN and null values aren't set. See: #7116 - if ( typeof value === "number" && isNaN( value ) || value == null ) { - return; - } - - // If a number was passed in, add 'px' to the (except for certain CSS properties) - if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) { - value += "px"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) { - // Wrapped to prevent IE from throwing errors when 'invalid' values are provided - // Fixes bug #5509 - try { - style[ name ] = value; - } catch(e) {} - } - - } else { - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra ) { - // Make sure that we're working with the right name - var ret, origName = jQuery.camelCase( name ), - hooks = jQuery.cssHooks[ origName ]; - - name = jQuery.cssProps[ origName ] || origName; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { - return ret; - - // Otherwise, if a way to get the computed value exists, use that - } else if ( curCSS ) { - return curCSS( elem, name, origName ); - } - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback ) { - var old = {}; - - // Remember the old values, and insert the new ones - for ( var name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - callback.call( elem ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - }, - - camelCase: function( string ) { - return string.replace( rdashAlpha, fcamelCase ); - } -}); - -// DEPRECATED, Use jQuery.css() instead -jQuery.curCSS = jQuery.css; - -jQuery.each(["height", "width"], function( i, name ) { - jQuery.cssHooks[ name ] = { - get: function( elem, computed, extra ) { - var val; - - if ( computed ) { - if ( elem.offsetWidth !== 0 ) { - val = getWH( elem, name, extra ); - - } else { - jQuery.swap( elem, cssShow, function() { - val = getWH( elem, name, extra ); - }); - } - - if ( val <= 0 ) { - val = curCSS( elem, name, name ); - - if ( val === "0px" && currentStyle ) { - val = currentStyle( elem, name, name ); - } - - if ( val != null ) { - // Should return "auto" instead of 0, use 0 for - // temporary backwards-compat - return val === "" || val === "auto" ? "0px" : val; - } - } - - if ( val < 0 || val == null ) { - val = elem.style[ name ]; - - // Should return "auto" instead of 0, use 0 for - // temporary backwards-compat - return val === "" || val === "auto" ? "0px" : val; - } - - return typeof val === "string" ? val : val + "px"; - } - }, - - set: function( elem, value ) { - if ( rnumpx.test( value ) ) { - // ignore negative width and height values #1599 - value = parseFloat(value); - - if ( value >= 0 ) { - return value + "px"; - } - - } else { - return value; - } - } - }; -}); - -if ( !jQuery.support.opacity ) { - jQuery.cssHooks.opacity = { - get: function( elem, computed ) { - // IE uses filters for opacity - return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ? - (parseFloat(RegExp.$1) / 100) + "" : - computed ? "1" : ""; - }, - - set: function( elem, value ) { - var style = elem.style; - - // IE has trouble with opacity if it does not have layout - // Force it by setting the zoom level - style.zoom = 1; - - // Set the alpha filter to set the opacity - var opacity = jQuery.isNaN(value) ? - "" : - "alpha(opacity=" + value * 100 + ")", - filter = style.filter || ""; - - style.filter = ralpha.test(filter) ? - filter.replace(ralpha, opacity) : - style.filter + ' ' + opacity; - } - }; -} - -if ( document.defaultView && document.defaultView.getComputedStyle ) { - getComputedStyle = function( elem, newName, name ) { - var ret, defaultView, computedStyle; - - name = name.replace( rupper, "-$1" ).toLowerCase(); - - if ( !(defaultView = elem.ownerDocument.defaultView) ) { - return undefined; - } - - if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) { - ret = computedStyle.getPropertyValue( name ); - if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { - ret = jQuery.style( elem, name ); - } - } - - return ret; - }; -} - -if ( document.documentElement.currentStyle ) { - currentStyle = function( elem, name ) { - var left, - ret = elem.currentStyle && elem.currentStyle[ name ], - rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ], - style = elem.style; - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { - // Remember the original values - left = style.left; - - // Put in the new values to get a computed value out - if ( rsLeft ) { - elem.runtimeStyle.left = elem.currentStyle.left; - } - style.left = name === "fontSize" ? "1em" : (ret || 0); - ret = style.pixelLeft + "px"; - - // Revert the changed values - style.left = left; - if ( rsLeft ) { - elem.runtimeStyle.left = rsLeft; - } - } - - return ret === "" ? "auto" : ret; - }; -} - -curCSS = getComputedStyle || currentStyle; - -function getWH( elem, name, extra ) { - var which = name === "width" ? cssWidth : cssHeight, - val = name === "width" ? elem.offsetWidth : elem.offsetHeight; - - if ( extra === "border" ) { - return val; - } - - jQuery.each( which, function() { - if ( !extra ) { - val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0; - } - - if ( extra === "margin" ) { - val += parseFloat(jQuery.css( elem, "margin" + this )) || 0; - - } else { - val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0; - } - }); - - return val; -} - -if ( jQuery.expr && jQuery.expr.filters ) { - jQuery.expr.filters.hidden = function( elem ) { - var width = elem.offsetWidth, - height = elem.offsetHeight; - - return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none"); - }; - - jQuery.expr.filters.visible = function( elem ) { - return !jQuery.expr.filters.hidden( elem ); - }; -} - - - - -var r20 = /%20/g, - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rhash = /#.*$/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL - rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /(?:^file|^widget|\-extension):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - rquery = /\?/, - rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, - rselectTextarea = /^(?:select|textarea)/i, - rspacesAjax = /\s+/, - rts = /([?&])_=[^&]*/, - rucHeaders = /(^|\-)([a-z])/g, - rucHeadersFunc = function( _, $1, $2 ) { - return $1 + $2.toUpperCase(); - }, - rurl = /^([\w\+\.\-]+:)\/\/([^\/?#:]*)(?::(\d+))?/, - - // Keep a copy of the old load method - _load = jQuery.fn.load, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Document location - ajaxLocation, - - // Document location segments - ajaxLocParts; - -// #8138, IE may throw an exception when accessing -// a field from document.location if document.domain has been set -try { - ajaxLocation = document.location.href; -} catch( e ) { - // Use the href attribute of an A element - // since IE will modify it given document.location - ajaxLocation = document.createElement( "a" ); - ajaxLocation.href = ""; - ajaxLocation = ajaxLocation.href; -} - -// Segment location into parts -ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ); - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - if ( jQuery.isFunction( func ) ) { - var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), - i = 0, - length = dataTypes.length, - dataType, - list, - placeBefore; - - // For each dataType in the dataTypeExpression - for(; i < length; i++ ) { - dataType = dataTypes[ i ]; - // We control if we're asked to add before - // any existing element - placeBefore = /^\+/.test( dataType ); - if ( placeBefore ) { - dataType = dataType.substr( 1 ) || "*"; - } - list = structure[ dataType ] = structure[ dataType ] || []; - // then we add to the structure accordingly - list[ placeBefore ? "unshift" : "push" ]( func ); - } - } - }; -} - -//Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, - dataType /* internal */, inspected /* internal */ ) { - - dataType = dataType || options.dataTypes[ 0 ]; - inspected = inspected || {}; - - inspected[ dataType ] = true; - - var list = structure[ dataType ], - i = 0, - length = list ? list.length : 0, - executeOnly = ( structure === prefilters ), - selection; - - for(; i < length && ( executeOnly || !selection ); i++ ) { - selection = list[ i ]( options, originalOptions, jqXHR ); - // If we got redirected to another dataType - // we try there if executing only and not done already - if ( typeof selection === "string" ) { - if ( !executeOnly || inspected[ selection ] ) { - selection = undefined; - } else { - options.dataTypes.unshift( selection ); - selection = inspectPrefiltersOrTransports( - structure, options, originalOptions, jqXHR, selection, inspected ); - } - } - } - // If we're only executing or nothing was selected - // we try the catchall dataType if not done already - if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { - selection = inspectPrefiltersOrTransports( - structure, options, originalOptions, jqXHR, "*", inspected ); - } - // unnecessary when only executing (prefilters) - // but it'll be ignored by the caller in that case - return selection; -} - -jQuery.fn.extend({ - load: function( url, params, callback ) { - if ( typeof url !== "string" && _load ) { - return _load.apply( this, arguments ); - - // Don't do a request if no elements are being requested - } else if ( !this.length ) { - return this; - } - - var off = url.indexOf( " " ); - if ( off >= 0 ) { - var selector = url.slice( off, url.length ); - url = url.slice( 0, off ); - } - - // Default to a GET request - var type = "GET"; - - // If the second parameter was provided - if ( params ) { - // If it's a function - if ( jQuery.isFunction( params ) ) { - // We assume that it's the callback - callback = params; - params = undefined; - - // Otherwise, build a param string - } else if ( typeof params === "object" ) { - params = jQuery.param( params, jQuery.ajaxSettings.traditional ); - type = "POST"; - } - } - - var self = this; - - // Request the remote document - jQuery.ajax({ - url: url, - type: type, - dataType: "html", - data: params, - // Complete callback (responseText is used internally) - complete: function( jqXHR, status, responseText ) { - // Store the response as specified by the jqXHR object - responseText = jqXHR.responseText; - // If successful, inject the HTML into all the matched elements - if ( jqXHR.isResolved() ) { - // #4825: Get the actual response in case - // a dataFilter is present in ajaxSettings - jqXHR.done(function( r ) { - responseText = r; - }); - // See if a selector was specified - self.html( selector ? - // Create a dummy div to hold the results - jQuery("<div>") - // inject the contents of the document in, removing the scripts - // to avoid any 'Permission Denied' errors in IE - .append(responseText.replace(rscript, "")) - - // Locate the specified elements - .find(selector) : - - // If not, just inject the full result - responseText ); - } - - if ( callback ) { - self.each( callback, [ responseText, status, jqXHR ] ); - } - } - }); - - return this; - }, - - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - - serializeArray: function() { - return this.map(function(){ - return this.elements ? jQuery.makeArray( this.elements ) : this; - }) - .filter(function(){ - return this.name && !this.disabled && - ( this.checked || rselectTextarea.test( this.nodeName ) || - rinput.test( this.type ) ); - }) - .map(function( i, elem ){ - var val = jQuery( this ).val(); - - return val == null ? - null : - jQuery.isArray( val ) ? - jQuery.map( val, function( val, i ){ - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - }) : - { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - }).get(); - } -}); - -// Attach a bunch of functions for handling common AJAX events -jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ - jQuery.fn[ o ] = function( f ){ - return this.bind( o, f ); - }; -} ); - -jQuery.each( [ "get", "post" ], function( i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - // shift arguments if data argument was omitted - if ( jQuery.isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - return jQuery.ajax({ - type: method, - url: url, - data: data, - success: callback, - dataType: type - }); - }; -} ); - -jQuery.extend({ - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function ( target, settings ) { - if ( !settings ) { - // Only one parameter, we extend ajaxSettings - settings = target; - target = jQuery.extend( true, jQuery.ajaxSettings, settings ); - } else { - // target was provided, we extend into it - jQuery.extend( true, target, jQuery.ajaxSettings, settings ); - } - // Flatten fields we don't want deep extended - for( var field in { context: 1, url: 1 } ) { - if ( field in settings ) { - target[ field ] = settings[ field ]; - } else if( field in jQuery.ajaxSettings ) { - target[ field ] = jQuery.ajaxSettings[ field ]; - } - } - return target; - }, - - ajaxSettings: { - url: ajaxLocation, - isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), - global: true, - type: "GET", - contentType: "application/x-www-form-urlencoded", - processData: true, - async: true, - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - traditional: false, - headers: {}, - crossDomain: null, - */ - - accepts: { - xml: "application/xml, text/xml", - html: "text/html", - text: "text/plain", - json: "application/json, text/javascript", - "*": "*/*" - }, - - contents: { - xml: /xml/, - html: /html/, - json: /json/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText" - }, - - // List of data converters - // 1) key format is "source_type destination_type" (a single space in-between) - // 2) the catchall symbol "*" can be used for source_type - converters: { - - // Convert anything to text - "* text": window.String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": jQuery.parseJSON, - - // Parse text as xml - "text xml": jQuery.parseXML - } - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - // Callbacks context - callbackContext = s.context || s, - // Context for global events - // It's the callbackContext if one was provided in the options - // and if it's a DOM node or a jQuery collection - globalEventContext = callbackContext !== s && - ( callbackContext.nodeType || callbackContext instanceof jQuery ) ? - jQuery( callbackContext ) : jQuery.event, - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery._Deferred(), - // Status-dependent callbacks - statusCode = s.statusCode || {}, - // ifModified key - ifModifiedKey, - // Headers (they are sent all at once) - requestHeaders = {}, - // Response headers - responseHeadersString, - responseHeaders, - // transport - transport, - // timeout handle - timeoutTimer, - // Cross-domain detection vars - parts, - // The jqXHR state - state = 0, - // To know if global events are to be dispatched - fireGlobals, - // Loop variable - i, - // Fake xhr - jqXHR = { - - readyState: 0, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( !state ) { - requestHeaders[ name.toLowerCase().replace( rucHeaders, rucHeadersFunc ) ] = value; - } - return this; - }, - - // Raw string - getAllResponseHeaders: function() { - return state === 2 ? responseHeadersString : null; - }, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( state === 2 ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; - } - } - match = responseHeaders[ key.toLowerCase() ]; - } - return match === undefined ? null : match; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( !state ) { - s.mimeType = type; - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - statusText = statusText || "abort"; - if ( transport ) { - transport.abort( statusText ); - } - done( 0, statusText ); - return this; - } - }; - - // Callback for when everything is done - // It is defined here because jslint complains if it is declared - // at the end of the function (which would be more logical and readable) - function done( status, statusText, responses, headers ) { - - // Called once - if ( state === 2 ) { - return; - } - - // State is "done" now - state = 2; - - // Clear timeout if it exists - if ( timeoutTimer ) { - clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status ? 4 : 0; - - var isSuccess, - success, - error, - response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined, - lastModified, - etag; - - // If successful, handle type chaining - if ( status >= 200 && status < 300 || status === 304 ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - - if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) { - jQuery.lastModified[ ifModifiedKey ] = lastModified; - } - if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) { - jQuery.etag[ ifModifiedKey ] = etag; - } - } - - // If not modified - if ( status === 304 ) { - - statusText = "notmodified"; - isSuccess = true; - - // If we have data - } else { - - try { - success = ajaxConvert( s, response ); - statusText = "success"; - isSuccess = true; - } catch(e) { - // We have a parsererror - statusText = "parsererror"; - error = e; - } - } - } else { - // We extract error from statusText - // then normalize statusText and status for non-aborts - error = statusText; - if( !statusText || status ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = statusText; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s] ); - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - // Attach deferreds - deferred.promise( jqXHR ); - jqXHR.success = jqXHR.done; - jqXHR.error = jqXHR.fail; - jqXHR.complete = completeDeferred.done; - - // Status-dependent callbacks - jqXHR.statusCode = function( map ) { - if ( map ) { - var tmp; - if ( state < 2 ) { - for( tmp in map ) { - statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; - } - } else { - tmp = map[ jqXHR.status ]; - jqXHR.then( tmp, tmp ); - } - } - return this; - }; - - // Remove hash character (#7531: and string promotion) - // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) - // We also use the url parameter if available - s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); - - // Extract dataTypes list - s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); - - // Determine if a cross-domain request is in order - if ( !s.crossDomain ) { - parts = rurl.exec( s.url.toLowerCase() ); - s.crossDomain = !!( parts && - ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] || - ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != - ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) - ); - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefiler, stop there - if ( state === 2 ) { - return false; - } - - // We can fire global events as of now if asked to - fireGlobals = s.global; - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // If data is available, append data to url - if ( s.data ) { - s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; - } - - // Get ifModifiedKey before adding the anti-cache parameter - ifModifiedKey = s.url; - - // Add anti-cache in url if needed - if ( s.cache === false ) { - - var ts = jQuery.now(), - // try replacing _= if it is there - ret = s.url.replace( rts, "$1_=" + ts ); - - // if nothing was replaced, add timestamp to the end - s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - requestHeaders[ "Content-Type" ] = s.contentType; - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - ifModifiedKey = ifModifiedKey || s.url; - if ( jQuery.lastModified[ ifModifiedKey ] ) { - requestHeaders[ "If-Modified-Since" ] = jQuery.lastModified[ ifModifiedKey ]; - } - if ( jQuery.etag[ ifModifiedKey ] ) { - requestHeaders[ "If-None-Match" ] = jQuery.etag[ ifModifiedKey ]; - } - } - - // Set the Accepts header for the server, depending on the dataType - requestHeaders.Accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? - s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : - s.accepts[ "*" ]; - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { - // Abort if not done already - jqXHR.abort(); - return false; - - } - - // Install callbacks on deferreds - for ( i in { success: 1, error: 1, complete: 1 } ) { - jqXHR[ i ]( s[ i ] ); - } - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = setTimeout( function(){ - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - state = 1; - transport.send( requestHeaders, done ); - } catch (e) { - // Propagate exception as error if not done - if ( status < 2 ) { - done( -1, e ); - // Simply rethrow otherwise - } else { - jQuery.error( e ); - } - } - } - - return jqXHR; - }, - - // Serialize an array of form elements or a set of - // key/values into a query string - param: function( a, traditional ) { - var s = [], - add = function( key, value ) { - // If value is a function, invoke it and return its value - value = jQuery.isFunction( value ) ? value() : value; - s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); - }; - - // Set traditional to true for jQuery <= 1.3.2 behavior. - if ( traditional === undefined ) { - traditional = jQuery.ajaxSettings.traditional; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( var prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ).replace( r20, "+" ); - } -}); - -function buildParams( prefix, obj, traditional, add ) { - if ( jQuery.isArray( obj ) && obj.length ) { - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - // If array item is non-scalar (array or object), encode its - // numeric index to resolve deserialization ambiguity issues. - // Note that rack (as of 1.0.0) can't currently deserialize - // nested arrays properly, and attempting to do so may cause - // a server error. Possible fixes are to modify rack's - // deserialization algorithm or to provide an option or flag - // to force array serialization to be shallow. - buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); - } - }); - - } else if ( !traditional && obj != null && typeof obj === "object" ) { - // If we see an array here, it is empty and should be treated as an empty - // object - if ( jQuery.isArray( obj ) || jQuery.isEmptyObject( obj ) ) { - add( prefix, "" ); - - // Serialize object item. - } else { - for ( var name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - } - - } else { - // Serialize scalar item. - add( prefix, obj ); - } -} - -// This is still on the jQuery object... for now -// Want to move this to jQuery.ajax some day -jQuery.extend({ - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {} - -}); - -/* Handles responses to an ajax request: - * - sets all responseXXX fields accordingly - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var contents = s.contents, - dataTypes = s.dataTypes, - responseFields = s.responseFields, - ct, - type, - finalDataType, - firstDataType; - - // Fill responseXXX fields - for( type in responseFields ) { - if ( type in responses ) { - jqXHR[ responseFields[type] ] = responses[ type ]; - } - } - - // Remove auto dataType and get content-type in the process - while( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "content-type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -// Chain conversions given the request and the original response -function ajaxConvert( s, response ) { - - // Apply the dataFilter if provided - if ( s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - var dataTypes = s.dataTypes, - converters = {}, - i, - key, - length = dataTypes.length, - tmp, - // Current and previous dataTypes - current = dataTypes[ 0 ], - prev, - // Conversion expression - conversion, - // Conversion function - conv, - // Conversion functions (transitive conversion) - conv1, - conv2; - - // For each dataType in the chain - for( i = 1; i < length; i++ ) { - - // Create converters map - // with lowercased keys - if ( i === 1 ) { - for( key in s.converters ) { - if( typeof key === "string" ) { - converters[ key.toLowerCase() ] = s.converters[ key ]; - } - } - } - - // Get the dataTypes - prev = current; - current = dataTypes[ i ]; - - // If current is auto dataType, update it to prev - if( current === "*" ) { - current = prev; - // If no auto and dataTypes are actually different - } else if ( prev !== "*" && prev !== current ) { - - // Get the converter - conversion = prev + " " + current; - conv = converters[ conversion ] || converters[ "* " + current ]; - - // If there is no direct converter, search transitively - if ( !conv ) { - conv2 = undefined; - for( conv1 in converters ) { - tmp = conv1.split( " " ); - if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { - conv2 = converters[ tmp[1] + " " + current ]; - if ( conv2 ) { - conv1 = converters[ conv1 ]; - if ( conv1 === true ) { - conv = conv2; - } else if ( conv2 === true ) { - conv = conv1; - } - break; - } - } - } - } - // If we found no converter, dispatch an error - if ( !( conv || conv2 ) ) { - jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); - } - // If found converter is not an equivalence - if ( conv !== true ) { - // Convert with 1 or 2 converters accordingly - response = conv ? conv( response ) : conv2( conv1(response) ); - } - } - } - return response; -} - - - - -var jsc = jQuery.now(), - jsre = /(\=)\?(&|$)|()\?\?()/i; - -// Default jsonp settings -jQuery.ajaxSetup({ - jsonp: "callback", - jsonpCallback: function() { - return jQuery.expando + "_" + ( jsc++ ); - } -}); - -// Detect, normalize options and install callbacks for jsonp requests -jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { - - var dataIsString = ( typeof s.data === "string" ); - - if ( s.dataTypes[ 0 ] === "jsonp" || - originalSettings.jsonpCallback || - originalSettings.jsonp != null || - s.jsonp !== false && ( jsre.test( s.url ) || - dataIsString && jsre.test( s.data ) ) ) { - - var responseContainer, - jsonpCallback = s.jsonpCallback = - jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, - previous = window[ jsonpCallback ], - url = s.url, - data = s.data, - replace = "$1" + jsonpCallback + "$2", - cleanUp = function() { - // Set callback back to previous value - window[ jsonpCallback ] = previous; - // Call if it was a function and we have a response - if ( responseContainer && jQuery.isFunction( previous ) ) { - window[ jsonpCallback ]( responseContainer[ 0 ] ); - } - }; - - if ( s.jsonp !== false ) { - url = url.replace( jsre, replace ); - if ( s.url === url ) { - if ( dataIsString ) { - data = data.replace( jsre, replace ); - } - if ( s.data === data ) { - // Add callback manually - url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; - } - } - } - - s.url = url; - s.data = data; - - // Install callback - window[ jsonpCallback ] = function( response ) { - responseContainer = [ response ]; - }; - - // Install cleanUp function - jqXHR.then( cleanUp, cleanUp ); - - // Use data converter to retrieve json after script execution - s.converters["script json"] = function() { - if ( !responseContainer ) { - jQuery.error( jsonpCallback + " was not called" ); - } - return responseContainer[ 0 ]; - }; - - // force json dataType - s.dataTypes[ 0 ] = "json"; - - // Delegate to script - return "script"; - } -} ); - - - - -// Install script dataType -jQuery.ajaxSetup({ - accepts: { - script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /javascript|ecmascript/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -}); - -// Handle cache's special case and global -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - s.global = false; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function(s) { - - // This transport only deals with cross domain requests - if ( s.crossDomain ) { - - var script, - head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; - - return { - - send: function( _, callback ) { - - script = document.createElement( "script" ); - - script.async = "async"; - - if ( s.scriptCharset ) { - script.charset = s.scriptCharset; - } - - script.src = s.url; - - // Attach handlers for all browsers - script.onload = script.onreadystatechange = function( _, isAbort ) { - - if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) { - - // Handle memory leak in IE - script.onload = script.onreadystatechange = null; - - // Remove the script - if ( head && script.parentNode ) { - head.removeChild( script ); - } - - // Dereference the script - script = undefined; - - // Callback if not abort - if ( !isAbort ) { - callback( 200, "success" ); - } - } - }; - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709 and #4378). - head.insertBefore( script, head.firstChild ); - }, - - abort: function() { - if ( script ) { - script.onload( 0, 1 ); - } - } - }; - } -} ); - - - - -var // #5280: next active xhr id and list of active xhrs' callbacks - xhrId = jQuery.now(), - xhrCallbacks, - - // XHR used to determine supports properties - testXHR; - -// #5280: Internet Explorer will keep connections alive if we don't abort on unload -function xhrOnUnloadAbort() { - jQuery( window ).unload(function() { - // Abort all pending requests - for ( var key in xhrCallbacks ) { - xhrCallbacks[ key ]( 0, 1 ); - } - }); -} - -// Functions to create xhrs -function createStandardXHR() { - try { - return new window.XMLHttpRequest(); - } catch( e ) {} -} - -function createActiveXHR() { - try { - return new window.ActiveXObject( "Microsoft.XMLHTTP" ); - } catch( e ) {} -} - -// Create the request object -// (This is still attached to ajaxSettings for backward compatibility) -jQuery.ajaxSettings.xhr = window.ActiveXObject ? - /* Microsoft failed to properly - * implement the XMLHttpRequest in IE7 (can't request local files), - * so we use the ActiveXObject when it is available - * Additionally XMLHttpRequest can be disabled in IE7/IE8 so - * we need a fallback. - */ - function() { - return !this.isLocal && createStandardXHR() || createActiveXHR(); - } : - // For all other browsers, use the standard XMLHttpRequest object - createStandardXHR; - -// Test if we can create an xhr object -testXHR = jQuery.ajaxSettings.xhr(); -jQuery.support.ajax = !!testXHR; - -// Does this browser support crossDomain XHR requests -jQuery.support.cors = testXHR && ( "withCredentials" in testXHR ); - -// No need for the temporary xhr anymore -testXHR = undefined; - -// Create transport if the browser can provide an xhr -if ( jQuery.support.ajax ) { - - jQuery.ajaxTransport(function( s ) { - // Cross domain only allowed if supported through XMLHttpRequest - if ( !s.crossDomain || jQuery.support.cors ) { - - var callback; - - return { - send: function( headers, complete ) { - - // Get a new xhr - var xhr = s.xhr(), - handle, - i; - - // Open the socket - // Passing null username, generates a login popup on Opera (#2865) - if ( s.username ) { - xhr.open( s.type, s.url, s.async, s.username, s.password ); - } else { - xhr.open( s.type, s.url, s.async ); - } - - // Apply custom fields if provided - if ( s.xhrFields ) { - for ( i in s.xhrFields ) { - xhr[ i ] = s.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( s.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( s.mimeType ); - } - - // Requested-With header - // Not set for crossDomain requests with no content - // (see why at http://trac.dojotoolkit.org/ticket/9486) - // Won't change header if already provided - if ( !( s.crossDomain && !s.hasContent ) && !headers["X-Requested-With"] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Need an extra try/catch for cross domain requests in Firefox 3 - try { - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - } catch( _ ) {} - - // Do send the request - // This may raise an exception which is actually - // handled in jQuery.ajax (so no try/catch here) - xhr.send( ( s.hasContent && s.data ) || null ); - - // Listener - callback = function( _, isAbort ) { - - var status, - statusText, - responseHeaders, - responses, - xml; - - // Firefox throws exceptions when accessing properties - // of an xhr when a network error occured - // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) - try { - - // Was never called and is aborted or complete - if ( callback && ( isAbort || xhr.readyState === 4 ) ) { - - // Only called once - callback = undefined; - - // Do not keep as active anymore - if ( handle ) { - xhr.onreadystatechange = jQuery.noop; - delete xhrCallbacks[ handle ]; - } - - // If it's an abort - if ( isAbort ) { - // Abort it manually if needed - if ( xhr.readyState !== 4 ) { - xhr.abort(); - } - } else { - status = xhr.status; - responseHeaders = xhr.getAllResponseHeaders(); - responses = {}; - xml = xhr.responseXML; - - // Construct response list - if ( xml && xml.documentElement /* #4958 */ ) { - responses.xml = xml; - } - responses.text = xhr.responseText; - - // Firefox throws an exception when accessing - // statusText for faulty cross-domain requests - try { - statusText = xhr.statusText; - } catch( e ) { - // We normalize with Webkit giving an empty statusText - statusText = ""; - } - - // Filter status for non standard behaviors - - // If the request is local and we have data: assume a success - // (success with no data won't get notified, that's the best we - // can do given current implementations) - if ( !status && s.isLocal && !s.crossDomain ) { - status = responses.text ? 200 : 404; - // IE - #1450: sometimes returns 1223 when it should be 204 - } else if ( status === 1223 ) { - status = 204; - } - } - } - } catch( firefoxAccessException ) { - if ( !isAbort ) { - complete( -1, firefoxAccessException ); - } - } - - // Call complete if needed - if ( responses ) { - complete( status, statusText, responses, responseHeaders ); - } - }; - - // if we're in sync mode or it's in cache - // and has been retrieved directly (IE6 & IE7) - // we need to manually fire the callback - if ( !s.async || xhr.readyState === 4 ) { - callback(); - } else { - // Create the active xhrs callbacks list if needed - // and attach the unload handler - if ( !xhrCallbacks ) { - xhrCallbacks = {}; - xhrOnUnloadAbort(); - } - // Add to list of active xhrs callbacks - handle = xhrId++; - xhr.onreadystatechange = xhrCallbacks[ handle ] = callback; - } - }, - - abort: function() { - if ( callback ) { - callback(0,1); - } - } - }; - } - }); -} - - - - -var elemdisplay = {}, - rfxtypes = /^(?:toggle|show|hide)$/, - rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, - timerId, - fxAttrs = [ - // height animations - [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], - // width animations - [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], - // opacity animations - [ "opacity" ] - ]; - -jQuery.fn.extend({ - show: function( speed, easing, callback ) { - var elem, display; - - if ( speed || speed === 0 ) { - return this.animate( genFx("show", 3), speed, easing, callback); - - } else { - for ( var i = 0, j = this.length; i < j; i++ ) { - elem = this[i]; - display = elem.style.display; - - // Reset the inline display of this element to learn if it is - // being hidden by cascaded rules or not - if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { - display = elem.style.display = ""; - } - - // Set elements which have been overridden with display: none - // in a stylesheet to whatever the default browser style is - // for such an element - if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { - jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName)); - } - } - - // Set the display of most of the elements in a second loop - // to avoid the constant reflow - for ( i = 0; i < j; i++ ) { - elem = this[i]; - display = elem.style.display; - - if ( display === "" || display === "none" ) { - elem.style.display = jQuery._data(elem, "olddisplay") || ""; - } - } - - return this; - } - }, - - hide: function( speed, easing, callback ) { - if ( speed || speed === 0 ) { - return this.animate( genFx("hide", 3), speed, easing, callback); - - } else { - for ( var i = 0, j = this.length; i < j; i++ ) { - var display = jQuery.css( this[i], "display" ); - - if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { - jQuery._data( this[i], "olddisplay", display ); - } - } - - // Set the display of the elements in a second loop - // to avoid the constant reflow - for ( i = 0; i < j; i++ ) { - this[i].style.display = "none"; - } - - return this; - } - }, - - // Save the old toggle function - _toggle: jQuery.fn.toggle, - - toggle: function( fn, fn2, callback ) { - var bool = typeof fn === "boolean"; - - if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { - this._toggle.apply( this, arguments ); - - } else if ( fn == null || bool ) { - this.each(function() { - var state = bool ? fn : jQuery(this).is(":hidden"); - jQuery(this)[ state ? "show" : "hide" ](); - }); - - } else { - this.animate(genFx("toggle", 3), fn, fn2, callback); - } - - return this; - }, - - fadeTo: function( speed, to, easing, callback ) { - return this.filter(":hidden").css("opacity", 0).show().end() - .animate({opacity: to}, speed, easing, callback); - }, - - animate: function( prop, speed, easing, callback ) { - var optall = jQuery.speed(speed, easing, callback); - - if ( jQuery.isEmptyObject( prop ) ) { - return this.each( optall.complete ); - } - - return this[ optall.queue === false ? "each" : "queue" ](function() { - // XXX 'this' does not always have a nodeName when running the - // test suite - - var opt = jQuery.extend({}, optall), p, - isElement = this.nodeType === 1, - hidden = isElement && jQuery(this).is(":hidden"), - self = this; - - for ( p in prop ) { - var name = jQuery.camelCase( p ); - - if ( p !== name ) { - prop[ name ] = prop[ p ]; - delete prop[ p ]; - p = name; - } - - if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) { - return opt.complete.call(this); - } - - if ( isElement && ( p === "height" || p === "width" ) ) { - // Make sure that nothing sneaks out - // Record all 3 overflow attributes because IE does not - // change the overflow attribute when overflowX and - // overflowY are set to the same value - opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; - - // Set display property to inline-block for height/width - // animations on inline elements that are having width/height - // animated - if ( jQuery.css( this, "display" ) === "inline" && - jQuery.css( this, "float" ) === "none" ) { - if ( !jQuery.support.inlineBlockNeedsLayout ) { - this.style.display = "inline-block"; - - } else { - var display = defaultDisplay(this.nodeName); - - // inline-level elements accept inline-block; - // block-level elements need to be inline with layout - if ( display === "inline" ) { - this.style.display = "inline-block"; - - } else { - this.style.display = "inline"; - this.style.zoom = 1; - } - } - } - } - - if ( jQuery.isArray( prop[p] ) ) { - // Create (if needed) and add to specialEasing - (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; - prop[p] = prop[p][0]; - } - } - - if ( opt.overflow != null ) { - this.style.overflow = "hidden"; - } - - opt.curAnim = jQuery.extend({}, prop); - - jQuery.each( prop, function( name, val ) { - var e = new jQuery.fx( self, opt, name ); - - if ( rfxtypes.test(val) ) { - e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop ); - - } else { - var parts = rfxnum.exec(val), - start = e.cur(); - - if ( parts ) { - var end = parseFloat( parts[2] ), - unit = parts[3] || ( jQuery.cssNumber[ name ] ? "" : "px" ); - - // We need to compute starting value - if ( unit !== "px" ) { - jQuery.style( self, name, (end || 1) + unit); - start = ((end || 1) / e.cur()) * start; - jQuery.style( self, name, start + unit); - } - - // If a +=/-= token was provided, we're doing a relative animation - if ( parts[1] ) { - end = ((parts[1] === "-=" ? -1 : 1) * end) + start; - } - - e.custom( start, end, unit ); - - } else { - e.custom( start, val, "" ); - } - } - }); - - // For JS strict compliance - return true; - }); - }, - - stop: function( clearQueue, gotoEnd ) { - var timers = jQuery.timers; - - if ( clearQueue ) { - this.queue([]); - } - - this.each(function() { - // go in reverse order so anything added to the queue during the loop is ignored - for ( var i = timers.length - 1; i >= 0; i-- ) { - if ( timers[i].elem === this ) { - if (gotoEnd) { - // force the next step to be the last - timers[i](true); - } - - timers.splice(i, 1); - } - } - }); - - // start the next in the queue if the last step wasn't forced - if ( !gotoEnd ) { - this.dequeue(); - } - - return this; - } - -}); - -function genFx( type, num ) { - var obj = {}; - - jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { - obj[ this ] = type; - }); - - return obj; -} - -// Generate shortcuts for custom animations -jQuery.each({ - slideDown: genFx("show", 1), - slideUp: genFx("hide", 1), - slideToggle: genFx("toggle", 1), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -}); - -jQuery.extend({ - speed: function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { - complete: fn || !fn && easing || - jQuery.isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !jQuery.isFunction(easing) && easing - }; - - opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : - opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default; - - // Queueing - opt.old = opt.complete; - opt.complete = function() { - if ( opt.queue !== false ) { - jQuery(this).dequeue(); - } - if ( jQuery.isFunction( opt.old ) ) { - opt.old.call( this ); - } - }; - - return opt; - }, - - easing: { - linear: function( p, n, firstNum, diff ) { - return firstNum + diff * p; - }, - swing: function( p, n, firstNum, diff ) { - return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; - } - }, - - timers: [], - - fx: function( elem, options, prop ) { - this.options = options; - this.elem = elem; - this.prop = prop; - - if ( !options.orig ) { - options.orig = {}; - } - } - -}); - -jQuery.fx.prototype = { - // Simple function for setting a style value - update: function() { - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); - }, - - // Get the current size - cur: function() { - if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) { - return this.elem[ this.prop ]; - } - - var parsed, - r = jQuery.css( this.elem, this.prop ); - // Empty strings, null, undefined and "auto" are converted to 0, - // complex values such as "rotate(1rad)" are returned as is, - // simple values such as "10px" are parsed to Float. - return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed; - }, - - // Start an animation from one number to another - custom: function( from, to, unit ) { - var self = this, - fx = jQuery.fx; - - this.startTime = jQuery.now(); - this.start = from; - this.end = to; - this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); - this.now = this.start; - this.pos = this.state = 0; - - function t( gotoEnd ) { - return self.step(gotoEnd); - } - - t.elem = this.elem; - - if ( t() && jQuery.timers.push(t) && !timerId ) { - timerId = setInterval(fx.tick, fx.interval); - } - }, - - // Simple 'show' function - show: function() { - // Remember where we started, so that we can go back to it later - this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); - this.options.show = true; - - // Begin the animation - // Make sure that we start at a small width/height to avoid any - // flash of content - this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); - - // Start by showing the element - jQuery( this.elem ).show(); - }, - - // Simple 'hide' function - hide: function() { - // Remember where we started, so that we can go back to it later - this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); - this.options.hide = true; - - // Begin the animation - this.custom(this.cur(), 0); - }, - - // Each step of an animation - step: function( gotoEnd ) { - var t = jQuery.now(), done = true; - - if ( gotoEnd || t >= this.options.duration + this.startTime ) { - this.now = this.end; - this.pos = this.state = 1; - this.update(); - - this.options.curAnim[ this.prop ] = true; - - for ( var i in this.options.curAnim ) { - if ( this.options.curAnim[i] !== true ) { - done = false; - } - } - - if ( done ) { - // Reset the overflow - if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { - var elem = this.elem, - options = this.options; - - jQuery.each( [ "", "X", "Y" ], function (index, value) { - elem.style[ "overflow" + value ] = options.overflow[index]; - } ); - } - - // Hide the element if the "hide" operation was done - if ( this.options.hide ) { - jQuery(this.elem).hide(); - } - - // Reset the properties, if the item has been hidden or shown - if ( this.options.hide || this.options.show ) { - for ( var p in this.options.curAnim ) { - jQuery.style( this.elem, p, this.options.orig[p] ); - } - } - - // Execute the complete function - this.options.complete.call( this.elem ); - } - - return false; - - } else { - var n = t - this.startTime; - this.state = n / this.options.duration; - - // Perform the easing function, defaults to swing - var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; - var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear"); - this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration); - this.now = this.start + ((this.end - this.start) * this.pos); - - // Perform the next step of the animation - this.update(); - } - - return true; - } -}; - -jQuery.extend( jQuery.fx, { - tick: function() { - var timers = jQuery.timers; - - for ( var i = 0; i < timers.length; i++ ) { - if ( !timers[i]() ) { - timers.splice(i--, 1); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - }, - - interval: 13, - - stop: function() { - clearInterval( timerId ); - timerId = null; - }, - - speeds: { - slow: 600, - fast: 200, - // Default speed - _default: 400 - }, - - step: { - opacity: function( fx ) { - jQuery.style( fx.elem, "opacity", fx.now ); - }, - - _default: function( fx ) { - if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { - fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; - } else { - fx.elem[ fx.prop ] = fx.now; - } - } - } -}); - -if ( jQuery.expr && jQuery.expr.filters ) { - jQuery.expr.filters.animated = function( elem ) { - return jQuery.grep(jQuery.timers, function( fn ) { - return elem === fn.elem; - }).length; - }; -} - -function defaultDisplay( nodeName ) { - if ( !elemdisplay[ nodeName ] ) { - var elem = jQuery("<" + nodeName + ">").appendTo("body"), - display = elem.css("display"); - - elem.remove(); - - if ( display === "none" || display === "" ) { - display = "block"; - } - - elemdisplay[ nodeName ] = display; - } - - return elemdisplay[ nodeName ]; -} - - - - -var rtable = /^t(?:able|d|h)$/i, - rroot = /^(?:body|html)$/i; - -if ( "getBoundingClientRect" in document.documentElement ) { - jQuery.fn.offset = function( options ) { - var elem = this[0], box; - - if ( options ) { - return this.each(function( i ) { - jQuery.offset.setOffset( this, options, i ); - }); - } - - if ( !elem || !elem.ownerDocument ) { - return null; - } - - if ( elem === elem.ownerDocument.body ) { - return jQuery.offset.bodyOffset( elem ); - } - - try { - box = elem.getBoundingClientRect(); - } catch(e) {} - - var doc = elem.ownerDocument, - docElem = doc.documentElement; - - // Make sure we're not dealing with a disconnected DOM node - if ( !box || !jQuery.contains( docElem, elem ) ) { - return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; - } - - var body = doc.body, - win = getWindow(doc), - clientTop = docElem.clientTop || body.clientTop || 0, - clientLeft = docElem.clientLeft || body.clientLeft || 0, - scrollTop = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ), - scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft), - top = box.top + scrollTop - clientTop, - left = box.left + scrollLeft - clientLeft; - - return { top: top, left: left }; - }; - -} else { - jQuery.fn.offset = function( options ) { - var elem = this[0]; - - if ( options ) { - return this.each(function( i ) { - jQuery.offset.setOffset( this, options, i ); - }); - } - - if ( !elem || !elem.ownerDocument ) { - return null; - } - - if ( elem === elem.ownerDocument.body ) { - return jQuery.offset.bodyOffset( elem ); - } - - jQuery.offset.initialize(); - - var computedStyle, - offsetParent = elem.offsetParent, - prevOffsetParent = elem, - doc = elem.ownerDocument, - docElem = doc.documentElement, - body = doc.body, - defaultView = doc.defaultView, - prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, - top = elem.offsetTop, - left = elem.offsetLeft; - - while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { - if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { - break; - } - - computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; - top -= elem.scrollTop; - left -= elem.scrollLeft; - - if ( elem === offsetParent ) { - top += elem.offsetTop; - left += elem.offsetLeft; - - if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { - top += parseFloat( computedStyle.borderTopWidth ) || 0; - left += parseFloat( computedStyle.borderLeftWidth ) || 0; - } - - prevOffsetParent = offsetParent; - offsetParent = elem.offsetParent; - } - - if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { - top += parseFloat( computedStyle.borderTopWidth ) || 0; - left += parseFloat( computedStyle.borderLeftWidth ) || 0; - } - - prevComputedStyle = computedStyle; - } - - if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { - top += body.offsetTop; - left += body.offsetLeft; - } - - if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { - top += Math.max( docElem.scrollTop, body.scrollTop ); - left += Math.max( docElem.scrollLeft, body.scrollLeft ); - } - - return { top: top, left: left }; - }; -} - -jQuery.offset = { - initialize: function() { - var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0, - html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>"; - - jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } ); - - container.innerHTML = html; - body.insertBefore( container, body.firstChild ); - innerDiv = container.firstChild; - checkDiv = innerDiv.firstChild; - td = innerDiv.nextSibling.firstChild.firstChild; - - this.doesNotAddBorder = (checkDiv.offsetTop !== 5); - this.doesAddBorderForTableAndCells = (td.offsetTop === 5); - - checkDiv.style.position = "fixed"; - checkDiv.style.top = "20px"; - - // safari subtracts parent border width here which is 5px - this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15); - checkDiv.style.position = checkDiv.style.top = ""; - - innerDiv.style.overflow = "hidden"; - innerDiv.style.position = "relative"; - - this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); - - this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); - - body.removeChild( container ); - body = container = innerDiv = checkDiv = table = td = null; - jQuery.offset.initialize = jQuery.noop; - }, - - bodyOffset: function( body ) { - var top = body.offsetTop, - left = body.offsetLeft; - - jQuery.offset.initialize(); - - if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { - top += parseFloat( jQuery.css(body, "marginTop") ) || 0; - left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; - } - - return { top: top, left: left }; - }, - - setOffset: function( elem, options, i ) { - var position = jQuery.css( elem, "position" ); - - // set position first, in-case top/left are set even on static elem - if ( position === "static" ) { - elem.style.position = "relative"; - } - - var curElem = jQuery( elem ), - curOffset = curElem.offset(), - curCSSTop = jQuery.css( elem, "top" ), - curCSSLeft = jQuery.css( elem, "left" ), - calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1), - props = {}, curPosition = {}, curTop, curLeft; - - // need to be able to calculate position if either top or left is auto and position is absolute - if ( calculatePosition ) { - curPosition = curElem.position(); - } - - curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0; - curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0; - - if ( jQuery.isFunction( options ) ) { - options = options.call( elem, i, curOffset ); - } - - if (options.top != null) { - props.top = (options.top - curOffset.top) + curTop; - } - if (options.left != null) { - props.left = (options.left - curOffset.left) + curLeft; - } - - if ( "using" in options ) { - options.using.call( elem, props ); - } else { - curElem.css( props ); - } - } -}; - - -jQuery.fn.extend({ - position: function() { - if ( !this[0] ) { - return null; - } - - var elem = this[0], - - // Get *real* offsetParent - offsetParent = this.offsetParent(), - - // Get correct offsets - offset = this.offset(), - parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); - - // Subtract element margins - // note: when an element has margin: auto the offsetLeft and marginLeft - // are the same in Safari causing offset.left to incorrectly be 0 - offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; - offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; - - // Add offsetParent borders - parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; - parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; - - // Subtract the two offsets - return { - top: offset.top - parentOffset.top, - left: offset.left - parentOffset.left - }; - }, - - offsetParent: function() { - return this.map(function() { - var offsetParent = this.offsetParent || document.body; - while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { - offsetParent = offsetParent.offsetParent; - } - return offsetParent; - }); - } -}); - - -// Create scrollLeft and scrollTop methods -jQuery.each( ["Left", "Top"], function( i, name ) { - var method = "scroll" + name; - - jQuery.fn[ method ] = function(val) { - var elem = this[0], win; - - if ( !elem ) { - return null; - } - - if ( val !== undefined ) { - // Set the scroll offset - return this.each(function() { - win = getWindow( this ); - - if ( win ) { - win.scrollTo( - !i ? val : jQuery(win).scrollLeft(), - i ? val : jQuery(win).scrollTop() - ); - - } else { - this[ method ] = val; - } - }); - } else { - win = getWindow( elem ); - - // Return the scroll offset - return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : - jQuery.support.boxModel && win.document.documentElement[ method ] || - win.document.body[ method ] : - elem[ method ]; - } - }; -}); - -function getWindow( elem ) { - return jQuery.isWindow( elem ) ? - elem : - elem.nodeType === 9 ? - elem.defaultView || elem.parentWindow : - false; -} - - - - -// Create innerHeight, innerWidth, outerHeight and outerWidth methods -jQuery.each([ "Height", "Width" ], function( i, name ) { - - var type = name.toLowerCase(); - - // innerHeight and innerWidth - jQuery.fn["inner" + name] = function() { - return this[0] ? - parseFloat( jQuery.css( this[0], type, "padding" ) ) : - null; - }; - - // outerHeight and outerWidth - jQuery.fn["outer" + name] = function( margin ) { - return this[0] ? - parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) : - null; - }; - - jQuery.fn[ type ] = function( size ) { - // Get window width or height - var elem = this[0]; - if ( !elem ) { - return size == null ? null : this; - } - - if ( jQuery.isFunction( size ) ) { - return this.each(function( i ) { - var self = jQuery( this ); - self[ type ]( size.call( this, i, self[ type ]() ) ); - }); - } - - if ( jQuery.isWindow( elem ) ) { - // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode - // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat - var docElemProp = elem.document.documentElement[ "client" + name ]; - return elem.document.compatMode === "CSS1Compat" && docElemProp || - elem.document.body[ "client" + name ] || docElemProp; - - // Get document width or height - } else if ( elem.nodeType === 9 ) { - // Either scroll[Width/Height] or offset[Width/Height], whichever is greater - return Math.max( - elem.documentElement["client" + name], - elem.body["scroll" + name], elem.documentElement["scroll" + name], - elem.body["offset" + name], elem.documentElement["offset" + name] - ); - - // Get or set width or height on the element - } else if ( size === undefined ) { - var orig = jQuery.css( elem, type ), - ret = parseFloat( orig ); - - return jQuery.isNaN( ret ) ? orig : ret; - - // Set the width or height on the element (default to pixels if value is unitless) - } else { - return this.css( type, typeof size === "string" ? size : size + "px" ); - } - }; - -}); - - -window.jQuery = window.$ = jQuery; -})(window); diff --git a/feincms/static/feincms/jquery-1.5.1.min.js b/feincms/static/feincms/jquery-1.5.1.min.js deleted file mode 100644 index 6437874c6..000000000 --- a/feincms/static/feincms/jquery-1.5.1.min.js +++ /dev/null @@ -1,16 +0,0 @@ -/*! - * jQuery JavaScript Library v1.5.1 - * http://jquery.com/ - * - * Copyright 2011, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Wed Feb 23 13:55:29 2011 -0500 - */ -(function(a,b){function cg(a){return d.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cd(a){if(!bZ[a]){var b=d("<"+a+">").appendTo("body"),c=b.css("display");b.remove();if(c==="none"||c==="")c="block";bZ[a]=c}return bZ[a]}function cc(a,b){var c={};d.each(cb.concat.apply([],cb.slice(0,b)),function(){c[this]=a});return c}function bY(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function bX(){try{return new a.XMLHttpRequest}catch(b){}}function bW(){d(a).unload(function(){for(var a in bU)bU[a](0,1)})}function bQ(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var e=a.dataTypes,f={},g,h,i=e.length,j,k=e[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h==="string"&&(f[h.toLowerCase()]=a.converters[h]);l=k,k=e[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=f[m]||f["* "+k];if(!n){p=b;for(o in f){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=f[j[1]+" "+k];if(p){o=f[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&d.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function bP(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function bO(a,b,c,e){if(d.isArray(b)&&b.length)d.each(b,function(b,f){c||bq.test(a)?e(a,f):bO(a+"["+(typeof f==="object"||d.isArray(f)?b:"")+"]",f,c,e)});else if(c||b==null||typeof b!=="object")e(a,b);else if(d.isArray(b)||d.isEmptyObject(b))e(a,"");else for(var f in b)bO(a+"["+f+"]",b[f],c,e)}function bN(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bH,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l==="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bN(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bN(a,c,d,e,"*",g));return l}function bM(a){return function(b,c){typeof b!=="string"&&(c=b,b="*");if(d.isFunction(c)){var e=b.toLowerCase().split(bB),f=0,g=e.length,h,i,j;for(;f<g;f++)h=e[f],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bo(a,b,c){var e=b==="width"?bi:bj,f=b==="width"?a.offsetWidth:a.offsetHeight;if(c==="border")return f;d.each(e,function(){c||(f-=parseFloat(d.css(a,"padding"+this))||0),c==="margin"?f+=parseFloat(d.css(a,"margin"+this))||0:f-=parseFloat(d.css(a,"border"+this+"Width"))||0});return f}function ba(a,b){b.src?d.ajax({url:b.src,async:!1,dataType:"script"}):d.globalEval(b.text||b.textContent||b.innerHTML||""),b.parentNode&&b.parentNode.removeChild(b)}function _(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function $(a,b){if(b.nodeType===1){var c=b.nodeName.toLowerCase();b.clearAttributes(),b.mergeAttributes(a);if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(d.expando)}}function Z(a,b){if(b.nodeType===1&&d.hasData(a)){var c=d.expando,e=d.data(a),f=d.data(b,e);if(e=e[c]){var g=e.events;f=f[c]=d.extend({},e);if(g){delete f.handle,f.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)d.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function Y(a,b){return d.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function O(a,b,c){if(d.isFunction(b))return d.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return d.grep(a,function(a,d){return a===b===c});if(typeof b==="string"){var e=d.grep(a,function(a){return a.nodeType===1});if(J.test(b))return d.filter(b,e,!c);b=d.filter(b,e)}return d.grep(a,function(a,e){return d.inArray(a,b)>=0===c})}function N(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function F(a,b){return(a&&a!=="*"?a+".":"")+b.replace(r,"`").replace(s,"&")}function E(a){var b,c,e,f,g,h,i,j,k,l,m,n,o,q=[],r=[],s=d._data(this,"events");if(a.liveFired!==this&&s&&s.live&&!a.target.disabled&&(!a.button||a.type!=="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var t=s.live.slice(0);for(i=0;i<t.length;i++)g=t[i],g.origType.replace(p,"")===a.type?r.push(g.selector):t.splice(i--,1);f=d(a.target).closest(r,a.currentTarget);for(j=0,k=f.length;j<k;j++){m=f[j];for(i=0;i<t.length;i++){g=t[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,e=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,e=d(a.relatedTarget).closest(g.selector)[0];(!e||e!==h)&&q.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=q.length;j<k;j++){f=q[j];if(c&&f.level>c)break;a.currentTarget=f.elem,a.data=f.handleObj.data,a.handleObj=f.handleObj,o=f.handleObj.origHandler.apply(f.elem,arguments);if(o===!1||a.isPropagationStopped()){c=f.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function C(a,c,e){var f=d.extend({},e[0]);f.type=a,f.originalEvent={},f.liveFired=b,d.event.handle.call(c,f),f.isDefaultPrevented()&&e[0].preventDefault()}function w(){return!0}function v(){return!1}function g(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function f(a,c,f){if(f===b&&a.nodeType===1){f=a.getAttribute("data-"+c);if(typeof f==="string"){try{f=f==="true"?!0:f==="false"?!1:f==="null"?null:d.isNaN(f)?e.test(f)?d.parseJSON(f):f:parseFloat(f)}catch(g){}d.data(a,c,f)}else f=b}return f}var c=a.document,d=function(){function I(){if(!d.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(I,1);return}d.ready()}}var d=function(a,b){return new d.fn.init(a,b,g)},e=a.jQuery,f=a.$,g,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,i=/\S/,j=/^\s+/,k=/\s+$/,l=/\d/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=navigator.userAgent,w,x=!1,y,z="then done fail isResolved isRejected promise".split(" "),A,B=Object.prototype.toString,C=Object.prototype.hasOwnProperty,D=Array.prototype.push,E=Array.prototype.slice,F=String.prototype.trim,G=Array.prototype.indexOf,H={};d.fn=d.prototype={constructor:d,init:function(a,e,f){var g,i,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!e&&c.body){this.context=c,this[0]=c.body,this.selector="body",this.length=1;return this}if(typeof a==="string"){g=h.exec(a);if(!g||!g[1]&&e)return!e||e.jquery?(e||f).find(a):this.constructor(e).find(a);if(g[1]){e=e instanceof d?e[0]:e,k=e?e.ownerDocument||e:c,j=m.exec(a),j?d.isPlainObject(e)?(a=[c.createElement(j[1])],d.fn.attr.call(a,e,!0)):a=[k.createElement(j[1])]:(j=d.buildFragment([g[1]],[k]),a=(j.cacheable?d.clone(j.fragment):j.fragment).childNodes);return d.merge(this,a)}i=c.getElementById(g[2]);if(i&&i.parentNode){if(i.id!==g[2])return f.find(a);this.length=1,this[0]=i}this.context=c,this.selector=a;return this}if(d.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return d.makeArray(a,this)},selector:"",jquery:"1.5.1",length:0,size:function(){return this.length},toArray:function(){return E.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var e=this.constructor();d.isArray(a)?D.apply(e,a):d.merge(e,a),e.prevObject=this,e.context=this.context,b==="find"?e.selector=this.selector+(this.selector?" ":"")+c:b&&(e.selector=this.selector+"."+b+"("+c+")");return e},each:function(a,b){return d.each(this,a,b)},ready:function(a){d.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(E.apply(this,arguments),"slice",E.call(arguments).join(","))},map:function(a){return this.pushStack(d.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:D,sort:[].sort,splice:[].splice},d.fn.init.prototype=d.fn,d.extend=d.fn.extend=function(){var a,c,e,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i==="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!=="object"&&!d.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){e=i[c],f=a[c];if(i===f)continue;l&&f&&(d.isPlainObject(f)||(g=d.isArray(f)))?(g?(g=!1,h=e&&d.isArray(e)?e:[]):h=e&&d.isPlainObject(e)?e:{},i[c]=d.extend(l,h,f)):f!==b&&(i[c]=f)}return i},d.extend({noConflict:function(b){a.$=f,b&&(a.jQuery=e);return d},isReady:!1,readyWait:1,ready:function(a){a===!0&&d.readyWait--;if(!d.readyWait||a!==!0&&!d.isReady){if(!c.body)return setTimeout(d.ready,1);d.isReady=!0;if(a!==!0&&--d.readyWait>0)return;y.resolveWith(c,[d]),d.fn.trigger&&d(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!x){x=!0;if(c.readyState==="complete")return setTimeout(d.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",A,!1),a.addEventListener("load",d.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",A),a.attachEvent("onload",d.ready);var b=!1;try{b=a.frameElement==null}catch(e){}c.documentElement.doScroll&&b&&I()}}},isFunction:function(a){return d.type(a)==="function"},isArray:Array.isArray||function(a){return d.type(a)==="array"},isWindow:function(a){return a&&typeof a==="object"&&"setInterval"in a},isNaN:function(a){return a==null||!l.test(a)||isNaN(a)},type:function(a){return a==null?String(a):H[B.call(a)]||"object"},isPlainObject:function(a){if(!a||d.type(a)!=="object"||a.nodeType||d.isWindow(a))return!1;if(a.constructor&&!C.call(a,"constructor")&&!C.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a){}return c===b||C.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!=="string"||!b)return null;b=d.trim(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return a.JSON&&a.JSON.parse?a.JSON.parse(b):(new Function("return "+b))();d.error("Invalid JSON: "+b)},parseXML:function(b,c,e){a.DOMParser?(e=new DOMParser,c=e.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),e=c.documentElement,(!e||!e.nodeName||e.nodeName==="parsererror")&&d.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(a){if(a&&i.test(a)){var b=c.head||c.getElementsByTagName("head")[0]||c.documentElement,e=c.createElement("script");d.support.scriptEval()?e.appendChild(c.createTextNode(a)):e.text=a,b.insertBefore(e,b.firstChild),b.removeChild(e)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,e){var f,g=0,h=a.length,i=h===b||d.isFunction(a);if(e){if(i){for(f in a)if(c.apply(a[f],e)===!1)break}else for(;g<h;)if(c.apply(a[g++],e)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(var j=a[0];g<h&&c.call(j,g,j)!==!1;j=a[++g]){}return a},trim:F?function(a){return a==null?"":F.call(a)}:function(a){return a==null?"":(a+"").replace(j,"").replace(k,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var e=d.type(a);a.length==null||e==="string"||e==="function"||e==="regexp"||d.isWindow(a)?D.call(c,a):d.merge(c,a)}return c},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length==="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,b,c){var d=[],e;for(var f=0,g=a.length;f<g;f++)e=b(a[f],f,c),e!=null&&(d[d.length]=e);return d.concat.apply([],d)},guid:1,proxy:function(a,c,e){arguments.length===2&&(typeof c==="string"?(e=a,a=e[c],c=b):c&&!d.isFunction(c)&&(e=c,c=b)),!c&&a&&(c=function(){return a.apply(e||this,arguments)}),a&&(c.guid=a.guid=a.guid||c.guid||d.guid++);return c},access:function(a,c,e,f,g,h){var i=a.length;if(typeof c==="object"){for(var j in c)d.access(a,j,c[j],f,g,e);return a}if(e!==b){f=!h&&f&&d.isFunction(e);for(var k=0;k<i;k++)g(a[k],c,f?e.call(a[k],k,g(a[k],c)):e,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},_Deferred:function(){var a=[],b,c,e,f={done:function(){if(!e){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=d.type(i),j==="array"?f.done.apply(f,i):j==="function"&&a.push(i);k&&f.resolveWith(k[0],k[1])}return this},resolveWith:function(d,f){if(!e&&!b&&!c){c=1;try{while(a[0])a.shift().apply(d,f)}catch(g){throw g}finally{b=[d,f],c=0}}return this},resolve:function(){f.resolveWith(d.isFunction(this.promise)?this.promise():this,arguments);return this},isResolved:function(){return c||b},cancel:function(){e=1,a=[];return this}};return f},Deferred:function(a){var b=d._Deferred(),c=d._Deferred(),e;d.extend(b,{then:function(a,c){b.done(a).fail(c);return this},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,promise:function(a){if(a==null){if(e)return e;e=a={}}var c=z.length;while(c--)a[z[c]]=b[z[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){var b=arguments.length,c=b<=1&&a&&d.isFunction(a.promise)?a:d.Deferred(),e=c.promise();if(b>1){var f=E.call(arguments,0),g=b,h=function(a){return function(b){f[a]=arguments.length>1?E.call(arguments,0):b,--g||c.resolveWith(e,f)}};while(b--)a=f[b],a&&d.isFunction(a.promise)?a.promise().then(h(b),c.reject):--g;g||c.resolveWith(e,f)}else c!==a&&c.resolve(a);return e},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}d.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.subclass=this.subclass,a.fn.init=function b(b,c){c&&c instanceof d&&!(c instanceof a)&&(c=a(c));return d.fn.init.call(this,b,c,e)},a.fn.init.prototype=a.fn;var e=a(c);return a},browser:{}}),y=d._Deferred(),d.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){H["[object "+b+"]"]=b.toLowerCase()}),w=d.uaMatch(v),w.browser&&(d.browser[w.browser]=!0,d.browser.version=w.version),d.browser.webkit&&(d.browser.safari=!0),G&&(d.inArray=function(a,b){return G.call(b,a)}),i.test(" ")&&(j=/^[\s\xA0]+/,k=/[\s\xA0]+$/),g=d(c),c.addEventListener?A=function(){c.removeEventListener("DOMContentLoaded",A,!1),d.ready()}:c.attachEvent&&(A=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",A),d.ready())});return d}();(function(){d.support={};var b=c.createElement("div");b.style.display="none",b.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";var e=b.getElementsByTagName("*"),f=b.getElementsByTagName("a")[0],g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=b.getElementsByTagName("input")[0];if(e&&e.length&&f){d.support={leadingWhitespace:b.firstChild.nodeType===3,tbody:!b.getElementsByTagName("tbody").length,htmlSerialize:!!b.getElementsByTagName("link").length,style:/red/.test(f.getAttribute("style")),hrefNormalized:f.getAttribute("href")==="/a",opacity:/^0.55$/.test(f.style.opacity),cssFloat:!!f.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,deleteExpando:!0,optDisabled:!1,checkClone:!1,noCloneEvent:!0,noCloneChecked:!0,boxModel:null,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableHiddenOffsets:!0},i.checked=!0,d.support.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,d.support.optDisabled=!h.disabled;var j=null;d.support.scriptEval=function(){if(j===null){var b=c.documentElement,e=c.createElement("script"),f="script"+d.now();try{e.appendChild(c.createTextNode("window."+f+"=1;"))}catch(g){}b.insertBefore(e,b.firstChild),a[f]?(j=!0,delete a[f]):j=!1,b.removeChild(e),b=e=f=null}return j};try{delete b.test}catch(k){d.support.deleteExpando=!1}!b.addEventListener&&b.attachEvent&&b.fireEvent&&(b.attachEvent("onclick",function l(){d.support.noCloneEvent=!1,b.detachEvent("onclick",l)}),b.cloneNode(!0).fireEvent("onclick")),b=c.createElement("div"),b.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";var m=c.createDocumentFragment();m.appendChild(b.firstChild),d.support.checkClone=m.cloneNode(!0).cloneNode(!0).lastChild.checked,d(function(){var a=c.createElement("div"),b=c.getElementsByTagName("body")[0];if(b){a.style.width=a.style.paddingLeft="1px",b.appendChild(a),d.boxModel=d.support.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,d.support.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",d.support.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";var e=a.getElementsByTagName("td");d.support.reliableHiddenOffsets=e[0].offsetHeight===0,e[0].style.display="",e[1].style.display="none",d.support.reliableHiddenOffsets=d.support.reliableHiddenOffsets&&e[0].offsetHeight===0,a.innerHTML="",b.removeChild(a).style.display="none",a=e=null}});var n=function(a){var b=c.createElement("div");a="on"+a;if(!b.attachEvent)return!0;var d=a in b;d||(b.setAttribute(a,"return;"),d=typeof b[a]==="function"),b=null;return d};d.support.submitBubbles=n("submit"),d.support.changeBubbles=n("change"),b=e=f=null}})();var e=/^(?:\{.*\}|\[.*\])$/;d.extend({cache:{},uuid:0,expando:"jQuery"+(d.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?d.cache[a[d.expando]]:a[d.expando];return!!a&&!g(a)},data:function(a,c,e,f){if(d.acceptData(a)){var g=d.expando,h=typeof c==="string",i,j=a.nodeType,k=j?d.cache:a,l=j?a[d.expando]:a[d.expando]&&d.expando;if((!l||f&&l&&!k[l][g])&&h&&e===b)return;l||(j?a[d.expando]=l=++d.uuid:l=d.expando),k[l]||(k[l]={},j||(k[l].toJSON=d.noop));if(typeof c==="object"||typeof c==="function")f?k[l][g]=d.extend(k[l][g],c):k[l]=d.extend(k[l],c);i=k[l],f&&(i[g]||(i[g]={}),i=i[g]),e!==b&&(i[c]=e);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[c]:i}},removeData:function(b,c,e){if(d.acceptData(b)){var f=d.expando,h=b.nodeType,i=h?d.cache:b,j=h?b[d.expando]:d.expando;if(!i[j])return;if(c){var k=e?i[j][f]:i[j];if(k){delete k[c];if(!g(k))return}}if(e){delete i[j][f];if(!g(i[j]))return}var l=i[j][f];d.support.deleteExpando||i!=a?delete i[j]:i[j]=null,l?(i[j]={},h||(i[j].toJSON=d.noop),i[j][f]=l):h&&(d.support.deleteExpando?delete b[d.expando]:b.removeAttribute?b.removeAttribute(d.expando):b[d.expando]=null)}},_data:function(a,b,c){return d.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=d.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),d.fn.extend({data:function(a,c){var e=null;if(typeof a==="undefined"){if(this.length){e=d.data(this[0]);if(this[0].nodeType===1){var g=this[0].attributes,h;for(var i=0,j=g.length;i<j;i++)h=g[i].name,h.indexOf("data-")===0&&(h=h.substr(5),f(this[0],h,e[h]))}}return e}if(typeof a==="object")return this.each(function(){d.data(this,a)});var k=a.split(".");k[1]=k[1]?"."+k[1]:"";if(c===b){e=this.triggerHandler("getData"+k[1]+"!",[k[0]]),e===b&&this.length&&(e=d.data(this[0],a),e=f(this[0],a,e));return e===b&&k[1]?this.data(k[0]):e}return this.each(function(){var b=d(this),e=[k[0],c];b.triggerHandler("setData"+k[1]+"!",e),d.data(this,a,c),b.triggerHandler("changeData"+k[1]+"!",e)})},removeData:function(a){return this.each(function(){d.removeData(this,a)})}}),d.extend({queue:function(a,b,c){if(a){b=(b||"fx")+"queue";var e=d._data(a,b);if(!c)return e||[];!e||d.isArray(c)?e=d._data(a,b,d.makeArray(c)):e.push(c);return e}},dequeue:function(a,b){b=b||"fx";var c=d.queue(a,b),e=c.shift();e==="inprogress"&&(e=c.shift()),e&&(b==="fx"&&c.unshift("inprogress"),e.call(a,function(){d.dequeue(a,b)})),c.length||d.removeData(a,b+"queue",!0)}}),d.fn.extend({queue:function(a,c){typeof a!=="string"&&(c=a,a="fx");if(c===b)return d.queue(this[0],a);return this.each(function(b){var e=d.queue(this,a,c);a==="fx"&&e[0]!=="inprogress"&&d.dequeue(this,a)})},dequeue:function(a){return this.each(function(){d.dequeue(this,a)})},delay:function(a,b){a=d.fx?d.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){d.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var h=/[\n\t\r]/g,i=/\s+/,j=/\r/g,k=/^(?:href|src|style)$/,l=/^(?:button|input)$/i,m=/^(?:button|input|object|select|textarea)$/i,n=/^a(?:rea)?$/i,o=/^(?:radio|checkbox)$/i;d.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"},d.fn.extend({attr:function(a,b){return d.access(this,a,b,!0,d.attr)},removeAttr:function(a,b){return this.each(function(){d.attr(this,a,""),this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(d.isFunction(a))return this.each(function(b){var c=d(this);c.addClass(a.call(this,b,c.attr("class")))});if(a&&typeof a==="string"){var b=(a||"").split(i);for(var c=0,e=this.length;c<e;c++){var f=this[c];if(f.nodeType===1)if(f.className){var g=" "+f.className+" ",h=f.className;for(var j=0,k=b.length;j<k;j++)g.indexOf(" "+b[j]+" ")<0&&(h+=" "+b[j]);f.className=d.trim(h)}else f.className=a}}return this},removeClass:function(a){if(d.isFunction(a))return this.each(function(b){var c=d(this);c.removeClass(a.call(this,b,c.attr("class")))});if(a&&typeof a==="string"||a===b){var c=(a||"").split(i);for(var e=0,f=this.length;e<f;e++){var g=this[e];if(g.nodeType===1&&g.className)if(a){var j=(" "+g.className+" ").replace(h," ");for(var k=0,l=c.length;k<l;k++)j=j.replace(" "+c[k]+" "," ");g.className=d.trim(j)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,e=typeof b==="boolean";if(d.isFunction(a))return this.each(function(c){var e=d(this);e.toggleClass(a.call(this,c,e.attr("class"),b),b)});return this.each(function(){if(c==="string"){var f,g=0,h=d(this),j=b,k=a.split(i);while(f=k[g++])j=e?j:!h.hasClass(f),h[j?"addClass":"removeClass"](f)}else if(c==="undefined"||c==="boolean")this.className&&d._data(this,"__className__",this.className),this.className=this.className||a===!1?"":d._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(h," ").indexOf(b)>-1)return!0;return!1},val:function(a){if(!arguments.length){var c=this[0];if(c){if(d.nodeName(c,"option")){var e=c.attributes.value;return!e||e.specified?c.value:c.text}if(d.nodeName(c,"select")){var f=c.selectedIndex,g=[],h=c.options,i=c.type==="select-one";if(f<0)return null;for(var k=i?f:0,l=i?f+1:h.length;k<l;k++){var m=h[k];if(m.selected&&(d.support.optDisabled?!m.disabled:m.getAttribute("disabled")===null)&&(!m.parentNode.disabled||!d.nodeName(m.parentNode,"optgroup"))){a=d(m).val();if(i)return a;g.push(a)}}if(i&&!g.length&&h.length)return d(h[f]).val();return g}if(o.test(c.type)&&!d.support.checkOn)return c.getAttribute("value")===null?"on":c.value;return(c.value||"").replace(j,"")}return b}var n=d.isFunction(a);return this.each(function(b){var c=d(this),e=a;if(this.nodeType===1){n&&(e=a.call(this,b,c.val())),e==null?e="":typeof e==="number"?e+="":d.isArray(e)&&(e=d.map(e,function(a){return a==null?"":a+""}));if(d.isArray(e)&&o.test(this.type))this.checked=d.inArray(c.val(),e)>=0;else if(d.nodeName(this,"select")){var f=d.makeArray(e);d("option",this).each(function(){this.selected=d.inArray(d(this).val(),f)>=0}),f.length||(this.selectedIndex=-1)}else this.value=e}})}}),d.extend({attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,e,f){if(!a||a.nodeType===3||a.nodeType===8||a.nodeType===2)return b;if(f&&c in d.attrFn)return d(a)[c](e);var g=a.nodeType!==1||!d.isXMLDoc(a),h=e!==b;c=g&&d.props[c]||c;if(a.nodeType===1){var i=k.test(c);if(c==="selected"&&!d.support.optSelected){var j=a.parentNode;j&&(j.selectedIndex,j.parentNode&&j.parentNode.selectedIndex)}if((c in a||a[c]!==b)&&g&&!i){h&&(c==="type"&&l.test(a.nodeName)&&a.parentNode&&d.error("type property can't be changed"),e===null?a.nodeType===1&&a.removeAttribute(c):a[c]=e);if(d.nodeName(a,"form")&&a.getAttributeNode(c))return a.getAttributeNode(c).nodeValue;if(c==="tabIndex"){var o=a.getAttributeNode("tabIndex");return o&&o.specified?o.value:m.test(a.nodeName)||n.test(a.nodeName)&&a.href?0:b}return a[c]}if(!d.support.style&&g&&c==="style"){h&&(a.style.cssText=""+e);return a.style.cssText}h&&a.setAttribute(c,""+e);if(!a.attributes[c]&&(a.hasAttribute&&!a.hasAttribute(c)))return b;var p=!d.support.hrefNormalized&&g&&i?a.getAttribute(c,2):a.getAttribute(c);return p===null?b:p}h&&(a[c]=e);return a[c]}});var p=/\.(.*)$/,q=/^(?:textarea|input|select)$/i,r=/\./g,s=/ /g,t=/[^\w\s.|`]/g,u=function(a){return a.replace(t,"\\$&")};d.event={add:function(c,e,f,g){if(c.nodeType!==3&&c.nodeType!==8){try{d.isWindow(c)&&(c!==a&&!c.frameElement)&&(c=a)}catch(h){}if(f===!1)f=v;else if(!f)return;var i,j;f.handler&&(i=f,f=i.handler),f.guid||(f.guid=d.guid++);var k=d._data(c);if(!k)return;var l=k.events,m=k.handle;l||(k.events=l={}),m||(k.handle=m=function(){return typeof d!=="undefined"&&!d.event.triggered?d.event.handle.apply(m.elem,arguments):b}),m.elem=c,e=e.split(" ");var n,o=0,p;while(n=e[o++]){j=i?d.extend({},i):{handler:f,data:g},n.indexOf(".")>-1?(p=n.split("."),n=p.shift(),j.namespace=p.slice(0).sort().join(".")):(p=[],j.namespace=""),j.type=n,j.guid||(j.guid=f.guid);var q=l[n],r=d.event.special[n]||{};if(!q){q=l[n]=[];if(!r.setup||r.setup.call(c,g,p,m)===!1)c.addEventListener?c.addEventListener(n,m,!1):c.attachEvent&&c.attachEvent("on"+n,m)}r.add&&(r.add.call(c,j),j.handler.guid||(j.handler.guid=f.guid)),q.push(j),d.event.global[n]=!0}c=null}},global:{},remove:function(a,c,e,f){if(a.nodeType!==3&&a.nodeType!==8){e===!1&&(e=v);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=d.hasData(a)&&d._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(e=c.handler,c=c.type);if(!c||typeof c==="string"&&c.charAt(0)==="."){c=c||"";for(h in t)d.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+d.map(m.slice(0).sort(),u).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!e){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))d.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=d.event.special[h]||{};for(j=f||0;j<p.length;j++){q=p[j];if(e.guid===q.guid){if(l||n.test(q.namespace))f==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(f!=null)break}}if(p.length===0||f!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&d.removeEvent(a,h,s.handle),g=null,delete t[h]}if(d.isEmptyObject(t)){var w=s.handle;w&&(w.elem=null),delete s.events,delete s.handle,d.isEmptyObject(s)&&d.removeData(a,b,!0)}}},trigger:function(a,c,e){var f=a.type||a,g=arguments[3];if(!g){a=typeof a==="object"?a[d.expando]?a:d.extend(d.Event(f),a):d.Event(f),f.indexOf("!")>=0&&(a.type=f=f.slice(0,-1),a.exclusive=!0),e||(a.stopPropagation(),d.event.global[f]&&d.each(d.cache,function(){var b=d.expando,e=this[b];e&&e.events&&e.events[f]&&d.event.trigger(a,c,e.handle.elem)}));if(!e||e.nodeType===3||e.nodeType===8)return b;a.result=b,a.target=e,c=d.makeArray(c),c.unshift(a)}a.currentTarget=e;var h=d._data(e,"handle");h&&h.apply(e,c);var i=e.parentNode||e.ownerDocument;try{e&&e.nodeName&&d.noData[e.nodeName.toLowerCase()]||e["on"+f]&&e["on"+f].apply(e,c)===!1&&(a.result=!1,a.preventDefault())}catch(j){}if(!a.isPropagationStopped()&&i)d.event.trigger(a,c,i,!0);else if(!a.isDefaultPrevented()){var k,l=a.target,m=f.replace(p,""),n=d.nodeName(l,"a")&&m==="click",o=d.event.special[m]||{};if((!o._default||o._default.call(e,a)===!1)&&!n&&!(l&&l.nodeName&&d.noData[l.nodeName.toLowerCase()])){try{l[m]&&(k=l["on"+m],k&&(l["on"+m]=null),d.event.triggered=!0,l[m]())}catch(q){}k&&(l["on"+m]=k),d.event.triggered=!1}}},handle:function(c){var e,f,g,h,i,j=[],k=d.makeArray(arguments);c=k[0]=d.event.fix(c||a.event),c.currentTarget=this,e=c.type.indexOf(".")<0&&!c.exclusive,e||(g=c.type.split("."),c.type=g.shift(),j=g.slice(0).sort(),h=new RegExp("(^|\\.)"+j.join("\\.(?:.*\\.)?")+"(\\.|$)")),c.namespace=c.namespace||j.join("."),i=d._data(this,"events"),f=(i||{})[c.type];if(i&&f){f=f.slice(0);for(var l=0,m=f.length;l<m;l++){var n=f[l];if(e||h.test(n.namespace)){c.handler=n.handler,c.data=n.data,c.handleObj=n;var o=n.handler.apply(this,k);o!==b&&(c.result=o,o===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[d.expando])return a;var e=a;a=d.Event(e);for(var f=this.props.length,g;f;)g=this.props[--f],a[g]=e[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=c.documentElement,i=c.body;a.pageX=a.clientX+(h&&h.scrollLeft||i&&i.scrollLeft||0)-(h&&h.clientLeft||i&&i.clientLeft||0),a.pageY=a.clientY+(h&&h.scrollTop||i&&i.scrollTop||0)-(h&&h.clientTop||i&&i.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:d.proxy,special:{ready:{setup:d.bindReady,teardown:d.noop},live:{add:function(a){d.event.add(this,F(a.origType,a.selector),d.extend({},a,{handler:E,guid:a.handler.guid}))},remove:function(a){d.event.remove(this,F(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){d.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},d.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},d.Event=function(a){if(!this.preventDefault)return new d.Event(a);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?w:v):this.type=a,this.timeStamp=d.now(),this[d.expando]=!0},d.Event.prototype={preventDefault:function(){this.isDefaultPrevented=w;var a=this.originalEvent;a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=w;var a=this.originalEvent;a&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=w,this.stopPropagation()},isDefaultPrevented:v,isPropagationStopped:v,isImmediatePropagationStopped:v};var x=function(a){var b=a.relatedTarget;try{if(b!==c&&!b.parentNode)return;while(b&&b!==this)b=b.parentNode;b!==this&&(a.type=a.data,d.event.handle.apply(this,arguments))}catch(e){}},y=function(a){a.type=a.data,d.event.handle.apply(this,arguments)};d.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){d.event.special[a]={setup:function(c){d.event.add(this,b,c&&c.selector?y:x,a)},teardown:function(a){d.event.remove(this,b,a&&a.selector?y:x)}}}),d.support.submitBubbles||(d.event.special.submit={setup:function(a,b){if(this.nodeName&&this.nodeName.toLowerCase()!=="form")d.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&d(b).closest("form").length&&C("submit",this,arguments)}),d.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&d(b).closest("form").length&&a.keyCode===13&&C("submit",this,arguments)});else return!1},teardown:function(a){d.event.remove(this,".specialSubmit")}});if(!d.support.changeBubbles){var z,A=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?d.map(a.options,function(a){return a.selected}).join("-"):"":a.nodeName.toLowerCase()==="select"&&(c=a.selectedIndex);return c},B=function B(a){var c=a.target,e,f;if(q.test(c.nodeName)&&!c.readOnly){e=d._data(c,"_change_data"),f=A(c),(a.type!=="focusout"||c.type!=="radio")&&d._data(c,"_change_data",f);if(e===b||f===e)return;if(e!=null||f)a.type="change",a.liveFired=b,d.event.trigger(a,arguments[1],c)}};d.event.special.change={filters:{focusout:B,beforedeactivate:B,click:function(a){var b=a.target,c=b.type;(c==="radio"||c==="checkbox"||b.nodeName.toLowerCase()==="select")&&B.call(this,a)},keydown:function(a){var b=a.target,c=b.type;(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&B.call(this,a)},beforeactivate:function(a){var b=a.target;d._data(b,"_change_data",A(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in z)d.event.add(this,c+".specialChange",z[c]);return q.test(this.nodeName)},teardown:function(a){d.event.remove(this,".specialChange");return q.test(this.nodeName)}},z=d.event.special.change.filters,z.focus=z.beforeactivate}c.addEventListener&&d.each({focus:"focusin",blur:"focusout"},function(a,b){function c(a){a=d.event.fix(a),a.type=b;return d.event.handle.call(this,a)}d.event.special[b]={setup:function(){this.addEventListener(a,c,!0)},teardown:function(){this.removeEventListener(a,c,!0)}}}),d.each(["bind","one"],function(a,c){d.fn[c]=function(a,e,f){if(typeof a==="object"){for(var g in a)this[c](g,e,a[g],f);return this}if(d.isFunction(e)||e===!1)f=e,e=b;var h=c==="one"?d.proxy(f,function(a){d(this).unbind(a,h);return f.apply(this,arguments)}):f;if(a==="unload"&&c!=="one")this.one(a,e,f);else for(var i=0,j=this.length;i<j;i++)d.event.add(this[i],a,h,e);return this}}),d.fn.extend({unbind:function(a,b){if(typeof a!=="object"||a.preventDefault)for(var e=0,f=this.length;e<f;e++)d.event.remove(this[e],a,b);else for(var c in a)this.unbind(c,a[c]);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){d.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){var c=d.Event(a);c.preventDefault(),c.stopPropagation(),d.event.trigger(c,b,this[0]);return c.result}},toggle:function(a){var b=arguments,c=1;while(c<b.length)d.proxy(a,b[c++]);return this.click(d.proxy(a,function(e){var f=(d._data(this,"lastToggle"+a.guid)||0)%c;d._data(this,"lastToggle"+a.guid,f+1),e.preventDefault();return b[f].apply(this,arguments)||!1}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var D={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};d.each(["live","die"],function(a,c){d.fn[c]=function(a,e,f,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:d(this.context);if(typeof a==="object"&&!a.preventDefault){for(var o in a)n[c](o,e,a[o],m);return this}d.isFunction(e)&&(f=e,e=b),a=(a||"").split(" ");while((h=a[i++])!=null){j=p.exec(h),k="",j&&(k=j[0],h=h.replace(p,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,h==="focus"||h==="blur"?(a.push(D[h]+k),h=h+k):h=(D[h]||h)+k;if(c==="live")for(var q=0,r=n.length;q<r;q++)d.event.add(n[q],"live."+F(h,m),{data:e,selector:m,handler:f,origType:h,origHandler:f,preType:l});else n.unbind("live."+F(h,m),f)}return this}}),d.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){d.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},d.attrFn&&(d.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!=="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,e,g){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!=="string")return e;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(f.call(n)==="[object Array]")if(u)if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&e.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&e.push(j[t]);else e.push.apply(e,n);else p(n,e);o&&(k(o,h,e,g),k.uniqueSort(e));return e};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!=="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(f){if(f===!0)continue}else g=o=!0}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b==="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1){}a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b==="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=u;typeof b==="string"&&!j.test(b)&&(b=b.toLowerCase(),d=b,g=t),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=u;typeof b==="string"&&!j.test(b)&&(b=b.toLowerCase(),d=b,g=t),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!=="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!=="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!=="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){return"text"===a.getAttribute("type")},radio:function(a){return"radio"===a.type},checkbox:function(a){return"checkbox"===a.type},file:function(a){return"file"===a.type},password:function(a){return"password"===a.type},submit:function(a){return"submit"===a.type},image:function(a){return"image"===a.type},reset:function(a){return"reset"===a.type},button:function(a){return"button"===a.type||a.nodeName.toLowerCase()==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(f.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length==="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(a===b){g=!0;return 0}if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!=="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!=="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!=="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!=="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector,d=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(e){d=!0}b&&(k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(d||!l.match.PSEUDO.test(c)&&!/!=/.test(c))return b.call(a,c)}catch(e){}return k(c,null,null,[a]).length>0})}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!=="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};d.find=k,d.expr=k.selectors,d.expr[":"]=d.expr.filters,d.unique=k.uniqueSort,d.text=k.getText,d.isXMLDoc=k.isXML,d.contains=k.contains}();var G=/Until$/,H=/^(?:parents|prevUntil|prevAll)/,I=/,/,J=/^.[^:#\[\.,]*$/,K=Array.prototype.slice,L=d.expr.match.POS,M={children:!0,contents:!0,next:!0,prev:!0};d.fn.extend({find:function(a){var b=this.pushStack("","find",a),c=0;for(var e=0,f=this.length;e<f;e++){c=b.length,d.find(a,this[e],b);if(e>0)for(var g=c;g<b.length;g++)for(var h=0;h<c;h++)if(b[h]===b[g]){b.splice(g--,1);break}}return b},has:function(a){var b=d(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(d.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(O(this,a,!1),"not",a)},filter:function(a){return this.pushStack(O(this,a,!0),"filter",a)},is:function(a){return!!a&&d.filter(a,this).length>0},closest:function(a,b){var c=[],e,f,g=this[0];if(d.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(e=0,f=a.length;e<f;e++)i=a[e],j[i]||(j[i]=d.expr.match.POS.test(i)?d(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:d(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=L.test(a)?d(a,b||this.context):null;for(e=0,f=this.length;e<f;e++){g=this[e];while(g){if(l?l.index(g)>-1:d.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b)break}}c=c.length>1?d.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a==="string")return d.inArray(this[0],a?d(a):this.parent().children());return d.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a==="string"?d(a,b):d.makeArray(a),e=d.merge(this.get(),c);return this.pushStack(N(c[0])||N(e[0])?e:d.unique(e))},andSelf:function(){return this.add(this.prevObject)}}),d.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return d.dir(a,"parentNode")},parentsUntil:function(a,b,c){return d.dir(a,"parentNode",c)},next:function(a){return d.nth(a,2,"nextSibling")},prev:function(a){return d.nth(a,2,"previousSibling")},nextAll:function(a){return d.dir(a,"nextSibling")},prevAll:function(a){return d.dir(a,"previousSibling")},nextUntil:function(a,b,c){return d.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return d.dir(a,"previousSibling",c)},siblings:function(a){return d.sibling(a.parentNode.firstChild,a)},children:function(a){return d.sibling(a.firstChild)},contents:function(a){return d.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:d.makeArray(a.childNodes)}},function(a,b){d.fn[a]=function(c,e){var f=d.map(this,b,c),g=K.call(arguments);G.test(a)||(e=c),e&&typeof e==="string"&&(f=d.filter(e,f)),f=this.length>1&&!M[a]?d.unique(f):f,(this.length>1||I.test(e))&&H.test(a)&&(f=f.reverse());return this.pushStack(f,a,g.join(","))}}),d.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?d.find.matchesSelector(b[0],a)?[b[0]]:[]:d.find.matches(a,b)},dir:function(a,c,e){var f=[],g=a[c];while(g&&g.nodeType!==9&&(e===b||g.nodeType!==1||!d(g).is(e)))g.nodeType===1&&f.push(g),g=g[c];return f},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var P=/ jQuery\d+="(?:\d+|null)"/g,Q=/^\s+/,R=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,S=/<([\w:]+)/,T=/<tbody/i,U=/<|&#?\w+;/,V=/<(?:script|object|embed|option|style)/i,W=/checked\s*(?:[^=]|=\s*.checked.)/i,X={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};X.optgroup=X.option,X.tbody=X.tfoot=X.colgroup=X.caption=X.thead,X.th=X.td,d.support.htmlSerialize||(X._default=[1,"div<div>","</div>"]),d.fn.extend({text:function(a){if(d.isFunction(a))return this.each(function(b){var c=d(this);c.text(a.call(this,b,c.text()))});if(typeof a!=="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return d.text(this)},wrapAll:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapAll(a.call(this,b))});if(this[0]){var b=d(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapInner(a.call(this,b))});return this.each(function(){var b=d(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){d(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){d.nodeName(this,"body")||d(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=d(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,d(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,e;(e=this[c])!=null;c++)if(!a||d.filter(a,[e]).length)!b&&e.nodeType===1&&(d.cleanData(e.getElementsByTagName("*")),d.cleanData([e])),e.parentNode&&e.parentNode.removeChild(e);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&d.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return d.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(P,""):null;if(typeof a!=="string"||V.test(a)||!d.support.leadingWhitespace&&Q.test(a)||X[(S.exec(a)||["",""])[1].toLowerCase()])d.isFunction(a)?this.each(function(b){var c=d(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);else{a=a.replace(R,"<$1></$2>");try{for(var c=0,e=this.length;c<e;c++)this[c].nodeType===1&&(d.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(f){this.empty().append(a)}}return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(d.isFunction(a))return this.each(function(b){var c=d(this),e=c.html();c.replaceWith(a.call(this,b,e))});typeof a!=="string"&&(a=d(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;d(this).remove(),b?d(b).before(a):d(c).append(a)})}return this.pushStack(d(d.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,e){var f,g,h,i,j=a[0],k=[];if(!d.support.checkClone&&arguments.length===3&&typeof j==="string"&&W.test(j))return this.each(function(){d(this).domManip(a,c,e,!0)});if(d.isFunction(j))return this.each(function(f){var g=d(this);a[0]=j.call(this,f,c?g.html():b),g.domManip(a,c,e)});if(this[0]){i=j&&j.parentNode,d.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?f={fragment:i}:f=d.buildFragment(a,this,k),h=f.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&d.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)e.call(c?Y(this[l],g):this[l],f.cacheable||m>1&&l<n?d.clone(h,!0,!0):h)}k.length&&d.each(k,ba)}return this}}),d.buildFragment=function(a,b,e){var f,g,h,i=b&&b[0]?b[0].ownerDocument||b[0]:c;a.length===1&&typeof a[0]==="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!V.test(a[0])&&(d.support.checkClone||!W.test(a[0]))&&(g=!0,h=d.fragments[a[0]],h&&(h!==1&&(f=h))),f||(f=i.createDocumentFragment(),d.clean(a,i,f,e)),g&&(d.fragments[a[0]]=h?f:1);return{fragment:f,cacheable:g}},d.fragments={},d.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){d.fn[a]=function(c){var e=[],f=d(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&f.length===1){f[b](this[0]);return this}for(var h=0,i=f.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();d(f[h])[b](j),e=e.concat(j)}return this.pushStack(e,a,f.selector)}}),d.extend({clone:function(a,b,c){var e=a.cloneNode(!0),f,g,h;if((!d.support.noCloneEvent||!d.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!d.isXMLDoc(a)){$(a,e),f=_(a),g=_(e);for(h=0;f[h];++h)$(f[h],g[h])}if(b){Z(a,e);if(c){f=_(a),g=_(e);for(h=0;f[h];++h)Z(f[h],g[h])}}return e},clean:function(a,b,e,f){b=b||c,typeof b.createElement==="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var g=[];for(var h=0,i;(i=a[h])!=null;h++){typeof i==="number"&&(i+="");if(!i)continue;if(typeof i!=="string"||U.test(i)){if(typeof i==="string"){i=i.replace(R,"<$1></$2>");var j=(S.exec(i)||["",""])[1].toLowerCase(),k=X[j]||X._default,l=k[0],m=b.createElement("div");m.innerHTML=k[1]+i+k[2];while(l--)m=m.lastChild;if(!d.support.tbody){var n=T.test(i),o=j==="table"&&!n?m.firstChild&&m.firstChild.childNodes:k[1]==="<table>"&&!n?m.childNodes:[];for(var p=o.length-1;p>=0;--p)d.nodeName(o[p],"tbody")&&!o[p].childNodes.length&&o[p].parentNode.removeChild(o[p])}!d.support.leadingWhitespace&&Q.test(i)&&m.insertBefore(b.createTextNode(Q.exec(i)[0]),m.firstChild),i=m.childNodes}}else i=b.createTextNode(i);i.nodeType?g.push(i):g=d.merge(g,i)}if(e)for(h=0;g[h];h++)!f||!d.nodeName(g[h],"script")||g[h].type&&g[h].type.toLowerCase()!=="text/javascript"?(g[h].nodeType===1&&g.splice.apply(g,[h+1,0].concat(d.makeArray(g[h].getElementsByTagName("script")))),e.appendChild(g[h])):f.push(g[h].parentNode?g[h].parentNode.removeChild(g[h]):g[h]);return g},cleanData:function(a){var b,c,e=d.cache,f=d.expando,g=d.event.special,h=d.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&d.noData[j.nodeName.toLowerCase()])continue;c=j[d.expando];if(c){b=e[c]&&e[c][f];if(b&&b.events){for(var k in b.events)g[k]?d.event.remove(j,k):d.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[d.expando]:j.removeAttribute&&j.removeAttribute(d.expando),delete e[c]}}}});var bb=/alpha\([^)]*\)/i,bc=/opacity=([^)]*)/,bd=/-([a-z])/ig,be=/([A-Z])/g,bf=/^-?\d+(?:px)?$/i,bg=/^-?\d/,bh={position:"absolute",visibility:"hidden",display:"block"},bi=["Left","Right"],bj=["Top","Bottom"],bk,bl,bm,bn=function(a,b){return b.toUpperCase()};d.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return d.access(this,a,c,!0,function(a,c,e){return e!==b?d.style(a,c,e):d.css(a,c)})},d.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bk(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0},cssProps:{"float":d.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,e,f){if(a&&a.nodeType!==3&&a.nodeType!==8&&a.style){var g,h=d.camelCase(c),i=a.style,j=d.cssHooks[h];c=d.cssProps[h]||h;if(e===b){if(j&&"get"in j&&(g=j.get(a,!1,f))!==b)return g;return i[c]}if(typeof e==="number"&&isNaN(e)||e==null)return;typeof e==="number"&&!d.cssNumber[h]&&(e+="px");if(!j||!("set"in j)||(e=j.set(a,e))!==b)try{i[c]=e}catch(k){}}},css:function(a,c,e){var f,g=d.camelCase(c),h=d.cssHooks[g];c=d.cssProps[g]||g;if(h&&"get"in h&&(f=h.get(a,!0,e))!==b)return f;if(bk)return bk(a,c,g)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bd,bn)}}),d.curCSS=d.css,d.each(["height","width"],function(a,b){d.cssHooks[b]={get:function(a,c,e){var f;if(c){a.offsetWidth!==0?f=bo(a,b,e):d.swap(a,bh,function(){f=bo(a,b,e)});if(f<=0){f=bk(a,b,b),f==="0px"&&bm&&(f=bm(a,b,b));if(f!=null)return f===""||f==="auto"?"0px":f}if(f<0||f==null){f=a.style[b];return f===""||f==="auto"?"0px":f}return typeof f==="string"?f:f+"px"}},set:function(a,b){if(!bf.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),d.support.opacity||(d.cssHooks.opacity={get:function(a,b){return bc.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style;c.zoom=1;var e=d.isNaN(b)?"":"alpha(opacity="+b*100+")",f=c.filter||"";c.filter=bb.test(f)?f.replace(bb,e):c.filter+" "+e}}),c.defaultView&&c.defaultView.getComputedStyle&&(bl=function(a,c,e){var f,g,h;e=e.replace(be,"-$1").toLowerCase();if(!(g=a.ownerDocument.defaultView))return b;if(h=g.getComputedStyle(a,null))f=h.getPropertyValue(e),f===""&&!d.contains(a.ownerDocument.documentElement,a)&&(f=d.style(a,e));return f}),c.documentElement.currentStyle&&(bm=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bf.test(d)&&bg.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bk=bl||bm,d.expr&&d.expr.filters&&(d.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!d.support.reliableHiddenOffsets&&(a.style.display||d.css(a,"display"))==="none"},d.expr.filters.visible=function(a){return!d.expr.filters.hidden(a)});var bp=/%20/g,bq=/\[\]$/,br=/\r?\n/g,bs=/#.*$/,bt=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bu=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bv=/(?:^file|^widget|\-extension):$/,bw=/^(?:GET|HEAD)$/,bx=/^\/\//,by=/\?/,bz=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bA=/^(?:select|textarea)/i,bB=/\s+/,bC=/([?&])_=[^&]*/,bD=/(^|\-)([a-z])/g,bE=function(a,b,c){return b+c.toUpperCase()},bF=/^([\w\+\.\-]+:)\/\/([^\/?#:]*)(?::(\d+))?/,bG=d.fn.load,bH={},bI={},bJ,bK;try{bJ=c.location.href}catch(bL){bJ=c.createElement("a"),bJ.href="",bJ=bJ.href}bK=bF.exec(bJ.toLowerCase()),d.fn.extend({load:function(a,c,e){if(typeof a!=="string"&&bG)return bG.apply(this,arguments);if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var g=a.slice(f,a.length);a=a.slice(0,f)}var h="GET";c&&(d.isFunction(c)?(e=c,c=b):typeof c==="object"&&(c=d.param(c,d.ajaxSettings.traditional),h="POST"));var i=this;d.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?d("<div>").append(c.replace(bz,"")).find(g):c)),e&&i.each(e,[c,b,a])}});return this},serialize:function(){return d.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?d.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bA.test(this.nodeName)||bu.test(this.type))}).map(function(a,b){var c=d(this).val();return c==null?null:d.isArray(c)?d.map(c,function(a,c){return{name:b.name,value:a.replace(br,"\r\n")}}):{name:b.name,value:c.replace(br,"\r\n")}}).get()}}),d.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){d.fn[b]=function(a){return this.bind(b,a)}}),d.each(["get","post"],function(a,c){d[c]=function(a,e,f,g){d.isFunction(e)&&(g=g||f,f=e,e=b);return d.ajax({type:c,url:a,data:e,success:f,dataType:g})}}),d.extend({getScript:function(a,c){return d.get(a,b,c,"script")},getJSON:function(a,b,c){return d.get(a,b,c,"json")},ajaxSetup:function(a,b){b?d.extend(!0,a,d.ajaxSettings,b):(b=a,a=d.extend(!0,d.ajaxSettings,b));for(var c in {context:1,url:1})c in b?a[c]=b[c]:c in d.ajaxSettings&&(a[c]=d.ajaxSettings[c]);return a},ajaxSettings:{url:bJ,isLocal:bv.test(bK[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":d.parseJSON,"text xml":d.parseXML}},ajaxPrefilter:bM(bH),ajaxTransport:bM(bI),ajax:function(a,c){function v(a,c,l,n){if(r!==2){r=2,p&&clearTimeout(p),o=b,m=n||"",u.readyState=a?4:0;var q,t,v,w=l?bP(e,u,l):b,x,y;if(a>=200&&a<300||a===304){if(e.ifModified){if(x=u.getResponseHeader("Last-Modified"))d.lastModified[k]=x;if(y=u.getResponseHeader("Etag"))d.etag[k]=y}if(a===304)c="notmodified",q=!0;else try{t=bQ(e,w),c="success",q=!0}catch(z){c="parsererror",v=z}}else{v=c;if(!c||a)c="error",a<0&&(a=0)}u.status=a,u.statusText=c,q?h.resolveWith(f,[t,c,u]):h.rejectWith(f,[u,c,v]),u.statusCode(j),j=b,s&&g.trigger("ajax"+(q?"Success":"Error"),[u,e,q?t:v]),i.resolveWith(f,[u,c]),s&&(g.trigger("ajaxComplete",[u,e]),--d.active||d.event.trigger("ajaxStop"))}}typeof a==="object"&&(c=a,a=b),c=c||{};var e=d.ajaxSetup({},c),f=e.context||e,g=f!==e&&(f.nodeType||f instanceof d)?d(f):d.event,h=d.Deferred(),i=d._Deferred(),j=e.statusCode||{},k,l={},m,n,o,p,q,r=0,s,t,u={readyState:0,setRequestHeader:function(a,b){r||(l[a.toLowerCase().replace(bD,bE)]=b);return this},getAllResponseHeaders:function(){return r===2?m:null},getResponseHeader:function(a){var c;if(r===2){if(!n){n={};while(c=bt.exec(m))n[c[1].toLowerCase()]=c[2]}c=n[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){r||(e.mimeType=a);return this},abort:function(a){a=a||"abort",o&&o.abort(a),v(0,a);return this}};h.promise(u),u.success=u.done,u.error=u.fail,u.complete=i.done,u.statusCode=function(a){if(a){var b;if(r<2)for(b in a)j[b]=[j[b],a[b]];else b=a[u.status],u.then(b,b)}return this},e.url=((a||e.url)+"").replace(bs,"").replace(bx,bK[1]+"//"),e.dataTypes=d.trim(e.dataType||"*").toLowerCase().split(bB),e.crossDomain||(q=bF.exec(e.url.toLowerCase()),e.crossDomain=q&&(q[1]!=bK[1]||q[2]!=bK[2]||(q[3]||(q[1]==="http:"?80:443))!=(bK[3]||(bK[1]==="http:"?80:443)))),e.data&&e.processData&&typeof e.data!=="string"&&(e.data=d.param(e.data,e.traditional)),bN(bH,e,c,u);if(r===2)return!1;s=e.global,e.type=e.type.toUpperCase(),e.hasContent=!bw.test(e.type),s&&d.active++===0&&d.event.trigger("ajaxStart");if(!e.hasContent){e.data&&(e.url+=(by.test(e.url)?"&":"?")+e.data),k=e.url;if(e.cache===!1){var w=d.now(),x=e.url.replace(bC,"$1_="+w);e.url=x+(x===e.url?(by.test(e.url)?"&":"?")+"_="+w:"")}}if(e.data&&e.hasContent&&e.contentType!==!1||c.contentType)l["Content-Type"]=e.contentType;e.ifModified&&(k=k||e.url,d.lastModified[k]&&(l["If-Modified-Since"]=d.lastModified[k]),d.etag[k]&&(l["If-None-Match"]=d.etag[k])),l.Accept=e.dataTypes[0]&&e.accepts[e.dataTypes[0]]?e.accepts[e.dataTypes[0]]+(e.dataTypes[0]!=="*"?", */*; q=0.01":""):e.accepts["*"];for(t in e.headers)u.setRequestHeader(t,e.headers[t]);if(e.beforeSend&&(e.beforeSend.call(f,u,e)===!1||r===2)){u.abort();return!1}for(t in {success:1,error:1,complete:1})u[t](e[t]);o=bN(bI,e,c,u);if(o){u.readyState=1,s&&g.trigger("ajaxSend",[u,e]),e.async&&e.timeout>0&&(p=setTimeout(function(){u.abort("timeout")},e.timeout));try{r=1,o.send(l,v)}catch(y){status<2?v(-1,y):d.error(y)}}else v(-1,"No Transport");return u},param:function(a,c){var e=[],f=function(a,b){b=d.isFunction(b)?b():b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=d.ajaxSettings.traditional);if(d.isArray(a)||a.jquery&&!d.isPlainObject(a))d.each(a,function(){f(this.name,this.value)});else for(var g in a)bO(g,a[g],c,f);return e.join("&").replace(bp,"+")}}),d.extend({active:0,lastModified:{},etag:{}});var bR=d.now(),bS=/(\=)\?(&|$)|()\?\?()/i;d.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return d.expando+"_"+bR++}}),d.ajaxPrefilter("json jsonp",function(b,c,e){var f=typeof b.data==="string";if(b.dataTypes[0]==="jsonp"||c.jsonpCallback||c.jsonp!=null||b.jsonp!==!1&&(bS.test(b.url)||f&&bS.test(b.data))){var g,h=b.jsonpCallback=d.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2",m=function(){a[h]=i,g&&d.isFunction(i)&&a[h](g[0])};b.jsonp!==!1&&(j=j.replace(bS,l),b.url===j&&(f&&(k=k.replace(bS,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},e.then(m,m),b.converters["script json"]=function(){g||d.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),d.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){d.globalEval(a);return a}}}),d.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),d.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var bT=d.now(),bU,bV;d.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&bX()||bY()}:bX,bV=d.ajaxSettings.xhr(),d.support.ajax=!!bV,d.support.cors=bV&&"withCredentials"in bV,bV=b,d.support.ajax&&d.ajaxTransport(function(a){if(!a.crossDomain||d.support.cors){var c;return{send:function(e,f){var g=a.xhr(),h,i;a.username?g.open(a.type,a.url,a.async,a.username,a.password):g.open(a.type,a.url,a.async);if(a.xhrFields)for(i in a.xhrFields)g[i]=a.xhrFields[i];a.mimeType&&g.overrideMimeType&&g.overrideMimeType(a.mimeType),(!a.crossDomain||a.hasContent)&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(i in e)g.setRequestHeader(i,e[i])}catch(j){}g.send(a.hasContent&&a.data||null),c=function(e,i){var j,k,l,m,n;try{if(c&&(i||g.readyState===4)){c=b,h&&(g.onreadystatechange=d.noop,delete bU[h]);if(i)g.readyState!==4&&g.abort();else{j=g.status,l=g.getAllResponseHeaders(),m={},n=g.responseXML,n&&n.documentElement&&(m.xml=n),m.text=g.responseText;try{k=g.statusText}catch(o){k=""}j||!a.isLocal||a.crossDomain?j===1223&&(j=204):j=m.text?200:404}}}catch(p){i||f(-1,p)}m&&f(j,k,m,l)},a.async&&g.readyState!==4?(bU||(bU={},bW()),h=bT++,g.onreadystatechange=bU[h]=c):c()},abort:function(){c&&c(0,1)}}}});var bZ={},b$=/^(?:toggle|show|hide)$/,b_=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,ca,cb=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];d.fn.extend({show:function(a,b,c){var e,f;if(a||a===0)return this.animate(cc("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)e=this[g],f=e.style.display,!d._data(e,"olddisplay")&&f==="none"&&(f=e.style.display=""),f===""&&d.css(e,"display")==="none"&&d._data(e,"olddisplay",cd(e.nodeName));for(g=0;g<h;g++){e=this[g],f=e.style.display;if(f===""||f==="none")e.style.display=d._data(e,"olddisplay")||""}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cc("hide",3),a,b,c);for(var e=0,f=this.length;e<f;e++){var g=d.css(this[e],"display");g!=="none"&&!d._data(this[e],"olddisplay")&&d._data(this[e],"olddisplay",g)}for(e=0;e<f;e++)this[e].style.display="none";return this},_toggle:d.fn.toggle,toggle:function(a,b,c){var e=typeof a==="boolean";d.isFunction(a)&&d.isFunction(b)?this._toggle.apply(this,arguments):a==null||e?this.each(function(){var b=e?a:d(this).is(":hidden");d(this)[b?"show":"hide"]()}):this.animate(cc("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,e){var f=d.speed(b,c,e);if(d.isEmptyObject(a))return this.each(f.complete);return this[f.queue===!1?"each":"queue"](function(){var b=d.extend({},f),c,e=this.nodeType===1,g=e&&d(this).is(":hidden"),h=this;for(c in a){var i=d.camelCase(c);c!==i&&(a[i]=a[c],delete a[c],c=i);if(a[c]==="hide"&&g||a[c]==="show"&&!g)return b.complete.call(this);if(e&&(c==="height"||c==="width")){b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY];if(d.css(this,"display")==="inline"&&d.css(this,"float")==="none")if(d.support.inlineBlockNeedsLayout){var j=cd(this.nodeName);j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)}else this.style.display="inline-block"}d.isArray(a[c])&&((b.specialEasing=b.specialEasing||{})[c]=a[c][1],a[c]=a[c][0])}b.overflow!=null&&(this.style.overflow="hidden"),b.curAnim=d.extend({},a),d.each(a,function(c,e){var f=new d.fx(h,b,c);if(b$.test(e))f[e==="toggle"?g?"show":"hide":e](a);else{var i=b_.exec(e),j=f.cur();if(i){var k=parseFloat(i[2]),l=i[3]||(d.cssNumber[c]?"":"px");l!=="px"&&(d.style(h,c,(k||1)+l),j=(k||1)/f.cur()*j,d.style(h,c,j+l)),i[1]&&(k=(i[1]==="-="?-1:1)*k+j),f.custom(j,k,l)}else f.custom(j,e,"")}});return!0})},stop:function(a,b){var c=d.timers;a&&this.queue([]),this.each(function(){for(var a=c.length-1;a>=0;a--)c[a].elem===this&&(b&&c[a](!0),c.splice(a,1))}),b||this.dequeue();return this}}),d.each({slideDown:cc("show",1),slideUp:cc("hide",1),slideToggle:cc("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){d.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),d.extend({speed:function(a,b,c){var e=a&&typeof a==="object"?d.extend({},a):{complete:c||!c&&b||d.isFunction(a)&&a,duration:a,easing:c&&b||b&&!d.isFunction(b)&&b};e.duration=d.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in d.fx.speeds?d.fx.speeds[e.duration]:d.fx.speeds._default,e.old=e.complete,e.complete=function(){e.queue!==!1&&d(this).dequeue(),d.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig||(b.orig={})}}),d.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(d.fx.step[this.prop]||d.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=d.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function g(a){return e.step(a)}var e=this,f=d.fx;this.startTime=d.now(),this.start=a,this.end=b,this.unit=c||this.unit||(d.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,g.elem=this.elem,g()&&d.timers.push(g)&&!ca&&(ca=setInterval(f.tick,f.interval))},show:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),d(this.elem).show()},hide:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=d.now(),c=!0;if(a||b>=this.options.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),this.options.curAnim[this.prop]=!0;for(var e in this.options.curAnim)this.options.curAnim[e]!==!0&&(c=!1);if(c){if(this.options.overflow!=null&&!d.support.shrinkWrapBlocks){var f=this.elem,g=this.options;d.each(["","X","Y"],function(a,b){f.style["overflow"+b]=g.overflow[a]})}this.options.hide&&d(this.elem).hide();if(this.options.hide||this.options.show)for(var h in this.options.curAnim)d.style(this.elem,h,this.options.orig[h]);this.options.complete.call(this.elem)}return!1}var i=b-this.startTime;this.state=i/this.options.duration;var j=this.options.specialEasing&&this.options.specialEasing[this.prop],k=this.options.easing||(d.easing.swing?"swing":"linear");this.pos=d.easing[j||k](this.state,i,0,1,this.options.duration),this.now=this.start+(this.end-this.start)*this.pos,this.update();return!0}},d.extend(d.fx,{tick:function(){var a=d.timers;for(var b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||d.fx.stop()},interval:13,stop:function(){clearInterval(ca),ca=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){d.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),d.expr&&d.expr.filters&&(d.expr.filters.animated=function(a){return d.grep(d.timers,function(b){return a===b.elem}).length});var ce=/^t(?:able|d|h)$/i,cf=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?d.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){d.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return d.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(e){}var f=b.ownerDocument,g=f.documentElement;if(!c||!d.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=f.body,i=cg(f),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||d.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||d.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:d.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){d.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return d.offset.bodyOffset(b);d.offset.initialize();var c,e=b.offsetParent,f=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(d.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===e&&(l+=b.offsetTop,m+=b.offsetLeft,d.offset.doesNotAddBorder&&(!d.offset.doesAddBorderForTableAndCells||!ce.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),f=e,e=b.offsetParent),d.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;d.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},d.offset={initialize:function(){var a=c.body,b=c.createElement("div"),e,f,g,h,i=parseFloat(d.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";d.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),e=b.firstChild,f=e.firstChild,h=e.nextSibling.firstChild.firstChild,this.doesNotAddBorder=f.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,f.style.position="fixed",f.style.top="20px",this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15,f.style.position=f.style.top="",e.style.overflow="hidden",e.style.position="relative",this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),a=b=e=f=g=h=null,d.offset.initialize=d.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;d.offset.initialize(),d.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(d.css(a,"marginTop"))||0,c+=parseFloat(d.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var e=d.css(a,"position");e==="static"&&(a.style.position="relative");var f=d(a),g=f.offset(),h=d.css(a,"top"),i=d.css(a,"left"),j=e==="absolute"&&d.inArray("auto",[h,i])>-1,k={},l={},m,n;j&&(l=f.position()),m=j?l.top:parseInt(h,10)||0,n=j?l.left:parseInt(i,10)||0,d.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):f.css(k)}},d.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),e=cf.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(d.css(a,"marginTop"))||0,c.left-=parseFloat(d.css(a,"marginLeft"))||0,e.top+=parseFloat(d.css(b[0],"borderTopWidth"))||0,e.left+=parseFloat(d.css(b[0],"borderLeftWidth"))||0;return{top:c.top-e.top,left:c.left-e.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&(!cf.test(a.nodeName)&&d.css(a,"position")==="static"))a=a.offsetParent;return a})}}),d.each(["Left","Top"],function(a,c){var e="scroll"+c;d.fn[e]=function(c){var f=this[0],g;if(!f)return null;if(c!==b)return this.each(function(){g=cg(this),g?g.scrollTo(a?d(g).scrollLeft():c,a?c:d(g).scrollTop()):this[e]=c});g=cg(f);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:d.support.boxModel&&g.document.documentElement[e]||g.document.body[e]:f[e]}}),d.each(["Height","Width"],function(a,c){var e=c.toLowerCase();d.fn["inner"+c]=function(){return this[0]?parseFloat(d.css(this[0],e,"padding")):null},d.fn["outer"+c]=function(a){return this[0]?parseFloat(d.css(this[0],e,a?"margin":"border")):null},d.fn[e]=function(a){var f=this[0];if(!f)return a==null?null:this;if(d.isFunction(a))return this.each(function(b){var c=d(this);c[e](a.call(this,b,c[e]()))});if(d.isWindow(f)){var g=f.document.documentElement["client"+c];return f.document.compatMode==="CSS1Compat"&&g||f.document.body["client"+c]||g}if(f.nodeType===9)return Math.max(f.documentElement["client"+c],f.body["scroll"+c],f.documentElement["scroll"+c],f.body["offset"+c],f.documentElement["offset"+c]);if(a===b){var h=d.css(f,e),i=parseFloat(h);return d.isNaN(i)?h:i}return this.css(e,typeof a==="string"?a:a+"px")}}),a.jQuery=a.$=d})(window); \ No newline at end of file diff --git a/feincms/static/feincms/jquery-ui-1.10.3.custom.min.js b/feincms/static/feincms/jquery-ui-1.10.3.custom.min.js new file mode 100755 index 000000000..7bda6aa93 --- /dev/null +++ b/feincms/static/feincms/jquery-ui-1.10.3.custom.min.js @@ -0,0 +1,6 @@ +/*! jQuery UI - v1.10.3 - 2013-11-17 +* http://jqueryui.com +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.sortable.js +* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ + +(function(e,t){function i(t,i){var s,n,r,o=t.nodeName.toLowerCase();return"area"===o?(s=t.parentNode,n=s.name,t.href&&n&&"map"===s.nodeName.toLowerCase()?(r=e("img[usemap=#"+n+"]")[0],!!r&&a(r)):!1):(/input|select|textarea|button|object/.test(o)?!t.disabled:"a"===o?t.href||i:i)&&a(t)}function a(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var s=0,n=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.3",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,a){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),a&&a.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var a,s,n=e(this[0]);n.length&&n[0]!==document;){if(a=n.css("position"),("absolute"===a||"relative"===a||"fixed"===a)&&(s=parseInt(n.css("zIndex"),10),!isNaN(s)&&0!==s))return s;n=n.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++s)})},removeUniqueId:function(){return this.each(function(){n.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,a){return!!e.data(t,a[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var a=e.attr(t,"tabindex"),s=isNaN(a);return(s||a>=0)&&i(t,!s)}}),e("<a>").outerWidth(1).jquery||e.each(["Width","Height"],function(i,a){function s(t,i,a,s){return e.each(n,function(){i-=parseFloat(e.css(t,"padding"+this))||0,a&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var n="Width"===a?["Left","Right"]:["Top","Bottom"],r=a.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+a]=function(i){return i===t?o["inner"+a].call(this):this.each(function(){e(this).css(r,s(this,i)+"px")})},e.fn["outer"+a]=function(t,i){return"number"!=typeof t?o["outer"+a].call(this,t):this.each(function(){e(this).css(r,s(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("<a>").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,a){var s,n=e.ui[t].prototype;for(s in a)n.plugins[s]=n.plugins[s]||[],n.plugins[s].push([i,a[s]])},call:function(e,t,i){var a,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(a=0;s.length>a;a++)e.options[s[a][0]]&&s[a][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var a=i&&"left"===i?"scrollLeft":"scrollTop",s=!1;return t[a]>0?!0:(t[a]=1,s=t[a]>0,t[a]=0,s)}})})(jQuery);(function(e,t){var i=0,s=Array.prototype.slice,a=e.cleanData;e.cleanData=function(t){for(var i,s=0;null!=(i=t[s]);s++)try{e(i).triggerHandler("remove")}catch(n){}a(t)},e.widget=function(i,s,a){var n,r,o,h,l={},u=i.split(".")[0];i=i.split(".")[1],n=u+"-"+i,a||(a=s,s=e.Widget),e.expr[":"][n.toLowerCase()]=function(t){return!!e.data(t,n)},e[u]=e[u]||{},r=e[u][i],o=e[u][i]=function(e,i){return this._createWidget?(arguments.length&&this._createWidget(e,i),t):new o(e,i)},e.extend(o,r,{version:a.version,_proto:e.extend({},a),_childConstructors:[]}),h=new s,h.options=e.widget.extend({},h.options),e.each(a,function(i,a){return e.isFunction(a)?(l[i]=function(){var e=function(){return s.prototype[i].apply(this,arguments)},t=function(e){return s.prototype[i].apply(this,e)};return function(){var i,s=this._super,n=this._superApply;return this._super=e,this._superApply=t,i=a.apply(this,arguments),this._super=s,this._superApply=n,i}}(),t):(l[i]=a,t)}),o.prototype=e.widget.extend(h,{widgetEventPrefix:r?h.widgetEventPrefix:i},l,{constructor:o,namespace:u,widgetName:i,widgetFullName:n}),r?(e.each(r._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete r._childConstructors):s._childConstructors.push(o),e.widget.bridge(i,o)},e.widget.extend=function(i){for(var a,n,r=s.call(arguments,1),o=0,h=r.length;h>o;o++)for(a in r[o])n=r[o][a],r[o].hasOwnProperty(a)&&n!==t&&(i[a]=e.isPlainObject(n)?e.isPlainObject(i[a])?e.widget.extend({},i[a],n):e.widget.extend({},n):n);return i},e.widget.bridge=function(i,a){var n=a.prototype.widgetFullName||i;e.fn[i]=function(r){var o="string"==typeof r,h=s.call(arguments,1),l=this;return r=!o&&h.length?e.widget.extend.apply(null,[r].concat(h)):r,o?this.each(function(){var s,a=e.data(this,n);return a?e.isFunction(a[r])&&"_"!==r.charAt(0)?(s=a[r].apply(a,h),s!==a&&s!==t?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):t):e.error("no such method '"+r+"' for "+i+" widget instance"):e.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+r+"'")}):this.each(function(){var t=e.data(this,n);t?t.option(r||{})._init():e.data(this,n,new a(r,this))}),l}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{disabled:!1,create:null},_createWidget:function(t,s){s=e(s||this.defaultElement||this)[0],this.element=e(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),s!==this&&(e.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===s&&this.destroy()}}),this.document=e(s.style?s.ownerDocument:s.document||s),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(i,s){var a,n,r,o=i;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof i)if(o={},a=i.split("."),i=a.shift(),a.length){for(n=o[i]=e.widget.extend({},this.options[i]),r=0;a.length-1>r;r++)n[a[r]]=n[a[r]]||{},n=n[a[r]];if(i=a.pop(),s===t)return n[i]===t?null:n[i];n[i]=s}else{if(s===t)return this.options[i]===t?null:this.options[i];o[i]=s}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,a){var n,r=this;"boolean"!=typeof i&&(a=s,s=i,i=!1),a?(s=n=e(s),this.bindings=this.bindings.add(s)):(a=s,s=this.element,n=this.widget()),e.each(a,function(a,o){function h(){return i||r.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?r[o]:o).apply(r,arguments):t}"string"!=typeof o&&(h.guid=o.guid=o.guid||h.guid||e.guid++);var l=a.match(/^(\w+)\s*(.*)$/),u=l[1]+r.eventNamespace,c=l[2];c?n.delegate(c,u,h):s.bind(u,h)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var a,n,r=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],n=i.originalEvent)for(a in n)a in i||(i[a]=n[a]);return this.element.trigger(i,s),!(e.isFunction(r)&&r.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,a,n){"string"==typeof a&&(a={effect:a});var r,o=a?a===!0||"number"==typeof a?i:a.effect||i:t;a=a||{},"number"==typeof a&&(a={duration:a}),r=!e.isEmptyObject(a),a.complete=n,a.delay&&s.delay(a.delay),r&&e.effects&&e.effects.effect[o]?s[t](a):o!==t&&s[o]?s[o](a.duration,a.easing,n):s.queue(function(i){e(this)[t](),n&&n.call(s[0]),i()})}})})(jQuery);(function(e){var t=!1;e(document).mouseup(function(){t=!1}),e.widget("ui.mouse",{version:"1.10.3",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(i){return!0===e.data(i.target,t.widgetName+".preventClickEvent")?(e.removeData(i.target,t.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!t){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,a=1===i.which,n="string"==typeof this.options.cancel&&i.target.nodeName?e(i.target).closest(this.options.cancel).length:!1;return a&&!n&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===e.data(i.target,this.widgetName+".preventClickEvent")&&e.removeData(i.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return s._mouseMove(e)},this._mouseUpDelegate=function(e){return s._mouseUp(e)},e(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),i.preventDefault(),t=!0,!0)):!0}},_mouseMove:function(t){return e.ui.ie&&(!document.documentMode||9>document.documentMode)&&!t.button?this._mouseUp(t):this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(e){function t(e,t,i){return e>t&&t+i>e}function i(e){return/left|right/.test(e.css("float"))||/inline|table-cell/.test(e.css("display"))}e.widget("ui.sortable",e.ui.mouse,{version:"1.10.3",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var e=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?"x"===e.axis||i(this.items[0].item):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var e=this.items.length-1;e>=0;e--)this.items[e].item.removeData(this.widgetName+"-item");return this},_setOption:function(t,i){"disabled"===t?(this.options[t]=i,this.widget().toggleClass("ui-sortable-disabled",!!i)):e.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(t,i){var s=null,a=!1,n=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(t),e(t.target).parents().each(function(){return e.data(this,n.widgetName+"-item")===n?(s=e(this),!1):undefined}),e.data(t.target,n.widgetName+"-item")===n&&(s=e(t.target)),s?!this.options.handle||i||(e(this.options.handle,s).find("*").addBack().each(function(){this===t.target&&(a=!0)}),a)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(t,i,s){var a,n,r=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(t),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,r.cursorAt&&this._adjustOffsetFromHelper(r.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),r.containment&&this._setContainment(),r.cursor&&"auto"!==r.cursor&&(n=this.document.find("body"),this.storedCursor=n.css("cursor"),n.css("cursor",r.cursor),this.storedStylesheet=e("<style>*{ cursor: "+r.cursor+" !important; }</style>").appendTo(n)),r.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",r.opacity)),r.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",r.zIndex)),this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(a=this.containers.length-1;a>=0;a--)this.containers[a]._trigger("activate",t,this._uiHash(this));return e.ui.ddmanager&&(e.ui.ddmanager.current=this),e.ui.ddmanager&&!r.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(t),!0},_mouseDrag:function(t){var i,s,a,n,r=this.options,o=!1;for(this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageY<r.scrollSensitivity?this.scrollParent[0].scrollTop=o=this.scrollParent[0].scrollTop+r.scrollSpeed:t.pageY-this.overflowOffset.top<r.scrollSensitivity&&(this.scrollParent[0].scrollTop=o=this.scrollParent[0].scrollTop-r.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-t.pageX<r.scrollSensitivity?this.scrollParent[0].scrollLeft=o=this.scrollParent[0].scrollLeft+r.scrollSpeed:t.pageX-this.overflowOffset.left<r.scrollSensitivity&&(this.scrollParent[0].scrollLeft=o=this.scrollParent[0].scrollLeft-r.scrollSpeed)):(t.pageY-e(document).scrollTop()<r.scrollSensitivity?o=e(document).scrollTop(e(document).scrollTop()-r.scrollSpeed):e(window).height()-(t.pageY-e(document).scrollTop())<r.scrollSensitivity&&(o=e(document).scrollTop(e(document).scrollTop()+r.scrollSpeed)),t.pageX-e(document).scrollLeft()<r.scrollSensitivity?o=e(document).scrollLeft(e(document).scrollLeft()-r.scrollSpeed):e(window).width()-(t.pageX-e(document).scrollLeft())<r.scrollSensitivity&&(o=e(document).scrollLeft(e(document).scrollLeft()+r.scrollSpeed))),o!==!1&&e.ui.ddmanager&&!r.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t)),this.positionAbs=this._convertPositionTo("absolute"),this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),i=this.items.length-1;i>=0;i--)if(s=this.items[i],a=s.item[0],n=this._intersectsWithPointer(s),n&&s.instance===this.currentContainer&&a!==this.currentItem[0]&&this.placeholder[1===n?"next":"prev"]()[0]!==a&&!e.contains(this.placeholder[0],a)&&("semi-dynamic"===this.options.type?!e.contains(this.element[0],a):!0)){if(this.direction=1===n?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(t,s),this._trigger("change",t,this._uiHash());break}return this._contactContainers(t),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),this._trigger("sort",t,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(t,i){if(t){if(e.ui.ddmanager&&!this.options.dropBehaviour&&e.ui.ddmanager.drop(this,t),this.options.revert){var s=this,a=this.placeholder.offset(),n=this.options.axis,r={};n&&"x"!==n||(r.left=a.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft)),n&&"y"!==n||(r.top=a.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,e(this.helper).animate(r,parseInt(this.options.revert,10)||500,function(){s._clear(t)})}else this._clear(t,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),"original"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var t=this.containers.length-1;t>=0;t--)this.containers[t]._trigger("deactivate",null,this._uiHash(this)),this.containers[t].containerCache.over&&(this.containers[t]._trigger("out",null,this._uiHash(this)),this.containers[t].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),e.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?e(this.domPosition.prev).after(this.currentItem):e(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(t){var i=this._getItemsAsjQuery(t&&t.connected),s=[];return t=t||{},e(i).each(function(){var i=(e(t.item||this).attr(t.attribute||"id")||"").match(t.expression||/(.+)[\-=_](.+)/);i&&s.push((t.key||i[1]+"[]")+"="+(t.key&&t.expression?i[1]:i[2]))}),!s.length&&t.key&&s.push(t.key+"="),s.join("&")},toArray:function(t){var i=this._getItemsAsjQuery(t&&t.connected),s=[];return t=t||{},i.each(function(){s.push(e(t.item||this).attr(t.attribute||"id")||"")}),s},_intersectsWith:function(e){var t=this.positionAbs.left,i=t+this.helperProportions.width,s=this.positionAbs.top,a=s+this.helperProportions.height,n=e.left,r=n+e.width,o=e.top,h=o+e.height,l=this.offset.click.top,u=this.offset.click.left,c="x"===this.options.axis||s+l>o&&h>s+l,d="y"===this.options.axis||t+u>n&&r>t+u,p=c&&d;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>e[this.floating?"width":"height"]?p:t+this.helperProportions.width/2>n&&r>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>o&&h>a-this.helperProportions.height/2},_intersectsWithPointer:function(e){var i="x"===this.options.axis||t(this.positionAbs.top+this.offset.click.top,e.top,e.height),s="y"===this.options.axis||t(this.positionAbs.left+this.offset.click.left,e.left,e.width),a=i&&s,n=this._getDragVerticalDirection(),r=this._getDragHorizontalDirection();return a?this.floating?r&&"right"===r||"down"===n?2:1:n&&("down"===n?2:1):!1},_intersectsWithSides:function(e){var i=t(this.positionAbs.top+this.offset.click.top,e.top+e.height/2,e.height),s=t(this.positionAbs.left+this.offset.click.left,e.left+e.width/2,e.width),a=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&s||"left"===n&&!s:a&&("down"===a&&i||"up"===a&&!i)},_getDragVerticalDirection:function(){var e=this.positionAbs.top-this.lastPositionAbs.top;return 0!==e&&(e>0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return 0!==e&&(e>0?"right":"left")},refresh:function(e){return this._refreshItems(e),this.refreshPositions(),this},_connectWith:function(){var e=this.options;return e.connectWith.constructor===String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(t){var i,s,a,n,r=[],o=[],h=this._connectWith();if(h&&t)for(i=h.length-1;i>=0;i--)for(a=e(h[i]),s=a.length-1;s>=0;s--)n=e.data(a[s],this.widgetFullName),n&&n!==this&&!n.options.disabled&&o.push([e.isFunction(n.options.items)?n.options.items.call(n.element):e(n.options.items,n.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),n]);for(o.push([e.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):e(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),i=o.length-1;i>=0;i--)o[i][0].each(function(){r.push(this)});return e(r)},_removeCurrentsFromItems:function(){var t=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=e.grep(this.items,function(e){for(var i=0;t.length>i;i++)if(t[i]===e.item[0])return!1;return!0})},_refreshItems:function(t){this.items=[],this.containers=[this];var i,s,a,n,r,o,h,l,u=this.items,c=[[e.isFunction(this.options.items)?this.options.items.call(this.element[0],t,{item:this.currentItem}):e(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(a=e(d[i]),s=a.length-1;s>=0;s--)n=e.data(a[s],this.widgetFullName),n&&n!==this&&!n.options.disabled&&(c.push([e.isFunction(n.options.items)?n.options.items.call(n.element[0],t,{item:this.currentItem}):e(n.options.items,n.element),n]),this.containers.push(n));for(i=c.length-1;i>=0;i--)for(r=c[i][1],o=c[i][0],s=0,l=o.length;l>s;s++)h=e(o[s]),h.data(this.widgetName+"-item",r),u.push({item:h,instance:r,width:0,height:0,left:0,top:0})},refreshPositions:function(t){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,a,n;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(a=this.options.toleranceElement?e(this.options.toleranceElement,s.item):s.item,t||(s.width=a.outerWidth(),s.height=a.outerHeight()),n=a.offset(),s.left=n.left,s.top=n.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)n=this.containers[i].element.offset(),this.containers[i].containerCache.left=n.left,this.containers[i].containerCache.top=n.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(t){t=t||this;var i,s=t.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=t.currentItem[0].nodeName.toLowerCase(),a=e("<"+s+">",t.document[0]).addClass(i||t.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");return"tr"===s?t.currentItem.children().each(function(){e("<td> </td>",t.document[0]).attr("colspan",e(this).attr("colspan")||1).appendTo(a)}):"img"===s&&a.attr("src",t.currentItem.attr("src")),i||a.css("visibility","hidden"),a},update:function(e,a){(!i||s.forcePlaceholderSize)&&(a.height()||a.height(t.currentItem.innerHeight()-parseInt(t.currentItem.css("paddingTop")||0,10)-parseInt(t.currentItem.css("paddingBottom")||0,10)),a.width()||a.width(t.currentItem.innerWidth()-parseInt(t.currentItem.css("paddingLeft")||0,10)-parseInt(t.currentItem.css("paddingRight")||0,10)))}}),t.placeholder=e(s.placeholder.element.call(t.element,t.currentItem)),t.currentItem.after(t.placeholder),s.placeholder.update(t,t.placeholder)},_contactContainers:function(s){var a,n,r,o,h,l,u,c,d,p,f=null,m=null;for(a=this.containers.length-1;a>=0;a--)if(!e.contains(this.currentItem[0],this.containers[a].element[0]))if(this._intersectsWith(this.containers[a].containerCache)){if(f&&e.contains(this.containers[a].element[0],f.element[0]))continue;f=this.containers[a],m=a}else this.containers[a].containerCache.over&&(this.containers[a]._trigger("out",s,this._uiHash(this)),this.containers[a].containerCache.over=0);if(f)if(1===this.containers.length)this.containers[m].containerCache.over||(this.containers[m]._trigger("over",s,this._uiHash(this)),this.containers[m].containerCache.over=1);else{for(r=1e4,o=null,p=f.floating||i(this.currentItem),h=p?"left":"top",l=p?"width":"height",u=this.positionAbs[h]+this.offset.click[h],n=this.items.length-1;n>=0;n--)e.contains(this.containers[m].element[0],this.items[n].item[0])&&this.items[n].item[0]!==this.currentItem[0]&&(!p||t(this.positionAbs.top+this.offset.click.top,this.items[n].top,this.items[n].height))&&(c=this.items[n].item.offset()[h],d=!1,Math.abs(c-u)>Math.abs(c+this.items[n][l]-u)&&(d=!0,c+=this.items[n][l]),r>Math.abs(c-u)&&(r=Math.abs(c-u),o=this.items[n],this.direction=d?"up":"down"));if(!o&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[m])return;o?this._rearrange(s,o,null,!0):this._rearrange(s,null,this.containers[m].element,!0),this._trigger("change",s,this._uiHash()),this.containers[m]._trigger("change",s,this._uiHash(this)),this.currentContainer=this.containers[m],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[m]._trigger("over",s,this._uiHash(this)),this.containers[m].containerCache.over=1}},_createHelper:function(t){var i=this.options,s=e.isFunction(i.helper)?e(i.helper.apply(this.element[0],[t,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||e("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(t){"string"==typeof t&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&e.ui.ie)&&(t={top:0,left:0}),{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var e=this.currentItem.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,i,s,a=this.options;"parent"===a.containment&&(a.containment=this.helper[0].parentNode),("document"===a.containment||"window"===a.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,e("document"===a.containment?document:window).width()-this.helperProportions.width-this.margins.left,(e("document"===a.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(a.containment)||(t=e(a.containment)[0],i=e(a.containment).offset(),s="hidden"!==e(t).css("overflow"),this.containment=[i.left+(parseInt(e(t).css("borderLeftWidth"),10)||0)+(parseInt(e(t).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(e(t).css("borderTopWidth"),10)||0)+(parseInt(e(t).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(t.scrollWidth,t.offsetWidth):t.offsetWidth)-(parseInt(e(t).css("borderLeftWidth"),10)||0)-(parseInt(e(t).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(t.scrollHeight,t.offsetHeight):t.offsetHeight)-(parseInt(e(t).css("borderTopWidth"),10)||0)-(parseInt(e(t).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(t,i){i||(i=this.position);var s="absolute"===t?1:-1,a="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,n=/(html|body)/i.test(a[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():n?0:a.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():n?0:a.scrollLeft())*s}},_generatePosition:function(t){var i,s,a=this.options,n=t.pageX,r=t.pageY,o="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(o[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==document&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(t.pageX-this.offset.click.left<this.containment[0]&&(n=this.containment[0]+this.offset.click.left),t.pageY-this.offset.click.top<this.containment[1]&&(r=this.containment[1]+this.offset.click.top),t.pageX-this.offset.click.left>this.containment[2]&&(n=this.containment[2]+this.offset.click.left),t.pageY-this.offset.click.top>this.containment[3]&&(r=this.containment[3]+this.offset.click.top)),a.grid&&(i=this.originalPageY+Math.round((r-this.originalPageY)/a.grid[1])*a.grid[1],r=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-a.grid[1]:i+a.grid[1]:i,s=this.originalPageX+Math.round((n-this.originalPageX)/a.grid[0])*a.grid[0],n=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-a.grid[0]:s+a.grid[0]:s)),{top:r-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:o.scrollTop()),left:n-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:o.scrollLeft())}},_rearrange:function(e,t,i,s){i?i[0].appendChild(this.placeholder[0]):t.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?t.item[0]:t.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var a=this.counter;this._delay(function(){a===this.counter&&this.refreshPositions(!s)})},_clear:function(e,t){this.reverting=!1;var i,s=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(i in this._storedCSS)("auto"===this._storedCSS[i]||"static"===this._storedCSS[i])&&(this._storedCSS[i]="");this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!t&&s.push(function(e){this._trigger("receive",e,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||t||s.push(function(e){this._trigger("update",e,this._uiHash())}),this!==this.currentContainer&&(t||(s.push(function(e){this._trigger("remove",e,this._uiHash())}),s.push(function(e){return function(t){e._trigger("receive",t,this._uiHash(this))}}.call(this,this.currentContainer)),s.push(function(e){return function(t){e._trigger("update",t,this._uiHash(this))}}.call(this,this.currentContainer)))),i=this.containers.length-1;i>=0;i--)t||s.push(function(e){return function(t){e._trigger("deactivate",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over&&(s.push(function(e){return function(t){e._trigger("out",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,this.cancelHelperRemoval){if(!t){for(this._trigger("beforeStop",e,this._uiHash()),i=0;s.length>i;i++)s[i].call(this,e);this._trigger("stop",e,this._uiHash())}return this.fromOutside=!1,!1}if(t||this._trigger("beforeStop",e,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null,!t){for(i=0;s.length>i;i++)s[i].call(this,e);this._trigger("stop",e,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){e.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(t){var i=t||this;return{helper:i.helper,placeholder:i.placeholder||e([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:t?t.element:null}}})})(jQuery); \ No newline at end of file diff --git a/feincms/static/feincms/jquery-ui-1.8.13.custom.js b/feincms/static/feincms/jquery-ui-1.8.13.custom.js deleted file mode 100644 index d1c428a3f..000000000 --- a/feincms/static/feincms/jquery-ui-1.8.13.custom.js +++ /dev/null @@ -1,3657 +0,0 @@ -/*! - * jQuery UI 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI - */ -(function( $, undefined ) { - -// prevent duplicate loading -// this is only a problem because we proxy existing functions -// and we don't want to double proxy them -$.ui = $.ui || {}; -if ( $.ui.version ) { - return; -} - -$.extend( $.ui, { - version: "1.8.13", - - keyCode: { - ALT: 18, - BACKSPACE: 8, - CAPS_LOCK: 20, - COMMA: 188, - COMMAND: 91, - COMMAND_LEFT: 91, // COMMAND - COMMAND_RIGHT: 93, - CONTROL: 17, - DELETE: 46, - DOWN: 40, - END: 35, - ENTER: 13, - ESCAPE: 27, - HOME: 36, - INSERT: 45, - LEFT: 37, - MENU: 93, // COMMAND_RIGHT - NUMPAD_ADD: 107, - NUMPAD_DECIMAL: 110, - NUMPAD_DIVIDE: 111, - NUMPAD_ENTER: 108, - NUMPAD_MULTIPLY: 106, - NUMPAD_SUBTRACT: 109, - PAGE_DOWN: 34, - PAGE_UP: 33, - PERIOD: 190, - RIGHT: 39, - SHIFT: 16, - SPACE: 32, - TAB: 9, - UP: 38, - WINDOWS: 91 // COMMAND - } -}); - -// plugins -$.fn.extend({ - _focus: $.fn.focus, - focus: function( delay, fn ) { - return typeof delay === "number" ? - this.each(function() { - var elem = this; - setTimeout(function() { - $( elem ).focus(); - if ( fn ) { - fn.call( elem ); - } - }, delay ); - }) : - this._focus.apply( this, arguments ); - }, - - scrollParent: function() { - var scrollParent; - if (($.browser.msie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) { - scrollParent = this.parents().filter(function() { - return (/(relative|absolute|fixed)/).test($.curCSS(this,'position',1)) && (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1)); - }).eq(0); - } else { - scrollParent = this.parents().filter(function() { - return (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1)); - }).eq(0); - } - - return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent; - }, - - zIndex: function( zIndex ) { - if ( zIndex !== undefined ) { - return this.css( "zIndex", zIndex ); - } - - if ( this.length ) { - var elem = $( this[ 0 ] ), position, value; - while ( elem.length && elem[ 0 ] !== document ) { - // Ignore z-index if position is set to a value where z-index is ignored by the browser - // This makes behavior of this function consistent across browsers - // WebKit always returns auto if the element is positioned - position = elem.css( "position" ); - if ( position === "absolute" || position === "relative" || position === "fixed" ) { - // IE returns 0 when zIndex is not specified - // other browsers return a string - // we ignore the case of nested elements with an explicit value of 0 - // <div style="z-index: -10;"><div style="z-index: 0;"></div></div> - value = parseInt( elem.css( "zIndex" ), 10 ); - if ( !isNaN( value ) && value !== 0 ) { - return value; - } - } - elem = elem.parent(); - } - } - - return 0; - }, - - disableSelection: function() { - return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + - ".ui-disableSelection", function( event ) { - event.preventDefault(); - }); - }, - - enableSelection: function() { - return this.unbind( ".ui-disableSelection" ); - } -}); - -$.each( [ "Width", "Height" ], function( i, name ) { - var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], - type = name.toLowerCase(), - orig = { - innerWidth: $.fn.innerWidth, - innerHeight: $.fn.innerHeight, - outerWidth: $.fn.outerWidth, - outerHeight: $.fn.outerHeight - }; - - function reduce( elem, size, border, margin ) { - $.each( side, function() { - size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0; - if ( border ) { - size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0; - } - if ( margin ) { - size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0; - } - }); - return size; - } - - $.fn[ "inner" + name ] = function( size ) { - if ( size === undefined ) { - return orig[ "inner" + name ].call( this ); - } - - return this.each(function() { - $( this ).css( type, reduce( this, size ) + "px" ); - }); - }; - - $.fn[ "outer" + name] = function( size, margin ) { - if ( typeof size !== "number" ) { - return orig[ "outer" + name ].call( this, size ); - } - - return this.each(function() { - $( this).css( type, reduce( this, size, true, margin ) + "px" ); - }); - }; -}); - -// selectors -function focusable( element, isTabIndexNotNaN ) { - var nodeName = element.nodeName.toLowerCase(); - if ( "area" === nodeName ) { - var map = element.parentNode, - mapName = map.name, - img; - if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { - return false; - } - img = $( "img[usemap=#" + mapName + "]" )[0]; - return !!img && visible( img ); - } - return ( /input|select|textarea|button|object/.test( nodeName ) - ? !element.disabled - : "a" == nodeName - ? element.href || isTabIndexNotNaN - : isTabIndexNotNaN) - // the element and all of its ancestors must be visible - && visible( element ); -} - -function visible( element ) { - return !$( element ).parents().andSelf().filter(function() { - return $.curCSS( this, "visibility" ) === "hidden" || - $.expr.filters.hidden( this ); - }).length; -} - -$.extend( $.expr[ ":" ], { - data: function( elem, i, match ) { - return !!$.data( elem, match[ 3 ] ); - }, - - focusable: function( element ) { - return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); - }, - - tabbable: function( element ) { - var tabIndex = $.attr( element, "tabindex" ), - isTabIndexNaN = isNaN( tabIndex ); - return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); - } -}); - -// support -$(function() { - var body = document.body, - div = body.appendChild( div = document.createElement( "div" ) ); - - $.extend( div.style, { - minHeight: "100px", - height: "auto", - padding: 0, - borderWidth: 0 - }); - - $.support.minHeight = div.offsetHeight === 100; - $.support.selectstart = "onselectstart" in div; - - // set display to none to avoid a layout bug in IE - // http://dev.jquery.com/ticket/4014 - body.removeChild( div ).style.display = "none"; -}); - - - - - -// deprecated -$.extend( $.ui, { - // $.ui.plugin is deprecated. Use the proxy pattern instead. - plugin: { - add: function( module, option, set ) { - var proto = $.ui[ module ].prototype; - for ( var i in set ) { - proto.plugins[ i ] = proto.plugins[ i ] || []; - proto.plugins[ i ].push( [ option, set[ i ] ] ); - } - }, - call: function( instance, name, args ) { - var set = instance.plugins[ name ]; - if ( !set || !instance.element[ 0 ].parentNode ) { - return; - } - - for ( var i = 0; i < set.length; i++ ) { - if ( instance.options[ set[ i ][ 0 ] ] ) { - set[ i ][ 1 ].apply( instance.element, args ); - } - } - } - }, - - // will be deprecated when we switch to jQuery 1.4 - use jQuery.contains() - contains: function( a, b ) { - return document.compareDocumentPosition ? - a.compareDocumentPosition( b ) & 16 : - a !== b && a.contains( b ); - }, - - // only used by resizable - hasScroll: function( el, a ) { - - //If overflow is hidden, the element might have extra content, but the user wants to hide it - if ( $( el ).css( "overflow" ) === "hidden") { - return false; - } - - var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", - has = false; - - if ( el[ scroll ] > 0 ) { - return true; - } - - // TODO: determine which cases actually cause this to happen - // if the element doesn't have the scroll set, see if it's possible to - // set the scroll - el[ scroll ] = 1; - has = ( el[ scroll ] > 0 ); - el[ scroll ] = 0; - return has; - }, - - // these are odd functions, fix the API or move into individual plugins - isOverAxis: function( x, reference, size ) { - //Determines when x coordinate is over "b" element axis - return ( x > reference ) && ( x < ( reference + size ) ); - }, - isOver: function( y, x, top, left, height, width ) { - //Determines when x, y coordinates is over "b" element - return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width ); - } -}); - -})( jQuery ); -/*! - * jQuery UI Widget 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Widget - */ -(function( $, undefined ) { - -// jQuery 1.4+ -if ( $.cleanData ) { - var _cleanData = $.cleanData; - $.cleanData = function( elems ) { - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { - $( elem ).triggerHandler( "remove" ); - } - _cleanData( elems ); - }; -} else { - var _remove = $.fn.remove; - $.fn.remove = function( selector, keepData ) { - return this.each(function() { - if ( !keepData ) { - if ( !selector || $.filter( selector, [ this ] ).length ) { - $( "*", this ).add( [ this ] ).each(function() { - $( this ).triggerHandler( "remove" ); - }); - } - } - return _remove.call( $(this), selector, keepData ); - }); - }; -} - -$.widget = function( name, base, prototype ) { - var namespace = name.split( "." )[ 0 ], - fullName; - name = name.split( "." )[ 1 ]; - fullName = namespace + "-" + name; - - if ( !prototype ) { - prototype = base; - base = $.Widget; - } - - // create selector for plugin - $.expr[ ":" ][ fullName ] = function( elem ) { - return !!$.data( elem, name ); - }; - - $[ namespace ] = $[ namespace ] || {}; - $[ namespace ][ name ] = function( options, element ) { - // allow instantiation without initializing for simple inheritance - if ( arguments.length ) { - this._createWidget( options, element ); - } - }; - - var basePrototype = new base(); - // we need to make the options hash a property directly on the new instance - // otherwise we'll modify the options hash on the prototype that we're - // inheriting from -// $.each( basePrototype, function( key, val ) { -// if ( $.isPlainObject(val) ) { -// basePrototype[ key ] = $.extend( {}, val ); -// } -// }); - basePrototype.options = $.extend( true, {}, basePrototype.options ); - $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { - namespace: namespace, - widgetName: name, - widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, - widgetBaseClass: fullName - }, prototype ); - - $.widget.bridge( name, $[ namespace ][ name ] ); -}; - -$.widget.bridge = function( name, object ) { - $.fn[ name ] = function( options ) { - var isMethodCall = typeof options === "string", - args = Array.prototype.slice.call( arguments, 1 ), - returnValue = this; - - // allow multiple hashes to be passed on init - options = !isMethodCall && args.length ? - $.extend.apply( null, [ true, options ].concat(args) ) : - options; - - // prevent calls to internal methods - if ( isMethodCall && options.charAt( 0 ) === "_" ) { - return returnValue; - } - - if ( isMethodCall ) { - this.each(function() { - var instance = $.data( this, name ), - methodValue = instance && $.isFunction( instance[options] ) ? - instance[ options ].apply( instance, args ) : - instance; - // TODO: add this back in 1.9 and use $.error() (see #5972) -// if ( !instance ) { -// throw "cannot call methods on " + name + " prior to initialization; " + -// "attempted to call method '" + options + "'"; -// } -// if ( !$.isFunction( instance[options] ) ) { -// throw "no such method '" + options + "' for " + name + " widget instance"; -// } -// var methodValue = instance[ options ].apply( instance, args ); - if ( methodValue !== instance && methodValue !== undefined ) { - returnValue = methodValue; - return false; - } - }); - } else { - this.each(function() { - var instance = $.data( this, name ); - if ( instance ) { - instance.option( options || {} )._init(); - } else { - $.data( this, name, new object( options, this ) ); - } - }); - } - - return returnValue; - }; -}; - -$.Widget = function( options, element ) { - // allow instantiation without initializing for simple inheritance - if ( arguments.length ) { - this._createWidget( options, element ); - } -}; - -$.Widget.prototype = { - widgetName: "widget", - widgetEventPrefix: "", - options: { - disabled: false - }, - _createWidget: function( options, element ) { - // $.widget.bridge stores the plugin instance, but we do it anyway - // so that it's stored even before the _create function runs - $.data( element, this.widgetName, this ); - this.element = $( element ); - this.options = $.extend( true, {}, - this.options, - this._getCreateOptions(), - options ); - - var self = this; - this.element.bind( "remove." + this.widgetName, function() { - self.destroy(); - }); - - this._create(); - this._trigger( "create" ); - this._init(); - }, - _getCreateOptions: function() { - return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; - }, - _create: function() {}, - _init: function() {}, - - destroy: function() { - this.element - .unbind( "." + this.widgetName ) - .removeData( this.widgetName ); - this.widget() - .unbind( "." + this.widgetName ) - .removeAttr( "aria-disabled" ) - .removeClass( - this.widgetBaseClass + "-disabled " + - "ui-state-disabled" ); - }, - - widget: function() { - return this.element; - }, - - option: function( key, value ) { - var options = key; - - if ( arguments.length === 0 ) { - // don't return a reference to the internal hash - return $.extend( {}, this.options ); - } - - if (typeof key === "string" ) { - if ( value === undefined ) { - return this.options[ key ]; - } - options = {}; - options[ key ] = value; - } - - this._setOptions( options ); - - return this; - }, - _setOptions: function( options ) { - var self = this; - $.each( options, function( key, value ) { - self._setOption( key, value ); - }); - - return this; - }, - _setOption: function( key, value ) { - this.options[ key ] = value; - - if ( key === "disabled" ) { - this.widget() - [ value ? "addClass" : "removeClass"]( - this.widgetBaseClass + "-disabled" + " " + - "ui-state-disabled" ) - .attr( "aria-disabled", value ); - } - - return this; - }, - - enable: function() { - return this._setOption( "disabled", false ); - }, - disable: function() { - return this._setOption( "disabled", true ); - }, - - _trigger: function( type, event, data ) { - var callback = this.options[ type ]; - - event = $.Event( event ); - event.type = ( type === this.widgetEventPrefix ? - type : - this.widgetEventPrefix + type ).toLowerCase(); - data = data || {}; - - // copy original event properties over to the new event - // this would happen if we could call $.event.fix instead of $.Event - // but we don't have a way to force an event to be fixed multiple times - if ( event.originalEvent ) { - for ( var i = $.event.props.length, prop; i; ) { - prop = $.event.props[ --i ]; - event[ prop ] = event.originalEvent[ prop ]; - } - } - - this.element.trigger( event, data ); - - return !( $.isFunction(callback) && - callback.call( this.element[0], event, data ) === false || - event.isDefaultPrevented() ); - } -}; - -})( jQuery ); -/*! - * jQuery UI Mouse 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Mouse - * - * Depends: - * jquery.ui.widget.js - */ -(function( $, undefined ) { - -var mouseHandled = false; -$(document).mousedown(function(e) { - mouseHandled = false; -}); - -$.widget("ui.mouse", { - options: { - cancel: ':input,option', - distance: 1, - delay: 0 - }, - _mouseInit: function() { - var self = this; - - this.element - .bind('mousedown.'+this.widgetName, function(event) { - return self._mouseDown(event); - }) - .bind('click.'+this.widgetName, function(event) { - if (true === $.data(event.target, self.widgetName + '.preventClickEvent')) { - $.removeData(event.target, self.widgetName + '.preventClickEvent'); - event.stopImmediatePropagation(); - return false; - } - }); - - this.started = false; - }, - - // TODO: make sure destroying one instance of mouse doesn't mess with - // other instances of mouse - _mouseDestroy: function() { - this.element.unbind('.'+this.widgetName); - }, - - _mouseDown: function(event) { - // don't let more than one widget handle mouseStart - if(mouseHandled) {return}; - - // we may have missed mouseup (out of window) - (this._mouseStarted && this._mouseUp(event)); - - this._mouseDownEvent = event; - - var self = this, - btnIsLeft = (event.which == 1), - elIsCancel = (typeof this.options.cancel == "string" ? $(event.target).parents().add(event.target).filter(this.options.cancel).length : false); - if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { - return true; - } - - this.mouseDelayMet = !this.options.delay; - if (!this.mouseDelayMet) { - this._mouseDelayTimer = setTimeout(function() { - self.mouseDelayMet = true; - }, this.options.delay); - } - - if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { - this._mouseStarted = (this._mouseStart(event) !== false); - if (!this._mouseStarted) { - event.preventDefault(); - return true; - } - } - - // Click event may never have fired (Gecko & Opera) - if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) { - $.removeData(event.target, this.widgetName + '.preventClickEvent'); - } - - // these delegates are required to keep context - this._mouseMoveDelegate = function(event) { - return self._mouseMove(event); - }; - this._mouseUpDelegate = function(event) { - return self._mouseUp(event); - }; - $(document) - .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate) - .bind('mouseup.'+this.widgetName, this._mouseUpDelegate); - - event.preventDefault(); - - mouseHandled = true; - return true; - }, - - _mouseMove: function(event) { - // IE mouseup check - mouseup happened when mouse was out of window - if ($.browser.msie && !(document.documentMode >= 9) && !event.button) { - return this._mouseUp(event); - } - - if (this._mouseStarted) { - this._mouseDrag(event); - return event.preventDefault(); - } - - if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { - this._mouseStarted = - (this._mouseStart(this._mouseDownEvent, event) !== false); - (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); - } - - return !this._mouseStarted; - }, - - _mouseUp: function(event) { - $(document) - .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate) - .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate); - - if (this._mouseStarted) { - this._mouseStarted = false; - - if (event.target == this._mouseDownEvent.target) { - $.data(event.target, this.widgetName + '.preventClickEvent', true); - } - - this._mouseStop(event); - } - - return false; - }, - - _mouseDistanceMet: function(event) { - return (Math.max( - Math.abs(this._mouseDownEvent.pageX - event.pageX), - Math.abs(this._mouseDownEvent.pageY - event.pageY) - ) >= this.options.distance - ); - }, - - _mouseDelayMet: function(event) { - return this.mouseDelayMet; - }, - - // These are placeholder methods, to be overriden by extending plugin - _mouseStart: function(event) {}, - _mouseDrag: function(event) {}, - _mouseStop: function(event) {}, - _mouseCapture: function(event) { return true; } -}); - -})(jQuery); -/* - * jQuery UI Draggable 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Draggables - * - * Depends: - * jquery.ui.core.js - * jquery.ui.mouse.js - * jquery.ui.widget.js - */ -(function( $, undefined ) { - -$.widget("ui.draggable", $.ui.mouse, { - widgetEventPrefix: "drag", - options: { - addClasses: true, - appendTo: "parent", - axis: false, - connectToSortable: false, - containment: false, - cursor: "auto", - cursorAt: false, - grid: false, - handle: false, - helper: "original", - iframeFix: false, - opacity: false, - refreshPositions: false, - revert: false, - revertDuration: 500, - scope: "default", - scroll: true, - scrollSensitivity: 20, - scrollSpeed: 20, - snap: false, - snapMode: "both", - snapTolerance: 20, - stack: false, - zIndex: false - }, - _create: function() { - - if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position"))) - this.element[0].style.position = 'relative'; - - (this.options.addClasses && this.element.addClass("ui-draggable")); - (this.options.disabled && this.element.addClass("ui-draggable-disabled")); - - this._mouseInit(); - - }, - - destroy: function() { - if(!this.element.data('draggable')) return; - this.element - .removeData("draggable") - .unbind(".draggable") - .removeClass("ui-draggable" - + " ui-draggable-dragging" - + " ui-draggable-disabled"); - this._mouseDestroy(); - - return this; - }, - - _mouseCapture: function(event) { - - var o = this.options; - - // among others, prevent a drag on a resizable-handle - if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle')) - return false; - - //Quit if we're not on a valid handle - this.handle = this._getHandle(event); - if (!this.handle) - return false; - - $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() { - $('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>') - .css({ - width: this.offsetWidth+"px", height: this.offsetHeight+"px", - position: "absolute", opacity: "0.001", zIndex: 1000 - }) - .css($(this).offset()) - .appendTo("body"); - }); - - return true; - - }, - - _mouseStart: function(event) { - - var o = this.options; - - //Create and append the visible helper - this.helper = this._createHelper(event); - - //Cache the helper size - this._cacheHelperProportions(); - - //If ddmanager is used for droppables, set the global draggable - if($.ui.ddmanager) - $.ui.ddmanager.current = this; - - /* - * - Position generation - - * This block generates everything position related - it's the core of draggables. - */ - - //Cache the margins of the original element - this._cacheMargins(); - - //Store the helper's css position - this.cssPosition = this.helper.css("position"); - this.scrollParent = this.helper.scrollParent(); - - //The element's absolute position on the page minus margins - this.offset = this.positionAbs = this.element.offset(); - this.offset = { - top: this.offset.top - this.margins.top, - left: this.offset.left - this.margins.left - }; - - $.extend(this.offset, { - click: { //Where the click happened, relative to the element - left: event.pageX - this.offset.left, - top: event.pageY - this.offset.top - }, - parent: this._getParentOffset(), - relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper - }); - - //Generate the original position - this.originalPosition = this.position = this._generatePosition(event); - this.originalPageX = event.pageX; - this.originalPageY = event.pageY; - - //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied - (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); - - //Set a containment if given in the options - if(o.containment) - this._setContainment(); - - //Trigger event + callbacks - if(this._trigger("start", event) === false) { - this._clear(); - return false; - } - - //Recache the helper size - this._cacheHelperProportions(); - - //Prepare the droppable offsets - if ($.ui.ddmanager && !o.dropBehaviour) - $.ui.ddmanager.prepareOffsets(this, event); - - this.helper.addClass("ui-draggable-dragging"); - this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position - return true; - }, - - _mouseDrag: function(event, noPropagation) { - - //Compute the helpers position - this.position = this._generatePosition(event); - this.positionAbs = this._convertPositionTo("absolute"); - - //Call plugins and callbacks and use the resulting position if something is returned - if (!noPropagation) { - var ui = this._uiHash(); - if(this._trigger('drag', event, ui) === false) { - this._mouseUp({}); - return false; - } - this.position = ui.position; - } - - if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px'; - if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px'; - if($.ui.ddmanager) $.ui.ddmanager.drag(this, event); - - return false; - }, - - _mouseStop: function(event) { - - //If we are using droppables, inform the manager about the drop - var dropped = false; - if ($.ui.ddmanager && !this.options.dropBehaviour) - dropped = $.ui.ddmanager.drop(this, event); - - //if a drop comes from outside (a sortable) - if(this.dropped) { - dropped = this.dropped; - this.dropped = false; - } - - //if the original element is removed, don't bother to continue if helper is set to "original" - if((!this.element[0] || !this.element[0].parentNode) && this.options.helper == "original") - return false; - - if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) { - var self = this; - $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() { - if(self._trigger("stop", event) !== false) { - self._clear(); - } - }); - } else { - if(this._trigger("stop", event) !== false) { - this._clear(); - } - } - - return false; - }, - - _mouseUp: function(event) { - if (this.options.iframeFix === true) { - $("div.ui-draggable-iframeFix").each(function() { - this.parentNode.removeChild(this); - }); //Remove frame helpers - } - - return $.ui.mouse.prototype._mouseUp.call(this, event); - }, - - cancel: function() { - - if(this.helper.is(".ui-draggable-dragging")) { - this._mouseUp({}); - } else { - this._clear(); - } - - return this; - - }, - - _getHandle: function(event) { - - var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false; - $(this.options.handle, this.element) - .find("*") - .andSelf() - .each(function() { - if(this == event.target) handle = true; - }); - - return handle; - - }, - - _createHelper: function(event) { - - var o = this.options; - var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone().removeAttr('id') : this.element); - - if(!helper.parents('body').length) - helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo)); - - if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) - helper.css("position", "absolute"); - - return helper; - - }, - - _adjustOffsetFromHelper: function(obj) { - if (typeof obj == 'string') { - obj = obj.split(' '); - } - if ($.isArray(obj)) { - obj = {left: +obj[0], top: +obj[1] || 0}; - } - if ('left' in obj) { - this.offset.click.left = obj.left + this.margins.left; - } - if ('right' in obj) { - this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; - } - if ('top' in obj) { - this.offset.click.top = obj.top + this.margins.top; - } - if ('bottom' in obj) { - this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; - } - }, - - _getParentOffset: function() { - - //Get the offsetParent and cache its position - this.offsetParent = this.helper.offsetParent(); - var po = this.offsetParent.offset(); - - // This is a special case where we need to modify a offset calculated on start, since the following happened: - // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent - // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that - // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag - if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) { - po.left += this.scrollParent.scrollLeft(); - po.top += this.scrollParent.scrollTop(); - } - - if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information - || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix - po = { top: 0, left: 0 }; - - return { - top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), - left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) - }; - - }, - - _getRelativeOffset: function() { - - if(this.cssPosition == "relative") { - var p = this.element.position(); - return { - top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), - left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() - }; - } else { - return { top: 0, left: 0 }; - } - - }, - - _cacheMargins: function() { - this.margins = { - left: (parseInt(this.element.css("marginLeft"),10) || 0), - top: (parseInt(this.element.css("marginTop"),10) || 0), - right: (parseInt(this.element.css("marginRight"),10) || 0), - bottom: (parseInt(this.element.css("marginBottom"),10) || 0) - }; - }, - - _cacheHelperProportions: function() { - this.helperProportions = { - width: this.helper.outerWidth(), - height: this.helper.outerHeight() - }; - }, - - _setContainment: function() { - - var o = this.options; - if(o.containment == 'parent') o.containment = this.helper[0].parentNode; - if(o.containment == 'document' || o.containment == 'window') this.containment = [ - (o.containment == 'document' ? 0 : $(window).scrollLeft()) - this.offset.relative.left - this.offset.parent.left, - (o.containment == 'document' ? 0 : $(window).scrollTop()) - this.offset.relative.top - this.offset.parent.top, - (o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left, - (o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top - ]; - - if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) { - var c = $(o.containment); - var ce = c[0]; if(!ce) return; - var co = c.offset(); - var over = ($(ce).css("overflow") != 'hidden'); - - this.containment = [ - (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0), - (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0), - (over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right, - (over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - this.margins.bottom - ]; - this.relative_container = c; - - } else if(o.containment.constructor == Array) { - this.containment = o.containment; - } - - }, - - _convertPositionTo: function(d, pos) { - - if(!pos) pos = this.position; - var mod = d == "absolute" ? 1 : -1; - var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); - - return { - top: ( - pos.top // The absolute mouse position - + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent - + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border) - - ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) - ), - left: ( - pos.left // The absolute mouse position - + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent - + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border) - - ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) - ) - }; - - }, - - _generatePosition: function(event) { - - var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); - var pageX = event.pageX; - var pageY = event.pageY; - - /* - * - Position constraining - - * Constrain the position to a mix of grid, containment. - */ - - if(this.originalPosition) { //If we are not dragging yet, we won't check for options - var containment; - if(this.containment) { - if (this.relative_container){ - var co = this.relative_container.offset(); - containment = [ this.containment[0] + co.left, - this.containment[1] + co.top, - this.containment[2] + co.left, - this.containment[3] + co.top ]; - } - else { - containment = this.containment; - } - - if(event.pageX - this.offset.click.left < containment[0]) pageX = containment[0] + this.offset.click.left; - if(event.pageY - this.offset.click.top < containment[1]) pageY = containment[1] + this.offset.click.top; - if(event.pageX - this.offset.click.left > containment[2]) pageX = containment[2] + this.offset.click.left; - if(event.pageY - this.offset.click.top > containment[3]) pageY = containment[3] + this.offset.click.top; - } - - if(o.grid) { - var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1]; - pageY = containment ? (!(top - this.offset.click.top < containment[1] || top - this.offset.click.top > containment[3]) ? top : (!(top - this.offset.click.top < containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; - - var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0]; - pageX = containment ? (!(left - this.offset.click.left < containment[0] || left - this.offset.click.left > containment[2]) ? left : (!(left - this.offset.click.left < containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; - } - - } - - return { - top: ( - pageY // The absolute mouse position - - this.offset.click.top // Click offset (relative to the element) - - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent - - this.offset.parent.top // The offsetParent's offset without borders (offset + border) - + ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) - ), - left: ( - pageX // The absolute mouse position - - this.offset.click.left // Click offset (relative to the element) - - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent - - this.offset.parent.left // The offsetParent's offset without borders (offset + border) - + ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) - ) - }; - - }, - - _clear: function() { - this.helper.removeClass("ui-draggable-dragging"); - if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove(); - //if($.ui.ddmanager) $.ui.ddmanager.current = null; - this.helper = null; - this.cancelHelperRemoval = false; - }, - - // From now on bulk stuff - mainly helpers - - _trigger: function(type, event, ui) { - ui = ui || this._uiHash(); - $.ui.plugin.call(this, type, [event, ui]); - if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins - return $.Widget.prototype._trigger.call(this, type, event, ui); - }, - - plugins: {}, - - _uiHash: function(event) { - return { - helper: this.helper, - position: this.position, - originalPosition: this.originalPosition, - offset: this.positionAbs - }; - } - -}); - -$.extend($.ui.draggable, { - version: "1.8.13" -}); - -$.ui.plugin.add("draggable", "connectToSortable", { - start: function(event, ui) { - - var inst = $(this).data("draggable"), o = inst.options, - uiSortable = $.extend({}, ui, { item: inst.element }); - inst.sortables = []; - $(o.connectToSortable).each(function() { - var sortable = $.data(this, 'sortable'); - if (sortable && !sortable.options.disabled) { - inst.sortables.push({ - instance: sortable, - shouldRevert: sortable.options.revert - }); - sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page). - sortable._trigger("activate", event, uiSortable); - } - }); - - }, - stop: function(event, ui) { - - //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper - var inst = $(this).data("draggable"), - uiSortable = $.extend({}, ui, { item: inst.element }); - - $.each(inst.sortables, function() { - if(this.instance.isOver) { - - this.instance.isOver = 0; - - inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance - this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work) - - //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid' - if(this.shouldRevert) this.instance.options.revert = true; - - //Trigger the stop of the sortable - this.instance._mouseStop(event); - - this.instance.options.helper = this.instance.options._helper; - - //If the helper has been the original item, restore properties in the sortable - if(inst.options.helper == 'original') - this.instance.currentItem.css({ top: 'auto', left: 'auto' }); - - } else { - this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance - this.instance._trigger("deactivate", event, uiSortable); - } - - }); - - }, - drag: function(event, ui) { - - var inst = $(this).data("draggable"), self = this; - - var checkPos = function(o) { - var dyClick = this.offset.click.top, dxClick = this.offset.click.left; - var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left; - var itemHeight = o.height, itemWidth = o.width; - var itemTop = o.top, itemLeft = o.left; - - return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth); - }; - - $.each(inst.sortables, function(i) { - - //Copy over some variables to allow calling the sortable's native _intersectsWith - this.instance.positionAbs = inst.positionAbs; - this.instance.helperProportions = inst.helperProportions; - this.instance.offset.click = inst.offset.click; - - if(this.instance._intersectsWith(this.instance.containerCache)) { - - //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once - if(!this.instance.isOver) { - - this.instance.isOver = 1; - //Now we fake the start of dragging for the sortable instance, - //by cloning the list group item, appending it to the sortable and using it as inst.currentItem - //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one) - this.instance.currentItem = $(self).clone().removeAttr('id').appendTo(this.instance.element).data("sortable-item", true); - this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it - this.instance.options.helper = function() { return ui.helper[0]; }; - - event.target = this.instance.currentItem[0]; - this.instance._mouseCapture(event, true); - this.instance._mouseStart(event, true, true); - - //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes - this.instance.offset.click.top = inst.offset.click.top; - this.instance.offset.click.left = inst.offset.click.left; - this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left; - this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top; - - inst._trigger("toSortable", event); - inst.dropped = this.instance.element; //draggable revert needs that - //hack so receive/update callbacks work (mostly) - inst.currentItem = inst.element; - this.instance.fromOutside = inst; - - } - - //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable - if(this.instance.currentItem) this.instance._mouseDrag(event); - - } else { - - //If it doesn't intersect with the sortable, and it intersected before, - //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval - if(this.instance.isOver) { - - this.instance.isOver = 0; - this.instance.cancelHelperRemoval = true; - - //Prevent reverting on this forced stop - this.instance.options.revert = false; - - // The out event needs to be triggered independently - this.instance._trigger('out', event, this.instance._uiHash(this.instance)); - - this.instance._mouseStop(event, true); - this.instance.options.helper = this.instance.options._helper; - - //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size - this.instance.currentItem.remove(); - if(this.instance.placeholder) this.instance.placeholder.remove(); - - inst._trigger("fromSortable", event); - inst.dropped = false; //draggable revert needs that - } - - }; - - }); - - } -}); - -$.ui.plugin.add("draggable", "cursor", { - start: function(event, ui) { - var t = $('body'), o = $(this).data('draggable').options; - if (t.css("cursor")) o._cursor = t.css("cursor"); - t.css("cursor", o.cursor); - }, - stop: function(event, ui) { - var o = $(this).data('draggable').options; - if (o._cursor) $('body').css("cursor", o._cursor); - } -}); - -$.ui.plugin.add("draggable", "opacity", { - start: function(event, ui) { - var t = $(ui.helper), o = $(this).data('draggable').options; - if(t.css("opacity")) o._opacity = t.css("opacity"); - t.css('opacity', o.opacity); - }, - stop: function(event, ui) { - var o = $(this).data('draggable').options; - if(o._opacity) $(ui.helper).css('opacity', o._opacity); - } -}); - -$.ui.plugin.add("draggable", "scroll", { - start: function(event, ui) { - var i = $(this).data("draggable"); - if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset(); - }, - drag: function(event, ui) { - - var i = $(this).data("draggable"), o = i.options, scrolled = false; - - if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') { - - if(!o.axis || o.axis != 'x') { - if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) - i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed; - else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) - i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed; - } - - if(!o.axis || o.axis != 'y') { - if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) - i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed; - else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) - i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed; - } - - } else { - - if(!o.axis || o.axis != 'x') { - if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) - scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); - else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) - scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); - } - - if(!o.axis || o.axis != 'y') { - if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) - scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); - else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) - scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); - } - - } - - if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) - $.ui.ddmanager.prepareOffsets(i, event); - - } -}); - -$.ui.plugin.add("draggable", "snap", { - start: function(event, ui) { - - var i = $(this).data("draggable"), o = i.options; - i.snapElements = []; - - $(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() { - var $t = $(this); var $o = $t.offset(); - if(this != i.element[0]) i.snapElements.push({ - item: this, - width: $t.outerWidth(), height: $t.outerHeight(), - top: $o.top, left: $o.left - }); - }); - - }, - drag: function(event, ui) { - - var inst = $(this).data("draggable"), o = inst.options; - var d = o.snapTolerance; - - var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width, - y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height; - - for (var i = inst.snapElements.length - 1; i >= 0; i--){ - - var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width, - t = inst.snapElements[i].top, b = t + inst.snapElements[i].height; - - //Yes, I know, this is insane ;) - if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) { - if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); - inst.snapElements[i].snapping = false; - continue; - } - - if(o.snapMode != 'inner') { - var ts = Math.abs(t - y2) <= d; - var bs = Math.abs(b - y1) <= d; - var ls = Math.abs(l - x2) <= d; - var rs = Math.abs(r - x1) <= d; - if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top; - if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top; - if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left; - if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left; - } - - var first = (ts || bs || ls || rs); - - if(o.snapMode != 'outer') { - var ts = Math.abs(t - y1) <= d; - var bs = Math.abs(b - y2) <= d; - var ls = Math.abs(l - x1) <= d; - var rs = Math.abs(r - x2) <= d; - if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top; - if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top; - if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left; - if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left; - } - - if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) - (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); - inst.snapElements[i].snapping = (ts || bs || ls || rs || first); - - }; - - } -}); - -$.ui.plugin.add("draggable", "stack", { - start: function(event, ui) { - - var o = $(this).data("draggable").options; - - var group = $.makeArray($(o.stack)).sort(function(a,b) { - return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0); - }); - if (!group.length) { return; } - - var min = parseInt(group[0].style.zIndex) || 0; - $(group).each(function(i) { - this.style.zIndex = min + i; - }); - - this[0].style.zIndex = min + group.length; - - } -}); - -$.ui.plugin.add("draggable", "zIndex", { - start: function(event, ui) { - var t = $(ui.helper), o = $(this).data("draggable").options; - if(t.css("zIndex")) o._zIndex = t.css("zIndex"); - t.css('zIndex', o.zIndex); - }, - stop: function(event, ui) { - var o = $(this).data("draggable").options; - if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex); - } -}); - -})(jQuery); -/* - * jQuery UI Droppable 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Droppables - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - * jquery.ui.mouse.js - * jquery.ui.draggable.js - */ -(function( $, undefined ) { - -$.widget("ui.droppable", { - widgetEventPrefix: "drop", - options: { - accept: '*', - activeClass: false, - addClasses: true, - greedy: false, - hoverClass: false, - scope: 'default', - tolerance: 'intersect' - }, - _create: function() { - - var o = this.options, accept = o.accept; - this.isover = 0; this.isout = 1; - - this.accept = $.isFunction(accept) ? accept : function(d) { - return d.is(accept); - }; - - //Store the droppable's proportions - this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight }; - - // Add the reference and positions to the manager - $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || []; - $.ui.ddmanager.droppables[o.scope].push(this); - - (o.addClasses && this.element.addClass("ui-droppable")); - - }, - - destroy: function() { - var drop = $.ui.ddmanager.droppables[this.options.scope]; - for ( var i = 0; i < drop.length; i++ ) - if ( drop[i] == this ) - drop.splice(i, 1); - - this.element - .removeClass("ui-droppable ui-droppable-disabled") - .removeData("droppable") - .unbind(".droppable"); - - return this; - }, - - _setOption: function(key, value) { - - if(key == 'accept') { - this.accept = $.isFunction(value) ? value : function(d) { - return d.is(value); - }; - } - $.Widget.prototype._setOption.apply(this, arguments); - }, - - _activate: function(event) { - var draggable = $.ui.ddmanager.current; - if(this.options.activeClass) this.element.addClass(this.options.activeClass); - (draggable && this._trigger('activate', event, this.ui(draggable))); - }, - - _deactivate: function(event) { - var draggable = $.ui.ddmanager.current; - if(this.options.activeClass) this.element.removeClass(this.options.activeClass); - (draggable && this._trigger('deactivate', event, this.ui(draggable))); - }, - - _over: function(event) { - - var draggable = $.ui.ddmanager.current; - if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element - - if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { - if(this.options.hoverClass) this.element.addClass(this.options.hoverClass); - this._trigger('over', event, this.ui(draggable)); - } - - }, - - _out: function(event) { - - var draggable = $.ui.ddmanager.current; - if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element - - if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { - if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass); - this._trigger('out', event, this.ui(draggable)); - } - - }, - - _drop: function(event,custom) { - - var draggable = custom || $.ui.ddmanager.current; - if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element - - var childrenIntersection = false; - this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() { - var inst = $.data(this, 'droppable'); - if( - inst.options.greedy - && !inst.options.disabled - && inst.options.scope == draggable.options.scope - && inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) - && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance) - ) { childrenIntersection = true; return false; } - }); - if(childrenIntersection) return false; - - if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { - if(this.options.activeClass) this.element.removeClass(this.options.activeClass); - if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass); - this._trigger('drop', event, this.ui(draggable)); - return this.element; - } - - return false; - - }, - - ui: function(c) { - return { - draggable: (c.currentItem || c.element), - helper: c.helper, - position: c.position, - offset: c.positionAbs - }; - } - -}); - -$.extend($.ui.droppable, { - version: "1.8.13" -}); - -$.ui.intersect = function(draggable, droppable, toleranceMode) { - - if (!droppable.offset) return false; - - var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width, - y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height; - var l = droppable.offset.left, r = l + droppable.proportions.width, - t = droppable.offset.top, b = t + droppable.proportions.height; - - switch (toleranceMode) { - case 'fit': - return (l <= x1 && x2 <= r - && t <= y1 && y2 <= b); - break; - case 'intersect': - return (l < x1 + (draggable.helperProportions.width / 2) // Right Half - && x2 - (draggable.helperProportions.width / 2) < r // Left Half - && t < y1 + (draggable.helperProportions.height / 2) // Bottom Half - && y2 - (draggable.helperProportions.height / 2) < b ); // Top Half - break; - case 'pointer': - var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left), - draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top), - isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width); - return isOver; - break; - case 'touch': - return ( - (y1 >= t && y1 <= b) || // Top edge touching - (y2 >= t && y2 <= b) || // Bottom edge touching - (y1 < t && y2 > b) // Surrounded vertically - ) && ( - (x1 >= l && x1 <= r) || // Left edge touching - (x2 >= l && x2 <= r) || // Right edge touching - (x1 < l && x2 > r) // Surrounded horizontally - ); - break; - default: - return false; - break; - } - -}; - -/* - This manager tracks offsets of draggables and droppables -*/ -$.ui.ddmanager = { - current: null, - droppables: { 'default': [] }, - prepareOffsets: function(t, event) { - - var m = $.ui.ddmanager.droppables[t.options.scope] || []; - var type = event ? event.type : null; // workaround for #2317 - var list = (t.currentItem || t.element).find(":data(droppable)").andSelf(); - - droppablesLoop: for (var i = 0; i < m.length; i++) { - - if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue; //No disabled and non-accepted - for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item - m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; //If the element is not visible, continue - - if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables - - m[i].offset = m[i].element.offset(); - m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight }; - - } - - }, - drop: function(draggable, event) { - - var dropped = false; - $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { - - if(!this.options) return; - if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) - dropped = dropped || this._drop.call(this, event); - - if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { - this.isout = 1; this.isover = 0; - this._deactivate.call(this, event); - } - - }); - return dropped; - - }, - drag: function(draggable, event) { - - //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse. - if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event); - - //Run through all droppables and check their positions based on specific tolerance options - $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { - - if(this.options.disabled || this.greedyChild || !this.visible) return; - var intersects = $.ui.intersect(draggable, this, this.options.tolerance); - - var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null); - if(!c) return; - - var parentInstance; - if (this.options.greedy) { - var parent = this.element.parents(':data(droppable):eq(0)'); - if (parent.length) { - parentInstance = $.data(parent[0], 'droppable'); - parentInstance.greedyChild = (c == 'isover' ? 1 : 0); - } - } - - // we just moved into a greedy child - if (parentInstance && c == 'isover') { - parentInstance['isover'] = 0; - parentInstance['isout'] = 1; - parentInstance._out.call(parentInstance, event); - } - - this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0; - this[c == "isover" ? "_over" : "_out"].call(this, event); - - // we just moved out of a greedy child - if (parentInstance && c == 'isout') { - parentInstance['isout'] = 0; - parentInstance['isover'] = 1; - parentInstance._over.call(parentInstance, event); - } - }); - - } -}; - -})(jQuery); -/* - * jQuery UI Sortable 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Sortables - * - * Depends: - * jquery.ui.core.js - * jquery.ui.mouse.js - * jquery.ui.widget.js - */ -(function( $, undefined ) { - -$.widget("ui.sortable", $.ui.mouse, { - widgetEventPrefix: "sort", - options: { - appendTo: "parent", - axis: false, - connectWith: false, - containment: false, - cursor: 'auto', - cursorAt: false, - dropOnEmpty: true, - forcePlaceholderSize: false, - forceHelperSize: false, - grid: false, - handle: false, - helper: "original", - items: '> *', - opacity: false, - placeholder: false, - revert: false, - scroll: true, - scrollSensitivity: 20, - scrollSpeed: 20, - scope: "default", - tolerance: "intersect", - zIndex: 1000 - }, - _create: function() { - - var o = this.options; - this.containerCache = {}; - this.element.addClass("ui-sortable"); - - //Get the items - this.refresh(); - - //Let's determine if the items are being displayed horizontally - this.floating = this.items.length ? o.axis === 'x' || (/left|right/).test(this.items[0].item.css('float')) || (/inline|table-cell/).test(this.items[0].item.css('display')) : false; - - //Let's determine the parent's offset - this.offset = this.element.offset(); - - //Initialize mouse events for interaction - this._mouseInit(); - - }, - - destroy: function() { - this.element - .removeClass("ui-sortable ui-sortable-disabled") - .removeData("sortable") - .unbind(".sortable"); - this._mouseDestroy(); - - for ( var i = this.items.length - 1; i >= 0; i-- ) - this.items[i].item.removeData("sortable-item"); - - return this; - }, - - _setOption: function(key, value){ - if ( key === "disabled" ) { - this.options[ key ] = value; - - this.widget() - [ value ? "addClass" : "removeClass"]( "ui-sortable-disabled" ); - } else { - // Don't call widget base _setOption for disable as it adds ui-state-disabled class - $.Widget.prototype._setOption.apply(this, arguments); - } - }, - - _mouseCapture: function(event, overrideHandle) { - - if (this.reverting) { - return false; - } - - if(this.options.disabled || this.options.type == 'static') return false; - - //We have to refresh the items data once first - this._refreshItems(event); - - //Find out if the clicked node (or one of its parents) is a actual item in this.items - var currentItem = null, self = this, nodes = $(event.target).parents().each(function() { - if($.data(this, 'sortable-item') == self) { - currentItem = $(this); - return false; - } - }); - if($.data(event.target, 'sortable-item') == self) currentItem = $(event.target); - - if(!currentItem) return false; - if(this.options.handle && !overrideHandle) { - var validHandle = false; - - $(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; }); - if(!validHandle) return false; - } - - this.currentItem = currentItem; - this._removeCurrentsFromItems(); - return true; - - }, - - _mouseStart: function(event, overrideHandle, noActivation) { - - var o = this.options, self = this; - this.currentContainer = this; - - //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture - this.refreshPositions(); - - //Create and append the visible helper - this.helper = this._createHelper(event); - - //Cache the helper size - this._cacheHelperProportions(); - - /* - * - Position generation - - * This block generates everything position related - it's the core of draggables. - */ - - //Cache the margins of the original element - this._cacheMargins(); - - //Get the next scrolling parent - this.scrollParent = this.helper.scrollParent(); - - //The element's absolute position on the page minus margins - this.offset = this.currentItem.offset(); - this.offset = { - top: this.offset.top - this.margins.top, - left: this.offset.left - this.margins.left - }; - - // Only after we got the offset, we can change the helper's position to absolute - // TODO: Still need to figure out a way to make relative sorting possible - this.helper.css("position", "absolute"); - this.cssPosition = this.helper.css("position"); - - $.extend(this.offset, { - click: { //Where the click happened, relative to the element - left: event.pageX - this.offset.left, - top: event.pageY - this.offset.top - }, - parent: this._getParentOffset(), - relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper - }); - - //Generate the original position - this.originalPosition = this._generatePosition(event); - this.originalPageX = event.pageX; - this.originalPageY = event.pageY; - - //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied - (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); - - //Cache the former DOM position - this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] }; - - //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way - if(this.helper[0] != this.currentItem[0]) { - this.currentItem.hide(); - } - - //Create the placeholder - this._createPlaceholder(); - - //Set a containment if given in the options - if(o.containment) - this._setContainment(); - - if(o.cursor) { // cursor option - if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor"); - $('body').css("cursor", o.cursor); - } - - if(o.opacity) { // opacity option - if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity"); - this.helper.css("opacity", o.opacity); - } - - if(o.zIndex) { // zIndex option - if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex"); - this.helper.css("zIndex", o.zIndex); - } - - //Prepare scrolling - if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') - this.overflowOffset = this.scrollParent.offset(); - - //Call callbacks - this._trigger("start", event, this._uiHash()); - - //Recache the helper size - if(!this._preserveHelperProportions) - this._cacheHelperProportions(); - - - //Post 'activate' events to possible containers - if(!noActivation) { - for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, self._uiHash(this)); } - } - - //Prepare possible droppables - if($.ui.ddmanager) - $.ui.ddmanager.current = this; - - if ($.ui.ddmanager && !o.dropBehaviour) - $.ui.ddmanager.prepareOffsets(this, event); - - this.dragging = true; - - this.helper.addClass("ui-sortable-helper"); - this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position - return true; - - }, - - _mouseDrag: function(event) { - - //Compute the helpers position - this.position = this._generatePosition(event); - this.positionAbs = this._convertPositionTo("absolute"); - - if (!this.lastPositionAbs) { - this.lastPositionAbs = this.positionAbs; - } - - //Do scrolling - if(this.options.scroll) { - var o = this.options, scrolled = false; - if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') { - - if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) - this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; - else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) - this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; - - if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) - this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; - else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) - this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; - - } else { - - if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) - scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); - else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) - scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); - - if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) - scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); - else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) - scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); - - } - - if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) - $.ui.ddmanager.prepareOffsets(this, event); - } - - //Regenerate the absolute position used for position checks - this.positionAbs = this._convertPositionTo("absolute"); - - //Set the helper position - if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px'; - if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px'; - - //Rearrange - for (var i = this.items.length - 1; i >= 0; i--) { - - //Cache variables and intersection, continue if no intersection - var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item); - if (!intersection) continue; - - if(itemElement != this.currentItem[0] //cannot intersect with itself - && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before - && !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked - && (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true) - //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container - ) { - - this.direction = intersection == 1 ? "down" : "up"; - - if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) { - this._rearrange(event, item); - } else { - break; - } - - this._trigger("change", event, this._uiHash()); - break; - } - } - - //Post events to containers - this._contactContainers(event); - - //Interconnect with droppables - if($.ui.ddmanager) $.ui.ddmanager.drag(this, event); - - //Call callbacks - this._trigger('sort', event, this._uiHash()); - - this.lastPositionAbs = this.positionAbs; - return false; - - }, - - _mouseStop: function(event, noPropagation) { - - if(!event) return; - - //If we are using droppables, inform the manager about the drop - if ($.ui.ddmanager && !this.options.dropBehaviour) - $.ui.ddmanager.drop(this, event); - - if(this.options.revert) { - var self = this; - var cur = self.placeholder.offset(); - - self.reverting = true; - - $(this.helper).animate({ - left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft), - top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop) - }, parseInt(this.options.revert, 10) || 500, function() { - self._clear(event); - }); - } else { - this._clear(event, noPropagation); - } - - return false; - - }, - - cancel: function() { - - var self = this; - - if(this.dragging) { - - this._mouseUp({ target: null }); - - if(this.options.helper == "original") - this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); - else - this.currentItem.show(); - - //Post deactivating events to containers - for (var i = this.containers.length - 1; i >= 0; i--){ - this.containers[i]._trigger("deactivate", null, self._uiHash(this)); - if(this.containers[i].containerCache.over) { - this.containers[i]._trigger("out", null, self._uiHash(this)); - this.containers[i].containerCache.over = 0; - } - } - - } - - if (this.placeholder) { - //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! - if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]); - if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove(); - - $.extend(this, { - helper: null, - dragging: false, - reverting: false, - _noFinalSort: null - }); - - if(this.domPosition.prev) { - $(this.domPosition.prev).after(this.currentItem); - } else { - $(this.domPosition.parent).prepend(this.currentItem); - } - } - - return this; - - }, - - serialize: function(o) { - - var items = this._getItemsAsjQuery(o && o.connected); - var str = []; o = o || {}; - - $(items).each(function() { - var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/)); - if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2])); - }); - - if(!str.length && o.key) { - str.push(o.key + '='); - } - - return str.join('&'); - - }, - - toArray: function(o) { - - var items = this._getItemsAsjQuery(o && o.connected); - var ret = []; o = o || {}; - - items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); }); - return ret; - - }, - - /* Be careful with the following core functions */ - _intersectsWith: function(item) { - - var x1 = this.positionAbs.left, - x2 = x1 + this.helperProportions.width, - y1 = this.positionAbs.top, - y2 = y1 + this.helperProportions.height; - - var l = item.left, - r = l + item.width, - t = item.top, - b = t + item.height; - - var dyClick = this.offset.click.top, - dxClick = this.offset.click.left; - - var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r; - - if( this.options.tolerance == "pointer" - || this.options.forcePointerForContainers - || (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height']) - ) { - return isOverElement; - } else { - - return (l < x1 + (this.helperProportions.width / 2) // Right Half - && x2 - (this.helperProportions.width / 2) < r // Left Half - && t < y1 + (this.helperProportions.height / 2) // Bottom Half - && y2 - (this.helperProportions.height / 2) < b ); // Top Half - - } - }, - - _intersectsWithPointer: function(item) { - - var isOverElementHeight = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), - isOverElementWidth = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), - isOverElement = isOverElementHeight && isOverElementWidth, - verticalDirection = this._getDragVerticalDirection(), - horizontalDirection = this._getDragHorizontalDirection(); - - if (!isOverElement) - return false; - - return this.floating ? - ( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 ) - : ( verticalDirection && (verticalDirection == "down" ? 2 : 1) ); - - }, - - _intersectsWithSides: function(item) { - - var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height), - isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width), - verticalDirection = this._getDragVerticalDirection(), - horizontalDirection = this._getDragHorizontalDirection(); - - if (this.floating && horizontalDirection) { - return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf)); - } else { - return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf)); - } - - }, - - _getDragVerticalDirection: function() { - var delta = this.positionAbs.top - this.lastPositionAbs.top; - return delta != 0 && (delta > 0 ? "down" : "up"); - }, - - _getDragHorizontalDirection: function() { - var delta = this.positionAbs.left - this.lastPositionAbs.left; - return delta != 0 && (delta > 0 ? "right" : "left"); - }, - - refresh: function(event) { - this._refreshItems(event); - this.refreshPositions(); - return this; - }, - - _connectWith: function() { - var options = this.options; - return options.connectWith.constructor == String - ? [options.connectWith] - : options.connectWith; - }, - - _getItemsAsjQuery: function(connected) { - - var self = this; - var items = []; - var queries = []; - var connectWith = this._connectWith(); - - if(connectWith && connected) { - for (var i = connectWith.length - 1; i >= 0; i--){ - var cur = $(connectWith[i]); - for (var j = cur.length - 1; j >= 0; j--){ - var inst = $.data(cur[j], 'sortable'); - if(inst && inst != this && !inst.options.disabled) { - queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), inst]); - } - }; - }; - } - - queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), this]); - - for (var i = queries.length - 1; i >= 0; i--){ - queries[i][0].each(function() { - items.push(this); - }); - }; - - return $(items); - - }, - - _removeCurrentsFromItems: function() { - - var list = this.currentItem.find(":data(sortable-item)"); - - for (var i=0; i < this.items.length; i++) { - - for (var j=0; j < list.length; j++) { - if(list[j] == this.items[i].item[0]) - this.items.splice(i,1); - }; - - }; - - }, - - _refreshItems: function(event) { - - this.items = []; - this.containers = [this]; - var items = this.items; - var self = this; - var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]]; - var connectWith = this._connectWith(); - - if(connectWith) { - for (var i = connectWith.length - 1; i >= 0; i--){ - var cur = $(connectWith[i]); - for (var j = cur.length - 1; j >= 0; j--){ - var inst = $.data(cur[j], 'sortable'); - if(inst && inst != this && !inst.options.disabled) { - queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]); - this.containers.push(inst); - } - }; - }; - } - - for (var i = queries.length - 1; i >= 0; i--) { - var targetData = queries[i][1]; - var _queries = queries[i][0]; - - for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) { - var item = $(_queries[j]); - - item.data('sortable-item', targetData); // Data for target checking (mouse manager) - - items.push({ - item: item, - instance: targetData, - width: 0, height: 0, - left: 0, top: 0 - }); - }; - }; - - }, - - refreshPositions: function(fast) { - - //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change - if(this.offsetParent && this.helper) { - this.offset.parent = this._getParentOffset(); - } - - for (var i = this.items.length - 1; i >= 0; i--){ - var item = this.items[i]; - - //We ignore calculating positions of all connected containers when we're not over them - if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0]) - continue; - - var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item; - - if (!fast) { - item.width = t.outerWidth(); - item.height = t.outerHeight(); - } - - var p = t.offset(); - item.left = p.left; - item.top = p.top; - }; - - if(this.options.custom && this.options.custom.refreshContainers) { - this.options.custom.refreshContainers.call(this); - } else { - for (var i = this.containers.length - 1; i >= 0; i--){ - var p = this.containers[i].element.offset(); - this.containers[i].containerCache.left = p.left; - this.containers[i].containerCache.top = p.top; - this.containers[i].containerCache.width = this.containers[i].element.outerWidth(); - this.containers[i].containerCache.height = this.containers[i].element.outerHeight(); - }; - } - - return this; - }, - - _createPlaceholder: function(that) { - - var self = that || this, o = self.options; - - if(!o.placeholder || o.placeholder.constructor == String) { - var className = o.placeholder; - o.placeholder = { - element: function() { - - var el = $(document.createElement(self.currentItem[0].nodeName)) - .addClass(className || self.currentItem[0].className+" ui-sortable-placeholder") - .removeClass("ui-sortable-helper")[0]; - - if(!className) - el.style.visibility = "hidden"; - - return el; - }, - update: function(container, p) { - - // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that - // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified - if(className && !o.forcePlaceholderSize) return; - - //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item - if(!p.height()) { p.height(self.currentItem.innerHeight() - parseInt(self.currentItem.css('paddingTop')||0, 10) - parseInt(self.currentItem.css('paddingBottom')||0, 10)); }; - if(!p.width()) { p.width(self.currentItem.innerWidth() - parseInt(self.currentItem.css('paddingLeft')||0, 10) - parseInt(self.currentItem.css('paddingRight')||0, 10)); }; - } - }; - } - - //Create the placeholder - self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem)); - - //Append it after the actual current item - self.currentItem.after(self.placeholder); - - //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) - o.placeholder.update(self, self.placeholder); - - }, - - _contactContainers: function(event) { - - // get innermost container that intersects with item - var innermostContainer = null, innermostIndex = null; - - - for (var i = this.containers.length - 1; i >= 0; i--){ - - // never consider a container that's located within the item itself - if($.ui.contains(this.currentItem[0], this.containers[i].element[0])) - continue; - - if(this._intersectsWith(this.containers[i].containerCache)) { - - // if we've already found a container and it's more "inner" than this, then continue - if(innermostContainer && $.ui.contains(this.containers[i].element[0], innermostContainer.element[0])) - continue; - - innermostContainer = this.containers[i]; - innermostIndex = i; - - } else { - // container doesn't intersect. trigger "out" event if necessary - if(this.containers[i].containerCache.over) { - this.containers[i]._trigger("out", event, this._uiHash(this)); - this.containers[i].containerCache.over = 0; - } - } - - } - - // if no intersecting containers found, return - if(!innermostContainer) return; - - // move the item into the container if it's not there already - if(this.containers.length === 1) { - this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); - this.containers[innermostIndex].containerCache.over = 1; - } else if(this.currentContainer != this.containers[innermostIndex]) { - - //When entering a new container, we will find the item with the least distance and append our item near it - var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[innermostIndex].floating ? 'left' : 'top']; - for (var j = this.items.length - 1; j >= 0; j--) { - if(!$.ui.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue; - var cur = this.items[j][this.containers[innermostIndex].floating ? 'left' : 'top']; - if(Math.abs(cur - base) < dist) { - dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; - } - } - - if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled - return; - - this.currentContainer = this.containers[innermostIndex]; - itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); - this._trigger("change", event, this._uiHash()); - this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); - - //Update the placeholder - this.options.placeholder.update(this.currentContainer, this.placeholder); - - this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); - this.containers[innermostIndex].containerCache.over = 1; - } - - - }, - - _createHelper: function(event) { - - var o = this.options; - var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem); - - if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already - $(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]); - - if(helper[0] == this.currentItem[0]) - this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") }; - - if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width()); - if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height()); - - return helper; - - }, - - _adjustOffsetFromHelper: function(obj) { - if (typeof obj == 'string') { - obj = obj.split(' '); - } - if ($.isArray(obj)) { - obj = {left: +obj[0], top: +obj[1] || 0}; - } - if ('left' in obj) { - this.offset.click.left = obj.left + this.margins.left; - } - if ('right' in obj) { - this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; - } - if ('top' in obj) { - this.offset.click.top = obj.top + this.margins.top; - } - if ('bottom' in obj) { - this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; - } - }, - - _getParentOffset: function() { - - - //Get the offsetParent and cache its position - this.offsetParent = this.helper.offsetParent(); - var po = this.offsetParent.offset(); - - // This is a special case where we need to modify a offset calculated on start, since the following happened: - // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent - // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that - // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag - if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) { - po.left += this.scrollParent.scrollLeft(); - po.top += this.scrollParent.scrollTop(); - } - - if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information - || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix - po = { top: 0, left: 0 }; - - return { - top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), - left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) - }; - - }, - - _getRelativeOffset: function() { - - if(this.cssPosition == "relative") { - var p = this.currentItem.position(); - return { - top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), - left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() - }; - } else { - return { top: 0, left: 0 }; - } - - }, - - _cacheMargins: function() { - this.margins = { - left: (parseInt(this.currentItem.css("marginLeft"),10) || 0), - top: (parseInt(this.currentItem.css("marginTop"),10) || 0) - }; - }, - - _cacheHelperProportions: function() { - this.helperProportions = { - width: this.helper.outerWidth(), - height: this.helper.outerHeight() - }; - }, - - _setContainment: function() { - - var o = this.options; - if(o.containment == 'parent') o.containment = this.helper[0].parentNode; - if(o.containment == 'document' || o.containment == 'window') this.containment = [ - 0 - this.offset.relative.left - this.offset.parent.left, - 0 - this.offset.relative.top - this.offset.parent.top, - $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left, - ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top - ]; - - if(!(/^(document|window|parent)$/).test(o.containment)) { - var ce = $(o.containment)[0]; - var co = $(o.containment).offset(); - var over = ($(ce).css("overflow") != 'hidden'); - - this.containment = [ - co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left, - co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top, - co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left, - co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - ]; - } - - }, - - _convertPositionTo: function(d, pos) { - - if(!pos) pos = this.position; - var mod = d == "absolute" ? 1 : -1; - var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); - - return { - top: ( - pos.top // The absolute mouse position - + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent - + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border) - - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) - ), - left: ( - pos.left // The absolute mouse position - + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent - + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border) - - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) - ) - }; - - }, - - _generatePosition: function(event) { - - var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); - - // This is another very weird special case that only happens for relative elements: - // 1. If the css position is relative - // 2. and the scroll parent is the document or similar to the offset parent - // we have to refresh the relative offset during the scroll so there are no jumps - if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) { - this.offset.relative = this._getRelativeOffset(); - } - - var pageX = event.pageX; - var pageY = event.pageY; - - /* - * - Position constraining - - * Constrain the position to a mix of grid, containment. - */ - - if(this.originalPosition) { //If we are not dragging yet, we won't check for options - - if(this.containment) { - if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left; - if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top; - if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left; - if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top; - } - - if(o.grid) { - var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1]; - pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; - - var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0]; - pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; - } - - } - - return { - top: ( - pageY // The absolute mouse position - - this.offset.click.top // Click offset (relative to the element) - - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent - - this.offset.parent.top // The offsetParent's offset without borders (offset + border) - + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) - ), - left: ( - pageX // The absolute mouse position - - this.offset.click.left // Click offset (relative to the element) - - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent - - this.offset.parent.left // The offsetParent's offset without borders (offset + border) - + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) - ) - }; - - }, - - _rearrange: function(event, i, a, hardRefresh) { - - a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling)); - - //Various things done here to improve the performance: - // 1. we create a setTimeout, that calls refreshPositions - // 2. on the instance, we have a counter variable, that get's higher after every append - // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same - // 4. this lets only the last addition to the timeout stack through - this.counter = this.counter ? ++this.counter : 1; - var self = this, counter = this.counter; - - window.setTimeout(function() { - if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove - },0); - - }, - - _clear: function(event, noPropagation) { - - this.reverting = false; - // We delay all events that have to be triggered to after the point where the placeholder has been removed and - // everything else normalized again - var delayedTriggers = [], self = this; - - // We first have to update the dom position of the actual currentItem - // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088) - if(!this._noFinalSort && this.currentItem[0].parentNode) this.placeholder.before(this.currentItem); - this._noFinalSort = null; - - if(this.helper[0] == this.currentItem[0]) { - for(var i in this._storedCSS) { - if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = ''; - } - this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); - } else { - this.currentItem.show(); - } - - if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); }); - if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed - if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element - if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); - for (var i = this.containers.length - 1; i >= 0; i--){ - if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) { - delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.containers[i])); - delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.containers[i])); - } - }; - }; - - //Post events to containers - for (var i = this.containers.length - 1; i >= 0; i--){ - if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i])); - if(this.containers[i].containerCache.over) { - delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i])); - this.containers[i].containerCache.over = 0; - } - } - - //Do what was originally in plugins - if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor - if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity - if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index - - this.dragging = false; - if(this.cancelHelperRemoval) { - if(!noPropagation) { - this._trigger("beforeStop", event, this._uiHash()); - for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events - this._trigger("stop", event, this._uiHash()); - } - return false; - } - - if(!noPropagation) this._trigger("beforeStop", event, this._uiHash()); - - //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! - this.placeholder[0].parentNode.removeChild(this.placeholder[0]); - - if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null; - - if(!noPropagation) { - for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events - this._trigger("stop", event, this._uiHash()); - } - - this.fromOutside = false; - return true; - - }, - - _trigger: function() { - if ($.Widget.prototype._trigger.apply(this, arguments) === false) { - this.cancel(); - } - }, - - _uiHash: function(inst) { - var self = inst || this; - return { - helper: self.helper, - placeholder: self.placeholder || $([]), - position: self.position, - originalPosition: self.originalPosition, - offset: self.positionAbs, - item: self.currentItem, - sender: inst ? inst.element : null - }; - } - -}); - -$.extend($.ui.sortable, { - version: "1.8.13" -}); - -})(jQuery); -/* - * jQuery UI Effects 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Effects/ - */ -;jQuery.effects || (function($, undefined) { - -$.effects = {}; - - - -/******************************************************************************/ -/****************************** COLOR ANIMATIONS ******************************/ -/******************************************************************************/ - -// override the animation for color styles -$.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor', - 'borderRightColor', 'borderTopColor', 'borderColor', 'color', 'outlineColor'], -function(i, attr) { - $.fx.step[attr] = function(fx) { - if (!fx.colorInit) { - fx.start = getColor(fx.elem, attr); - fx.end = getRGB(fx.end); - fx.colorInit = true; - } - - fx.elem.style[attr] = 'rgb(' + - Math.max(Math.min(parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0], 10), 255), 0) + ',' + - Math.max(Math.min(parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1], 10), 255), 0) + ',' + - Math.max(Math.min(parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2], 10), 255), 0) + ')'; - }; -}); - -// Color Conversion functions from highlightFade -// By Blair Mitchelmore -// http://jquery.offput.ca/highlightFade/ - -// Parse strings looking for color tuples [255,255,255] -function getRGB(color) { - var result; - - // Check if we're already dealing with an array of colors - if ( color && color.constructor == Array && color.length == 3 ) - return color; - - // Look for rgb(num,num,num) - if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) - return [parseInt(result[1],10), parseInt(result[2],10), parseInt(result[3],10)]; - - // Look for rgb(num%,num%,num%) - if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)) - return [parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55]; - - // Look for #a0b1c2 - if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)) - return [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)]; - - // Look for #fff - if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)) - return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)]; - - // Look for rgba(0, 0, 0, 0) == transparent in Safari 3 - if (result = /rgba\(0, 0, 0, 0\)/.exec(color)) - return colors['transparent']; - - // Otherwise, we're most likely dealing with a named color - return colors[$.trim(color).toLowerCase()]; -} - -function getColor(elem, attr) { - var color; - - do { - color = $.curCSS(elem, attr); - - // Keep going until we find an element that has color, or we hit the body - if ( color != '' && color != 'transparent' || $.nodeName(elem, "body") ) - break; - - attr = "backgroundColor"; - } while ( elem = elem.parentNode ); - - return getRGB(color); -}; - -// Some named colors to work with -// From Interface by Stefan Petre -// http://interface.eyecon.ro/ - -var colors = { - aqua:[0,255,255], - azure:[240,255,255], - beige:[245,245,220], - black:[0,0,0], - blue:[0,0,255], - brown:[165,42,42], - cyan:[0,255,255], - darkblue:[0,0,139], - darkcyan:[0,139,139], - darkgrey:[169,169,169], - darkgreen:[0,100,0], - darkkhaki:[189,183,107], - darkmagenta:[139,0,139], - darkolivegreen:[85,107,47], - darkorange:[255,140,0], - darkorchid:[153,50,204], - darkred:[139,0,0], - darksalmon:[233,150,122], - darkviolet:[148,0,211], - fuchsia:[255,0,255], - gold:[255,215,0], - green:[0,128,0], - indigo:[75,0,130], - khaki:[240,230,140], - lightblue:[173,216,230], - lightcyan:[224,255,255], - lightgreen:[144,238,144], - lightgrey:[211,211,211], - lightpink:[255,182,193], - lightyellow:[255,255,224], - lime:[0,255,0], - magenta:[255,0,255], - maroon:[128,0,0], - navy:[0,0,128], - olive:[128,128,0], - orange:[255,165,0], - pink:[255,192,203], - purple:[128,0,128], - violet:[128,0,128], - red:[255,0,0], - silver:[192,192,192], - white:[255,255,255], - yellow:[255,255,0], - transparent: [255,255,255] -}; - - - -/******************************************************************************/ -/****************************** CLASS ANIMATIONS ******************************/ -/******************************************************************************/ - -var classAnimationActions = ['add', 'remove', 'toggle'], - shorthandStyles = { - border: 1, - borderBottom: 1, - borderColor: 1, - borderLeft: 1, - borderRight: 1, - borderTop: 1, - borderWidth: 1, - margin: 1, - padding: 1 - }; - -function getElementStyles() { - var style = document.defaultView - ? document.defaultView.getComputedStyle(this, null) - : this.currentStyle, - newStyle = {}, - key, - camelCase; - - // webkit enumerates style porperties - if (style && style.length && style[0] && style[style[0]]) { - var len = style.length; - while (len--) { - key = style[len]; - if (typeof style[key] == 'string') { - camelCase = key.replace(/\-(\w)/g, function(all, letter){ - return letter.toUpperCase(); - }); - newStyle[camelCase] = style[key]; - } - } - } else { - for (key in style) { - if (typeof style[key] === 'string') { - newStyle[key] = style[key]; - } - } - } - - return newStyle; -} - -function filterStyles(styles) { - var name, value; - for (name in styles) { - value = styles[name]; - if ( - // ignore null and undefined values - value == null || - // ignore functions (when does this occur?) - $.isFunction(value) || - // shorthand styles that need to be expanded - name in shorthandStyles || - // ignore scrollbars (break in IE) - (/scrollbar/).test(name) || - - // only colors or values that can be converted to numbers - (!(/color/i).test(name) && isNaN(parseFloat(value))) - ) { - delete styles[name]; - } - } - - return styles; -} - -function styleDifference(oldStyle, newStyle) { - var diff = { _: 0 }, // http://dev.jquery.com/ticket/5459 - name; - - for (name in newStyle) { - if (oldStyle[name] != newStyle[name]) { - diff[name] = newStyle[name]; - } - } - - return diff; -} - -$.effects.animateClass = function(value, duration, easing, callback) { - if ($.isFunction(easing)) { - callback = easing; - easing = null; - } - - return this.queue(function() { - var that = $(this), - originalStyleAttr = that.attr('style') || ' ', - originalStyle = filterStyles(getElementStyles.call(this)), - newStyle, - className = that.attr('class'); - - $.each(classAnimationActions, function(i, action) { - if (value[action]) { - that[action + 'Class'](value[action]); - } - }); - newStyle = filterStyles(getElementStyles.call(this)); - that.attr('class', className); - - that.animate(styleDifference(originalStyle, newStyle), { - queue: false, - duration: duration, - easding: easing, - complete: function() { - $.each(classAnimationActions, function(i, action) { - if (value[action]) { that[action + 'Class'](value[action]); } - }); - // work around bug in IE by clearing the cssText before setting it - if (typeof that.attr('style') == 'object') { - that.attr('style').cssText = ''; - that.attr('style').cssText = originalStyleAttr; - } else { - that.attr('style', originalStyleAttr); - } - if (callback) { callback.apply(this, arguments); } - $.dequeue( this ); - } - }); - }); -}; - -$.fn.extend({ - _addClass: $.fn.addClass, - addClass: function(classNames, speed, easing, callback) { - return speed ? $.effects.animateClass.apply(this, [{ add: classNames },speed,easing,callback]) : this._addClass(classNames); - }, - - _removeClass: $.fn.removeClass, - removeClass: function(classNames,speed,easing,callback) { - return speed ? $.effects.animateClass.apply(this, [{ remove: classNames },speed,easing,callback]) : this._removeClass(classNames); - }, - - _toggleClass: $.fn.toggleClass, - toggleClass: function(classNames, force, speed, easing, callback) { - if ( typeof force == "boolean" || force === undefined ) { - if ( !speed ) { - // without speed parameter; - return this._toggleClass(classNames, force); - } else { - return $.effects.animateClass.apply(this, [(force?{add:classNames}:{remove:classNames}),speed,easing,callback]); - } - } else { - // without switch parameter; - return $.effects.animateClass.apply(this, [{ toggle: classNames },force,speed,easing]); - } - }, - - switchClass: function(remove,add,speed,easing,callback) { - return $.effects.animateClass.apply(this, [{ add: add, remove: remove },speed,easing,callback]); - } -}); - - - -/******************************************************************************/ -/*********************************** EFFECTS **********************************/ -/******************************************************************************/ - -$.extend($.effects, { - version: "1.8.13", - - // Saves a set of properties in a data storage - save: function(element, set) { - for(var i=0; i < set.length; i++) { - if(set[i] !== null) element.data("ec.storage."+set[i], element[0].style[set[i]]); - } - }, - - // Restores a set of previously saved properties from a data storage - restore: function(element, set) { - for(var i=0; i < set.length; i++) { - if(set[i] !== null) element.css(set[i], element.data("ec.storage."+set[i])); - } - }, - - setMode: function(el, mode) { - if (mode == 'toggle') mode = el.is(':hidden') ? 'show' : 'hide'; // Set for toggle - return mode; - }, - - getBaseline: function(origin, original) { // Translates a [top,left] array into a baseline value - // this should be a little more flexible in the future to handle a string & hash - var y, x; - switch (origin[0]) { - case 'top': y = 0; break; - case 'middle': y = 0.5; break; - case 'bottom': y = 1; break; - default: y = origin[0] / original.height; - }; - switch (origin[1]) { - case 'left': x = 0; break; - case 'center': x = 0.5; break; - case 'right': x = 1; break; - default: x = origin[1] / original.width; - }; - return {x: x, y: y}; - }, - - // Wraps the element around a wrapper that copies position properties - createWrapper: function(element) { - - // if the element is already wrapped, return it - if (element.parent().is('.ui-effects-wrapper')) { - return element.parent(); - } - - // wrap the element - var props = { - width: element.outerWidth(true), - height: element.outerHeight(true), - 'float': element.css('float') - }, - wrapper = $('<div></div>') - .addClass('ui-effects-wrapper') - .css({ - fontSize: '100%', - background: 'transparent', - border: 'none', - margin: 0, - padding: 0 - }); - - element.wrap(wrapper); - wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element - - // transfer positioning properties to the wrapper - if (element.css('position') == 'static') { - wrapper.css({ position: 'relative' }); - element.css({ position: 'relative' }); - } else { - $.extend(props, { - position: element.css('position'), - zIndex: element.css('z-index') - }); - $.each(['top', 'left', 'bottom', 'right'], function(i, pos) { - props[pos] = element.css(pos); - if (isNaN(parseInt(props[pos], 10))) { - props[pos] = 'auto'; - } - }); - element.css({position: 'relative', top: 0, left: 0, right: 'auto', bottom: 'auto' }); - } - - return wrapper.css(props).show(); - }, - - removeWrapper: function(element) { - if (element.parent().is('.ui-effects-wrapper')) - return element.parent().replaceWith(element); - return element; - }, - - setTransition: function(element, list, factor, value) { - value = value || {}; - $.each(list, function(i, x){ - unit = element.cssUnit(x); - if (unit[0] > 0) value[x] = unit[0] * factor + unit[1]; - }); - return value; - } -}); - - -function _normalizeArguments(effect, options, speed, callback) { - // shift params for method overloading - if (typeof effect == 'object') { - callback = options; - speed = null; - options = effect; - effect = options.effect; - } - if ($.isFunction(options)) { - callback = options; - speed = null; - options = {}; - } - if (typeof options == 'number' || $.fx.speeds[options]) { - callback = speed; - speed = options; - options = {}; - } - if ($.isFunction(speed)) { - callback = speed; - speed = null; - } - - options = options || {}; - - speed = speed || options.duration; - speed = $.fx.off ? 0 : typeof speed == 'number' - ? speed : speed in $.fx.speeds ? $.fx.speeds[speed] : $.fx.speeds._default; - - callback = callback || options.complete; - - return [effect, options, speed, callback]; -} - -function standardSpeed( speed ) { - // valid standard speeds - if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) { - return true; - } - - // invalid strings - treat as "normal" speed - if ( typeof speed === "string" && !$.effects[ speed ] ) { - return true; - } - - return false; -} - -$.fn.extend({ - effect: function(effect, options, speed, callback) { - var args = _normalizeArguments.apply(this, arguments), - // TODO: make effects take actual parameters instead of a hash - args2 = { - options: args[1], - duration: args[2], - callback: args[3] - }, - mode = args2.options.mode, - effectMethod = $.effects[effect]; - - if ( $.fx.off || !effectMethod ) { - // delegate to the original method (e.g., .show()) if possible - if ( mode ) { - return this[ mode ]( args2.duration, args2.callback ); - } else { - return this.each(function() { - if ( args2.callback ) { - args2.callback.call( this ); - } - }); - } - } - - return effectMethod.call(this, args2); - }, - - _show: $.fn.show, - show: function(speed) { - if ( standardSpeed( speed ) ) { - return this._show.apply(this, arguments); - } else { - var args = _normalizeArguments.apply(this, arguments); - args[1].mode = 'show'; - return this.effect.apply(this, args); - } - }, - - _hide: $.fn.hide, - hide: function(speed) { - if ( standardSpeed( speed ) ) { - return this._hide.apply(this, arguments); - } else { - var args = _normalizeArguments.apply(this, arguments); - args[1].mode = 'hide'; - return this.effect.apply(this, args); - } - }, - - // jQuery core overloads toggle and creates _toggle - __toggle: $.fn.toggle, - toggle: function(speed) { - if ( standardSpeed( speed ) || typeof speed === "boolean" || $.isFunction( speed ) ) { - return this.__toggle.apply(this, arguments); - } else { - var args = _normalizeArguments.apply(this, arguments); - args[1].mode = 'toggle'; - return this.effect.apply(this, args); - } - }, - - // helper functions - cssUnit: function(key) { - var style = this.css(key), val = []; - $.each( ['em','px','%','pt'], function(i, unit){ - if(style.indexOf(unit) > 0) - val = [parseFloat(style), unit]; - }); - return val; - } -}); - - - -/******************************************************************************/ -/*********************************** EASING ***********************************/ -/******************************************************************************/ - -/* - * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ - * - * Uses the built in easing capabilities added In jQuery 1.1 - * to offer multiple easing options - * - * TERMS OF USE - jQuery Easing - * - * Open source under the BSD License. - * - * Copyright 2008 George McGinley Smith - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * Neither the name of the author nor the names of contributors may be used to endorse - * or promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * -*/ - -// t: current time, b: begInnIng value, c: change In value, d: duration -$.easing.jswing = $.easing.swing; - -$.extend($.easing, -{ - def: 'easeOutQuad', - swing: function (x, t, b, c, d) { - //alert($.easing.default); - return $.easing[$.easing.def](x, t, b, c, d); - }, - easeInQuad: function (x, t, b, c, d) { - return c*(t/=d)*t + b; - }, - easeOutQuad: function (x, t, b, c, d) { - return -c *(t/=d)*(t-2) + b; - }, - easeInOutQuad: function (x, t, b, c, d) { - if ((t/=d/2) < 1) return c/2*t*t + b; - return -c/2 * ((--t)*(t-2) - 1) + b; - }, - easeInCubic: function (x, t, b, c, d) { - return c*(t/=d)*t*t + b; - }, - easeOutCubic: function (x, t, b, c, d) { - return c*((t=t/d-1)*t*t + 1) + b; - }, - easeInOutCubic: function (x, t, b, c, d) { - if ((t/=d/2) < 1) return c/2*t*t*t + b; - return c/2*((t-=2)*t*t + 2) + b; - }, - easeInQuart: function (x, t, b, c, d) { - return c*(t/=d)*t*t*t + b; - }, - easeOutQuart: function (x, t, b, c, d) { - return -c * ((t=t/d-1)*t*t*t - 1) + b; - }, - easeInOutQuart: function (x, t, b, c, d) { - if ((t/=d/2) < 1) return c/2*t*t*t*t + b; - return -c/2 * ((t-=2)*t*t*t - 2) + b; - }, - easeInQuint: function (x, t, b, c, d) { - return c*(t/=d)*t*t*t*t + b; - }, - easeOutQuint: function (x, t, b, c, d) { - return c*((t=t/d-1)*t*t*t*t + 1) + b; - }, - easeInOutQuint: function (x, t, b, c, d) { - if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b; - return c/2*((t-=2)*t*t*t*t + 2) + b; - }, - easeInSine: function (x, t, b, c, d) { - return -c * Math.cos(t/d * (Math.PI/2)) + c + b; - }, - easeOutSine: function (x, t, b, c, d) { - return c * Math.sin(t/d * (Math.PI/2)) + b; - }, - easeInOutSine: function (x, t, b, c, d) { - return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b; - }, - easeInExpo: function (x, t, b, c, d) { - return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; - }, - easeOutExpo: function (x, t, b, c, d) { - return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; - }, - easeInOutExpo: function (x, t, b, c, d) { - if (t==0) return b; - if (t==d) return b+c; - if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; - return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; - }, - easeInCirc: function (x, t, b, c, d) { - return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b; - }, - easeOutCirc: function (x, t, b, c, d) { - return c * Math.sqrt(1 - (t=t/d-1)*t) + b; - }, - easeInOutCirc: function (x, t, b, c, d) { - if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b; - return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b; - }, - easeInElastic: function (x, t, b, c, d) { - var s=1.70158;var p=0;var a=c; - if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; - if (a < Math.abs(c)) { a=c; var s=p/4; } - else var s = p/(2*Math.PI) * Math.asin (c/a); - return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; - }, - easeOutElastic: function (x, t, b, c, d) { - var s=1.70158;var p=0;var a=c; - if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; - if (a < Math.abs(c)) { a=c; var s=p/4; } - else var s = p/(2*Math.PI) * Math.asin (c/a); - return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b; - }, - easeInOutElastic: function (x, t, b, c, d) { - var s=1.70158;var p=0;var a=c; - if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5); - if (a < Math.abs(c)) { a=c; var s=p/4; } - else var s = p/(2*Math.PI) * Math.asin (c/a); - if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; - return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b; - }, - easeInBack: function (x, t, b, c, d, s) { - if (s == undefined) s = 1.70158; - return c*(t/=d)*t*((s+1)*t - s) + b; - }, - easeOutBack: function (x, t, b, c, d, s) { - if (s == undefined) s = 1.70158; - return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; - }, - easeInOutBack: function (x, t, b, c, d, s) { - if (s == undefined) s = 1.70158; - if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; - return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; - }, - easeInBounce: function (x, t, b, c, d) { - return c - $.easing.easeOutBounce (x, d-t, 0, c, d) + b; - }, - easeOutBounce: function (x, t, b, c, d) { - if ((t/=d) < (1/2.75)) { - return c*(7.5625*t*t) + b; - } else if (t < (2/2.75)) { - return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; - } else if (t < (2.5/2.75)) { - return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; - } else { - return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; - } - }, - easeInOutBounce: function (x, t, b, c, d) { - if (t < d/2) return $.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b; - return $.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b; - } -}); - -/* - * - * TERMS OF USE - EASING EQUATIONS - * - * Open source under the BSD License. - * - * Copyright 2001 Robert Penner - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * Neither the name of the author nor the names of contributors may be used to endorse - * or promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE - * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -})(jQuery); diff --git a/feincms/static/feincms/jquery-ui-1.8.13.custom.min.js b/feincms/static/feincms/jquery-ui-1.8.13.custom.min.js deleted file mode 100644 index 88e0b1c35..000000000 --- a/feincms/static/feincms/jquery-ui-1.8.13.custom.min.js +++ /dev/null @@ -1,216 +0,0 @@ -/*! - * jQuery UI 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI - */ -(function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.13", -keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus(); -b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this, -"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection", -function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth, -outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a,"tabindex"),d=isNaN(b); -return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e= -0;e<b.length;e++)a.options[b[e][0]]&&b[e][1].apply(a.element,d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&c.ui.isOverAxis(b,e,i)}})}})(jQuery); -;/*! - * jQuery UI Widget 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Widget - */ -(function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)b(d).triggerHandler("remove");k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){b(this).triggerHandler("remove")});return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h, -a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.charAt(0)==="_")return h; -e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options=b.extend(true,{},this.options, -this._getCreateOptions(),a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")}, -widget:function(){return this.element},option:function(a,c){var d=a;if(arguments.length===0)return b.extend({},this.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(d,e){c._setOption(d,e)});return this},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this}, -enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery); -;/*! - * jQuery UI Mouse 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Mouse - * - * Depends: - * jquery.ui.widget.js - */ -(function(b){var d=false;b(document).mousedown(function(){d=false});b.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(c){return a._mouseDown(c)}).bind("click."+this.widgetName,function(c){if(true===b.data(c.target,a.widgetName+".preventClickEvent")){b.removeData(c.target,a.widgetName+".preventClickEvent");c.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+ -this.widgetName)},_mouseDown:function(a){if(!d){this._mouseStarted&&this._mouseUp(a);this._mouseDownEvent=a;var c=this,f=a.which==1,g=typeof this.options.cancel=="string"?b(a.target).parents().add(a.target).filter(this.options.cancel).length:false;if(!f||g||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){c.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted= -this._mouseStart(a)!==false;if(!this._mouseStarted){a.preventDefault();return true}}true===b.data(a.target,this.widgetName+".preventClickEvent")&&b.removeData(a.target,this.widgetName+".preventClickEvent");this._mouseMoveDelegate=function(e){return c._mouseMove(e)};this._mouseUpDelegate=function(e){return c._mouseUp(e)};b(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);a.preventDefault();return d=true}},_mouseMove:function(a){if(b.browser.msie&& -!(document.documentMode>=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted= -false;a.target==this._mouseDownEvent.target&&b.data(a.target,this.widgetName+".preventClickEvent",true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); -;/* - * jQuery UI Draggable 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Draggables - * - * Depends: - * jquery.ui.core.js - * jquery.ui.mouse.js - * jquery.ui.widget.js - */ -(function(d){d.widget("ui.draggable",d.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper== -"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(a){var b= -this.options;if(this.helper||b.disabled||d(a.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(a);if(!this.handle)return false;d(b.iframeFix===true?"iframe":b.iframeFix).each(function(){d('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(d(this).offset()).appendTo("body")});return true},_mouseStart:function(a){var b=this.options;this.helper= -this._createHelper(a);this._cacheHelperProportions();if(d.ui.ddmanager)d.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}); -this.originalPosition=this.position=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);b.containment&&this._setContainment();if(this._trigger("start",a)===false){this._clear();return false}this._cacheHelperProportions();d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(a,true);return true},_mouseDrag:function(a,b){this.position=this._generatePosition(a); -this.positionAbs=this._convertPositionTo("absolute");if(!b){b=this._uiHash();if(this._trigger("drag",a,b)===false){this._mouseUp({});return false}this.position=b.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);return false},_mouseStop:function(a){var b=false;if(d.ui.ddmanager&&!this.options.dropBehaviour)b= -d.ui.ddmanager.drop(this,a);if(this.dropped){b=this.dropped;this.dropped=false}if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return false;if(this.options.revert=="invalid"&&!b||this.options.revert=="valid"&&b||this.options.revert===true||d.isFunction(this.options.revert)&&this.options.revert.call(this.element,b)){var c=this;d(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){c._trigger("stop",a)!==false&&c._clear()})}else this._trigger("stop", -a)!==false&&this._clear();return false},_mouseUp:function(a){this.options.iframeFix===true&&d("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)});return d.ui.mouse.prototype._mouseUp.call(this,a)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(a){var b=!this.options.handle||!d(this.options.handle,this.element).length?true:false;d(this.options.handle,this.element).find("*").andSelf().each(function(){if(this== -a.target)b=true});return b},_createHelper:function(a){var b=this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a])):b.helper=="clone"?this.element.clone().removeAttr("id"):this.element;a.parents("body").length||a.appendTo(b.appendTo=="parent"?this.element[0].parentNode:b.appendTo);a[0]!=this.element[0]&&!/(fixed|absolute)/.test(a.css("position"))&&a.css("position","absolute");return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a= -{left:+a[0],top:+a[1]||0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&& -d.ui.contains(this.scrollParent[0],this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a= -this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions= -{width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment=="parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[(a.containment=="document"?0:d(window).scrollLeft())-this.offset.relative.left-this.offset.parent.left,(a.containment=="document"?0:d(window).scrollTop())-this.offset.relative.top-this.offset.parent.top,(a.containment=="document"?0:d(window).scrollLeft())+ -d(a.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a.containment=="document"?0:d(window).scrollTop())+(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)&&a.containment.constructor!=Array){a=d(a.containment);var b=a[0];if(b){a.offset();var c=d(b).css("overflow")!="hidden";this.containment=[(parseInt(d(b).css("borderLeftWidth"), -10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0),(parseInt(d(b).css("borderTopWidth"),10)||0)+(parseInt(d(b).css("paddingTop"),10)||0),(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"),10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height- -this.margins.top-this.margins.bottom];this.relative_container=a}}else if(a.containment.constructor==Array)this.containment=a.containment},_convertPositionTo:function(a,b){if(!b)b=this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName);return{top:b.top+this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&& -d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0], -this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName),e=a.pageX,h=a.pageY;if(this.originalPosition){var g;if(this.containment){if(this.relative_container){g=this.relative_container.offset();g=[this.containment[0]+g.left,this.containment[1]+g.top,this.containment[2]+g.left,this.containment[3]+g.top]}else g=this.containment;if(a.pageX-this.offset.click.left<g[0])e=g[0]+this.offset.click.left;if(a.pageY-this.offset.click.top<g[1])h=g[1]+this.offset.click.top; -if(a.pageX-this.offset.click.left>g[2])e=g[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>g[3])h=g[3]+this.offset.click.top}if(b.grid){h=this.originalPageY+Math.round((h-this.originalPageY)/b.grid[1])*b.grid[1];h=g?!(h-this.offset.click.top<g[1]||h-this.offset.click.top>g[3])?h:!(h-this.offset.click.top<g[1])?h-b.grid[1]:h+b.grid[1]:h;e=this.originalPageX+Math.round((e-this.originalPageX)/b.grid[0])*b.grid[0];e=g?!(e-this.offset.click.left<g[0]||e-this.offset.click.left>g[2])?e:!(e-this.offset.click.left< -g[0])?e-b.grid[0]:e+b.grid[0]:e}}return{top:h-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop()),left:e-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"); -this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove();this.helper=null;this.cancelHelperRemoval=false},_trigger:function(a,b,c){c=c||this._uiHash();d.ui.plugin.call(this,a,[b,c]);if(a=="drag")this.positionAbs=this._convertPositionTo("absolute");return d.Widget.prototype._trigger.call(this,a,b,c)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}});d.extend(d.ui.draggable,{version:"1.8.13"}); -d.ui.plugin.add("draggable","connectToSortable",{start:function(a,b){var c=d(this).data("draggable"),f=c.options,e=d.extend({},b,{item:c.element});c.sortables=[];d(f.connectToSortable).each(function(){var h=d.data(this,"sortable");if(h&&!h.options.disabled){c.sortables.push({instance:h,shouldRevert:h.options.revert});h.refreshPositions();h._trigger("activate",a,e)}})},stop:function(a,b){var c=d(this).data("draggable"),f=d.extend({},b,{item:c.element});d.each(c.sortables,function(){if(this.instance.isOver){this.instance.isOver= -0;c.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert)this.instance.options.revert=true;this.instance._mouseStop(a);this.instance.options.helper=this.instance.options._helper;c.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})}else{this.instance.cancelHelperRemoval=false;this.instance._trigger("deactivate",a,f)}})},drag:function(a,b){var c=d(this).data("draggable"),f=this;d.each(c.sortables,function(){this.instance.positionAbs= -c.positionAbs;this.instance.helperProportions=c.helperProportions;this.instance.offset.click=c.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){if(!this.instance.isOver){this.instance.isOver=1;this.instance.currentItem=d(f).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",true);this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return b.helper[0]};a.target=this.instance.currentItem[0];this.instance._mouseCapture(a, -true);this.instance._mouseStart(a,true,true);this.instance.offset.click.top=c.offset.click.top;this.instance.offset.click.left=c.offset.click.left;this.instance.offset.parent.left-=c.offset.parent.left-this.instance.offset.parent.left;this.instance.offset.parent.top-=c.offset.parent.top-this.instance.offset.parent.top;c._trigger("toSortable",a);c.dropped=this.instance.element;c.currentItem=c.element;this.instance.fromOutside=c}this.instance.currentItem&&this.instance._mouseDrag(a)}else if(this.instance.isOver){this.instance.isOver= -0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",a,this.instance._uiHash(this.instance));this.instance._mouseStop(a,true);this.instance.options.helper=this.instance.options._helper;this.instance.currentItem.remove();this.instance.placeholder&&this.instance.placeholder.remove();c._trigger("fromSortable",a);c.dropped=false}})}});d.ui.plugin.add("draggable","cursor",{start:function(){var a=d("body"),b=d(this).data("draggable").options;if(a.css("cursor"))b._cursor= -a.css("cursor");a.css("cursor",b.cursor)},stop:function(){var a=d(this).data("draggable").options;a._cursor&&d("body").css("cursor",a._cursor)}});d.ui.plugin.add("draggable","opacity",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;if(a.css("opacity"))b._opacity=a.css("opacity");a.css("opacity",b.opacity)},stop:function(a,b){a=d(this).data("draggable").options;a._opacity&&d(b.helper).css("opacity",a._opacity)}});d.ui.plugin.add("draggable","scroll",{start:function(){var a=d(this).data("draggable"); -if(a.scrollParent[0]!=document&&a.scrollParent[0].tagName!="HTML")a.overflowOffset=a.scrollParent.offset()},drag:function(a){var b=d(this).data("draggable"),c=b.options,f=false;if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){if(!c.axis||c.axis!="x")if(b.overflowOffset.top+b.scrollParent[0].offsetHeight-a.pageY<c.scrollSensitivity)b.scrollParent[0].scrollTop=f=b.scrollParent[0].scrollTop+c.scrollSpeed;else if(a.pageY-b.overflowOffset.top<c.scrollSensitivity)b.scrollParent[0].scrollTop= -f=b.scrollParent[0].scrollTop-c.scrollSpeed;if(!c.axis||c.axis!="y")if(b.overflowOffset.left+b.scrollParent[0].offsetWidth-a.pageX<c.scrollSensitivity)b.scrollParent[0].scrollLeft=f=b.scrollParent[0].scrollLeft+c.scrollSpeed;else if(a.pageX-b.overflowOffset.left<c.scrollSensitivity)b.scrollParent[0].scrollLeft=f=b.scrollParent[0].scrollLeft-c.scrollSpeed}else{if(!c.axis||c.axis!="x")if(a.pageY-d(document).scrollTop()<c.scrollSensitivity)f=d(document).scrollTop(d(document).scrollTop()-c.scrollSpeed); -else if(d(window).height()-(a.pageY-d(document).scrollTop())<c.scrollSensitivity)f=d(document).scrollTop(d(document).scrollTop()+c.scrollSpeed);if(!c.axis||c.axis!="y")if(a.pageX-d(document).scrollLeft()<c.scrollSensitivity)f=d(document).scrollLeft(d(document).scrollLeft()-c.scrollSpeed);else if(d(window).width()-(a.pageX-d(document).scrollLeft())<c.scrollSensitivity)f=d(document).scrollLeft(d(document).scrollLeft()+c.scrollSpeed)}f!==false&&d.ui.ddmanager&&!c.dropBehaviour&&d.ui.ddmanager.prepareOffsets(b, -a)}});d.ui.plugin.add("draggable","snap",{start:function(){var a=d(this).data("draggable"),b=a.options;a.snapElements=[];d(b.snap.constructor!=String?b.snap.items||":data(draggable)":b.snap).each(function(){var c=d(this),f=c.offset();this!=a.element[0]&&a.snapElements.push({item:this,width:c.outerWidth(),height:c.outerHeight(),top:f.top,left:f.left})})},drag:function(a,b){for(var c=d(this).data("draggable"),f=c.options,e=f.snapTolerance,h=b.offset.left,g=h+c.helperProportions.width,n=b.offset.top, -o=n+c.helperProportions.height,i=c.snapElements.length-1;i>=0;i--){var j=c.snapElements[i].left,l=j+c.snapElements[i].width,k=c.snapElements[i].top,m=k+c.snapElements[i].height;if(j-e<h&&h<l+e&&k-e<n&&n<m+e||j-e<h&&h<l+e&&k-e<o&&o<m+e||j-e<g&&g<l+e&&k-e<n&&n<m+e||j-e<g&&g<l+e&&k-e<o&&o<m+e){if(f.snapMode!="inner"){var p=Math.abs(k-o)<=e,q=Math.abs(m-n)<=e,r=Math.abs(j-g)<=e,s=Math.abs(l-h)<=e;if(p)b.position.top=c._convertPositionTo("relative",{top:k-c.helperProportions.height,left:0}).top-c.margins.top; -if(q)b.position.top=c._convertPositionTo("relative",{top:m,left:0}).top-c.margins.top;if(r)b.position.left=c._convertPositionTo("relative",{top:0,left:j-c.helperProportions.width}).left-c.margins.left;if(s)b.position.left=c._convertPositionTo("relative",{top:0,left:l}).left-c.margins.left}var t=p||q||r||s;if(f.snapMode!="outer"){p=Math.abs(k-n)<=e;q=Math.abs(m-o)<=e;r=Math.abs(j-h)<=e;s=Math.abs(l-g)<=e;if(p)b.position.top=c._convertPositionTo("relative",{top:k,left:0}).top-c.margins.top;if(q)b.position.top= -c._convertPositionTo("relative",{top:m-c.helperProportions.height,left:0}).top-c.margins.top;if(r)b.position.left=c._convertPositionTo("relative",{top:0,left:j}).left-c.margins.left;if(s)b.position.left=c._convertPositionTo("relative",{top:0,left:l-c.helperProportions.width}).left-c.margins.left}if(!c.snapElements[i].snapping&&(p||q||r||s||t))c.options.snap.snap&&c.options.snap.snap.call(c.element,a,d.extend(c._uiHash(),{snapItem:c.snapElements[i].item}));c.snapElements[i].snapping=p||q||r||s||t}else{c.snapElements[i].snapping&& -c.options.snap.release&&c.options.snap.release.call(c.element,a,d.extend(c._uiHash(),{snapItem:c.snapElements[i].item}));c.snapElements[i].snapping=false}}}});d.ui.plugin.add("draggable","stack",{start:function(){var a=d(this).data("draggable").options;a=d.makeArray(d(a.stack)).sort(function(c,f){return(parseInt(d(c).css("zIndex"),10)||0)-(parseInt(d(f).css("zIndex"),10)||0)});if(a.length){var b=parseInt(a[0].style.zIndex)||0;d(a).each(function(c){this.style.zIndex=b+c});this[0].style.zIndex=b+a.length}}}); -d.ui.plugin.add("draggable","zIndex",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;if(a.css("zIndex"))b._zIndex=a.css("zIndex");a.css("zIndex",b.zIndex)},stop:function(a,b){a=d(this).data("draggable").options;a._zIndex&&d(b.helper).css("zIndex",a._zIndex)}})})(jQuery); -;/* - * jQuery UI Droppable 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Droppables - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - * jquery.ui.mouse.js - * jquery.ui.draggable.js - */ -(function(d){d.widget("ui.droppable",{widgetEventPrefix:"drop",options:{accept:"*",activeClass:false,addClasses:true,greedy:false,hoverClass:false,scope:"default",tolerance:"intersect"},_create:function(){var a=this.options,b=a.accept;this.isover=0;this.isout=1;this.accept=d.isFunction(b)?b:function(c){return c.is(b)};this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight};d.ui.ddmanager.droppables[a.scope]=d.ui.ddmanager.droppables[a.scope]||[];d.ui.ddmanager.droppables[a.scope].push(this); -a.addClasses&&this.element.addClass("ui-droppable")},destroy:function(){for(var a=d.ui.ddmanager.droppables[this.options.scope],b=0;b<a.length;b++)a[b]==this&&a.splice(b,1);this.element.removeClass("ui-droppable ui-droppable-disabled").removeData("droppable").unbind(".droppable");return this},_setOption:function(a,b){if(a=="accept")this.accept=d.isFunction(b)?b:function(c){return c.is(b)};d.Widget.prototype._setOption.apply(this,arguments)},_activate:function(a){var b=d.ui.ddmanager.current;this.options.activeClass&& -this.element.addClass(this.options.activeClass);b&&this._trigger("activate",a,this.ui(b))},_deactivate:function(a){var b=d.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass);b&&this._trigger("deactivate",a,this.ui(b))},_over:function(a){var b=d.ui.ddmanager.current;if(!(!b||(b.currentItem||b.element)[0]==this.element[0]))if(this.accept.call(this.element[0],b.currentItem||b.element)){this.options.hoverClass&&this.element.addClass(this.options.hoverClass); -this._trigger("over",a,this.ui(b))}},_out:function(a){var b=d.ui.ddmanager.current;if(!(!b||(b.currentItem||b.element)[0]==this.element[0]))if(this.accept.call(this.element[0],b.currentItem||b.element)){this.options.hoverClass&&this.element.removeClass(this.options.hoverClass);this._trigger("out",a,this.ui(b))}},_drop:function(a,b){var c=b||d.ui.ddmanager.current;if(!c||(c.currentItem||c.element)[0]==this.element[0])return false;var e=false;this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var g= -d.data(this,"droppable");if(g.options.greedy&&!g.options.disabled&&g.options.scope==c.options.scope&&g.accept.call(g.element[0],c.currentItem||c.element)&&d.ui.intersect(c,d.extend(g,{offset:g.element.offset()}),g.options.tolerance)){e=true;return false}});if(e)return false;if(this.accept.call(this.element[0],c.currentItem||c.element)){this.options.activeClass&&this.element.removeClass(this.options.activeClass);this.options.hoverClass&&this.element.removeClass(this.options.hoverClass);this._trigger("drop", -a,this.ui(c));return this.element}return false},ui:function(a){return{draggable:a.currentItem||a.element,helper:a.helper,position:a.position,offset:a.positionAbs}}});d.extend(d.ui.droppable,{version:"1.8.13"});d.ui.intersect=function(a,b,c){if(!b.offset)return false;var e=(a.positionAbs||a.position.absolute).left,g=e+a.helperProportions.width,f=(a.positionAbs||a.position.absolute).top,h=f+a.helperProportions.height,i=b.offset.left,k=i+b.proportions.width,j=b.offset.top,l=j+b.proportions.height; -switch(c){case "fit":return i<=e&&g<=k&&j<=f&&h<=l;case "intersect":return i<e+a.helperProportions.width/2&&g-a.helperProportions.width/2<k&&j<f+a.helperProportions.height/2&&h-a.helperProportions.height/2<l;case "pointer":return d.ui.isOver((a.positionAbs||a.position.absolute).top+(a.clickOffset||a.offset.click).top,(a.positionAbs||a.position.absolute).left+(a.clickOffset||a.offset.click).left,j,i,b.proportions.height,b.proportions.width);case "touch":return(f>=j&&f<=l||h>=j&&h<=l||f<j&&h>l)&&(e>= -i&&e<=k||g>=i&&g<=k||e<i&&g>k);default:return false}};d.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(a,b){var c=d.ui.ddmanager.droppables[a.options.scope]||[],e=b?b.type:null,g=(a.currentItem||a.element).find(":data(droppable)").andSelf(),f=0;a:for(;f<c.length;f++)if(!(c[f].options.disabled||a&&!c[f].accept.call(c[f].element[0],a.currentItem||a.element))){for(var h=0;h<g.length;h++)if(g[h]==c[f].element[0]){c[f].proportions.height=0;continue a}c[f].visible=c[f].element.css("display")!= -"none";if(c[f].visible){e=="mousedown"&&c[f]._activate.call(c[f],b);c[f].offset=c[f].element.offset();c[f].proportions={width:c[f].element[0].offsetWidth,height:c[f].element[0].offsetHeight}}}},drop:function(a,b){var c=false;d.each(d.ui.ddmanager.droppables[a.options.scope]||[],function(){if(this.options){if(!this.options.disabled&&this.visible&&d.ui.intersect(a,this,this.options.tolerance))c=c||this._drop.call(this,b);if(!this.options.disabled&&this.visible&&this.accept.call(this.element[0],a.currentItem|| -a.element)){this.isout=1;this.isover=0;this._deactivate.call(this,b)}}});return c},drag:function(a,b){a.options.refreshPositions&&d.ui.ddmanager.prepareOffsets(a,b);d.each(d.ui.ddmanager.droppables[a.options.scope]||[],function(){if(!(this.options.disabled||this.greedyChild||!this.visible)){var c=d.ui.intersect(a,this,this.options.tolerance);if(c=!c&&this.isover==1?"isout":c&&this.isover==0?"isover":null){var e;if(this.options.greedy){var g=this.element.parents(":data(droppable):eq(0)");if(g.length){e= -d.data(g[0],"droppable");e.greedyChild=c=="isover"?1:0}}if(e&&c=="isover"){e.isover=0;e.isout=1;e._out.call(e,b)}this[c]=1;this[c=="isout"?"isover":"isout"]=0;this[c=="isover"?"_over":"_out"].call(this,b);if(e&&c=="isout"){e.isout=0;e.isover=1;e._over.call(e,b)}}}})}}})(jQuery); -;/* - * jQuery UI Sortable 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Sortables - * - * Depends: - * jquery.ui.core.js - * jquery.ui.mouse.js - * jquery.ui.widget.js - */ -(function(d){d.widget("ui.sortable",d.ui.mouse,{widgetEventPrefix:"sort",options:{appendTo:"parent",axis:false,connectWith:false,containment:false,cursor:"auto",cursorAt:false,dropOnEmpty:true,forcePlaceholderSize:false,forceHelperSize:false,grid:false,handle:false,helper:"original",items:"> *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1E3},_create:function(){var a=this.options;this.containerCache={};this.element.addClass("ui-sortable"); -this.refresh();this.floating=this.items.length?a.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var a=this.items.length-1;a>=0;a--)this.items[a].item.removeData("sortable-item");return this},_setOption:function(a,b){if(a=== -"disabled"){this.options[a]=b;this.widget()[b?"addClass":"removeClass"]("ui-sortable-disabled")}else d.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(a,b){if(this.reverting)return false;if(this.options.disabled||this.options.type=="static")return false;this._refreshItems(a);var c=null,e=this;d(a.target).parents().each(function(){if(d.data(this,"sortable-item")==e){c=d(this);return false}});if(d.data(a.target,"sortable-item")==e)c=d(a.target);if(!c)return false;if(this.options.handle&& -!b){var f=false;d(this.options.handle,c).find("*").andSelf().each(function(){if(this==a.target)f=true});if(!f)return false}this.currentItem=c;this._removeCurrentsFromItems();return true},_mouseStart:function(a,b,c){b=this.options;var e=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(a);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top, -left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]}; -this.helper[0]!=this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder();b.containment&&this._setContainment();if(b.cursor){if(d("body").css("cursor"))this._storedCursor=d("body").css("cursor");d("body").css("cursor",b.cursor)}if(b.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",b.opacity)}if(b.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",b.zIndex)}if(this.scrollParent[0]!= -document&&this.scrollParent[0].tagName!="HTML")this.overflowOffset=this.scrollParent.offset();this._trigger("start",a,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!c)for(c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("activate",a,e._uiHash(this));if(d.ui.ddmanager)d.ui.ddmanager.current=this;d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(a); -return true},_mouseDrag:function(a){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs)this.lastPositionAbs=this.positionAbs;if(this.options.scroll){var b=this.options,c=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if(this.overflowOffset.top+this.scrollParent[0].offsetHeight-a.pageY<b.scrollSensitivity)this.scrollParent[0].scrollTop=c=this.scrollParent[0].scrollTop+b.scrollSpeed;else if(a.pageY-this.overflowOffset.top< -b.scrollSensitivity)this.scrollParent[0].scrollTop=c=this.scrollParent[0].scrollTop-b.scrollSpeed;if(this.overflowOffset.left+this.scrollParent[0].offsetWidth-a.pageX<b.scrollSensitivity)this.scrollParent[0].scrollLeft=c=this.scrollParent[0].scrollLeft+b.scrollSpeed;else if(a.pageX-this.overflowOffset.left<b.scrollSensitivity)this.scrollParent[0].scrollLeft=c=this.scrollParent[0].scrollLeft-b.scrollSpeed}else{if(a.pageY-d(document).scrollTop()<b.scrollSensitivity)c=d(document).scrollTop(d(document).scrollTop()- -b.scrollSpeed);else if(d(window).height()-(a.pageY-d(document).scrollTop())<b.scrollSensitivity)c=d(document).scrollTop(d(document).scrollTop()+b.scrollSpeed);if(a.pageX-d(document).scrollLeft()<b.scrollSensitivity)c=d(document).scrollLeft(d(document).scrollLeft()-b.scrollSpeed);else if(d(window).width()-(a.pageX-d(document).scrollLeft())<b.scrollSensitivity)c=d(document).scrollLeft(d(document).scrollLeft()+b.scrollSpeed)}c!==false&&d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this, -a)}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";for(b=this.items.length-1;b>=0;b--){c=this.items[b];var e=c.item[0],f=this._intersectsWithPointer(c);if(f)if(e!=this.currentItem[0]&&this.placeholder[f==1?"next":"prev"]()[0]!=e&&!d.ui.contains(this.placeholder[0],e)&&(this.options.type=="semi-dynamic"?!d.ui.contains(this.element[0], -e):true)){this.direction=f==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(c))this._rearrange(a,c);else break;this._trigger("change",a,this._uiHash());break}}this._contactContainers(a);d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);this._trigger("sort",a,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(a,b){if(a){d.ui.ddmanager&&!this.options.dropBehaviour&&d.ui.ddmanager.drop(this,a);if(this.options.revert){var c=this;b=c.placeholder.offset(); -c.reverting=true;d(this.helper).animate({left:b.left-this.offset.parent.left-c.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:b.top-this.offset.parent.top-c.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){c._clear(a)})}else this._clear(a,b);return false}},cancel:function(){var a=this;if(this.dragging){this._mouseUp({target:null});this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"): -this.currentItem.show();for(var b=this.containers.length-1;b>=0;b--){this.containers[b]._trigger("deactivate",null,a._uiHash(this));if(this.containers[b].containerCache.over){this.containers[b]._trigger("out",null,a._uiHash(this));this.containers[b].containerCache.over=0}}}if(this.placeholder){this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove();d.extend(this,{helper:null, -dragging:false,reverting:false,_noFinalSort:null});this.domPosition.prev?d(this.domPosition.prev).after(this.currentItem):d(this.domPosition.parent).prepend(this.currentItem)}return this},serialize:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};d(b).each(function(){var e=(d(a.item||this).attr(a.attribute||"id")||"").match(a.expression||/(.+)[-=_](.+)/);if(e)c.push((a.key||e[1]+"[]")+"="+(a.key&&a.expression?e[1]:e[2]))});!c.length&&a.key&&c.push(a.key+"=");return c.join("&")}, -toArray:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};b.each(function(){c.push(d(a.item||this).attr(a.attribute||"id")||"")});return c},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,e=this.positionAbs.top,f=e+this.helperProportions.height,g=a.left,h=g+a.width,i=a.top,k=i+a.height,j=this.offset.click.top,l=this.offset.click.left;j=e+j>i&&e+j<k&&b+l>g&&b+l<h;return this.options.tolerance=="pointer"||this.options.forcePointerForContainers|| -this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>a[this.floating?"width":"height"]?j:g<b+this.helperProportions.width/2&&c-this.helperProportions.width/2<h&&i<e+this.helperProportions.height/2&&f-this.helperProportions.height/2<k},_intersectsWithPointer:function(a){var b=d.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,a.top,a.height);a=d.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,a.left,a.width);b=b&&a;a=this._getDragVerticalDirection(); -var c=this._getDragHorizontalDirection();if(!b)return false;return this.floating?c&&c=="right"||a=="down"?2:1:a&&(a=="down"?2:1)},_intersectsWithSides:function(a){var b=d.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,a.top+a.height/2,a.height);a=d.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,a.left+a.width/2,a.width);var c=this._getDragVerticalDirection(),e=this._getDragHorizontalDirection();return this.floating&&e?e=="right"&&a||e=="left"&&!a:c&&(c=="down"&&b||c=="up"&&!b)}, -_getDragVerticalDirection:function(){var a=this.positionAbs.top-this.lastPositionAbs.top;return a!=0&&(a>0?"down":"up")},_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){this._refreshItems(a);this.refreshPositions();return this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(a){var b=[],c=[],e=this._connectWith(); -if(e&&a)for(a=e.length-1;a>=0;a--)for(var f=d(e[a]),g=f.length-1;g>=0;g--){var h=d.data(f[g],"sortable");if(h&&h!=this&&!h.options.disabled)c.push([d.isFunction(h.options.items)?h.options.items.call(h.element):d(h.options.items,h.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),h])}c.push([d.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):d(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), -this]);for(a=c.length-1;a>=0;a--)c[a][0].each(function(){b.push(this)});return d(b)},_removeCurrentsFromItems:function(){for(var a=this.currentItem.find(":data(sortable-item)"),b=0;b<this.items.length;b++)for(var c=0;c<a.length;c++)a[c]==this.items[b].item[0]&&this.items.splice(b,1)},_refreshItems:function(a){this.items=[];this.containers=[this];var b=this.items,c=[[d.isFunction(this.options.items)?this.options.items.call(this.element[0],a,{item:this.currentItem}):d(this.options.items,this.element), -this]],e=this._connectWith();if(e)for(var f=e.length-1;f>=0;f--)for(var g=d(e[f]),h=g.length-1;h>=0;h--){var i=d.data(g[h],"sortable");if(i&&i!=this&&!i.options.disabled){c.push([d.isFunction(i.options.items)?i.options.items.call(i.element[0],a,{item:this.currentItem}):d(i.options.items,i.element),i]);this.containers.push(i)}}for(f=c.length-1;f>=0;f--){a=c[f][1];e=c[f][0];h=0;for(g=e.length;h<g;h++){i=d(e[h]);i.data("sortable-item",a);b.push({item:i,instance:a,width:0,height:0,left:0,top:0})}}},refreshPositions:function(a){if(this.offsetParent&& -this.helper)this.offset.parent=this._getParentOffset();for(var b=this.items.length-1;b>=0;b--){var c=this.items[b];if(!(c.instance!=this.currentContainer&&this.currentContainer&&c.item[0]!=this.currentItem[0])){var e=this.options.toleranceElement?d(this.options.toleranceElement,c.item):c.item;if(!a){c.width=e.outerWidth();c.height=e.outerHeight()}e=e.offset();c.left=e.left;c.top=e.top}}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(b= -this.containers.length-1;b>=0;b--){e=this.containers[b].element.offset();this.containers[b].containerCache.left=e.left;this.containers[b].containerCache.top=e.top;this.containers[b].containerCache.width=this.containers[b].element.outerWidth();this.containers[b].containerCache.height=this.containers[b].element.outerHeight()}return this},_createPlaceholder:function(a){var b=a||this,c=b.options;if(!c.placeholder||c.placeholder.constructor==String){var e=c.placeholder;c.placeholder={element:function(){var f= -d(document.createElement(b.currentItem[0].nodeName)).addClass(e||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!e)f.style.visibility="hidden";return f},update:function(f,g){if(!(e&&!c.forcePlaceholderSize)){g.height()||g.height(b.currentItem.innerHeight()-parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10));g.width()||g.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")|| -0,10))}}}}b.placeholder=d(c.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder);c.placeholder.update(b,b.placeholder)},_contactContainers:function(a){for(var b=null,c=null,e=this.containers.length-1;e>=0;e--)if(!d.ui.contains(this.currentItem[0],this.containers[e].element[0]))if(this._intersectsWith(this.containers[e].containerCache)){if(!(b&&d.ui.contains(this.containers[e].element[0],b.element[0]))){b=this.containers[e];c=e}}else if(this.containers[e].containerCache.over){this.containers[e]._trigger("out", -a,this._uiHash(this));this.containers[e].containerCache.over=0}if(b)if(this.containers.length===1){this.containers[c]._trigger("over",a,this._uiHash(this));this.containers[c].containerCache.over=1}else if(this.currentContainer!=this.containers[c]){b=1E4;e=null;for(var f=this.positionAbs[this.containers[c].floating?"left":"top"],g=this.items.length-1;g>=0;g--)if(d.ui.contains(this.containers[c].element[0],this.items[g].item[0])){var h=this.items[g][this.containers[c].floating?"left":"top"];if(Math.abs(h- -f)<b){b=Math.abs(h-f);e=this.items[g]}}if(e||this.options.dropOnEmpty){this.currentContainer=this.containers[c];e?this._rearrange(a,e,null,true):this._rearrange(a,null,this.containers[c].element,true);this._trigger("change",a,this._uiHash());this.containers[c]._trigger("change",a,this._uiHash(this));this.options.placeholder.update(this.currentContainer,this.placeholder);this.containers[c]._trigger("over",a,this._uiHash(this));this.containers[c].containerCache.over=1}}},_createHelper:function(a){var b= -this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a,this.currentItem])):b.helper=="clone"?this.currentItem.clone():this.currentItem;a.parents("body").length||d(b.appendTo!="parent"?b.appendTo:this.currentItem[0].parentNode)[0].appendChild(a[0]);if(a[0]==this.currentItem[0])this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")};if(a[0].style.width== -""||b.forceHelperSize)a.width(this.currentItem.width());if(a[0].style.height==""||b.forceHelperSize)a.height(this.currentItem.height());return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a={left:+a[0],top:+a[1]||0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top= -this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a= -{top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.currentItem.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"), -10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment=="parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,d(a.containment=="document"? -document:window).width()-this.helperProportions.width-this.margins.left,(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)){var b=d(a.containment)[0];a=d(a.containment).offset();var c=d(b).css("overflow")!="hidden";this.containment=[a.left+(parseInt(d(b).css("borderLeftWidth"),10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0)-this.margins.left,a.top+(parseInt(d(b).css("borderTopWidth"), -10)||0)+(parseInt(d(b).css("paddingTop"),10)||0)-this.margins.top,a.left+(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,a.top+(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"),10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(a,b){if(!b)b= -this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(c[0].tagName);return{top:b.top+this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():e?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&& -this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():e?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(c[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0]))this.offset.relative=this._getRelativeOffset(); -var f=a.pageX,g=a.pageY;if(this.originalPosition){if(this.containment){if(a.pageX-this.offset.click.left<this.containment[0])f=this.containment[0]+this.offset.click.left;if(a.pageY-this.offset.click.top<this.containment[1])g=this.containment[1]+this.offset.click.top;if(a.pageX-this.offset.click.left>this.containment[2])f=this.containment[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>this.containment[3])g=this.containment[3]+this.offset.click.top}if(b.grid){g=this.originalPageY+Math.round((g- -this.originalPageY)/b.grid[1])*b.grid[1];g=this.containment?!(g-this.offset.click.top<this.containment[1]||g-this.offset.click.top>this.containment[3])?g:!(g-this.offset.click.top<this.containment[1])?g-b.grid[1]:g+b.grid[1]:g;f=this.originalPageX+Math.round((f-this.originalPageX)/b.grid[0])*b.grid[0];f=this.containment?!(f-this.offset.click.left<this.containment[0]||f-this.offset.click.left>this.containment[2])?f:!(f-this.offset.click.left<this.containment[0])?f-b.grid[0]:f+b.grid[0]:f}}return{top:g- -this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(d.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():e?0:c.scrollTop()),left:f-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(d.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():e?0:c.scrollLeft())}},_rearrange:function(a,b,c,e){c?c[0].appendChild(this.placeholder[0]):b.item[0].parentNode.insertBefore(this.placeholder[0], -this.direction=="down"?b.item[0]:b.item[0].nextSibling);this.counter=this.counter?++this.counter:1;var f=this,g=this.counter;window.setTimeout(function(){g==f.counter&&f.refreshPositions(!e)},0)},_clear:function(a,b){this.reverting=false;var c=[];!this._noFinalSort&&this.currentItem[0].parentNode&&this.placeholder.before(this.currentItem);this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var e in this._storedCSS)if(this._storedCSS[e]=="auto"||this._storedCSS[e]=="static")this._storedCSS[e]= -"";this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();this.fromOutside&&!b&&c.push(function(f){this._trigger("receive",f,this._uiHash(this.fromOutside))});if((this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!b)c.push(function(f){this._trigger("update",f,this._uiHash())});if(!d.ui.contains(this.element[0],this.currentItem[0])){b||c.push(function(f){this._trigger("remove", -f,this._uiHash())});for(e=this.containers.length-1;e>=0;e--)if(d.ui.contains(this.containers[e].element[0],this.currentItem[0])&&!b){c.push(function(f){return function(g){f._trigger("receive",g,this._uiHash(this))}}.call(this,this.containers[e]));c.push(function(f){return function(g){f._trigger("update",g,this._uiHash(this))}}.call(this,this.containers[e]))}}for(e=this.containers.length-1;e>=0;e--){b||c.push(function(f){return function(g){f._trigger("deactivate",g,this._uiHash(this))}}.call(this, -this.containers[e]));if(this.containers[e].containerCache.over){c.push(function(f){return function(g){f._trigger("out",g,this._uiHash(this))}}.call(this,this.containers[e]));this.containers[e].containerCache.over=0}}this._storedCursor&&d("body").css("cursor",this._storedCursor);this._storedOpacity&&this.helper.css("opacity",this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!b){this._trigger("beforeStop", -a,this._uiHash());for(e=0;e<c.length;e++)c[e].call(this,a);this._trigger("stop",a,this._uiHash())}return false}b||this._trigger("beforeStop",a,this._uiHash());this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.helper[0]!=this.currentItem[0]&&this.helper.remove();this.helper=null;if(!b){for(e=0;e<c.length;e++)c[e].call(this,a);this._trigger("stop",a,this._uiHash())}this.fromOutside=false;return true},_trigger:function(){d.Widget.prototype._trigger.apply(this,arguments)===false&&this.cancel()}, -_uiHash:function(a){var b=a||this;return{helper:b.helper,placeholder:b.placeholder||d([]),position:b.position,originalPosition:b.originalPosition,offset:b.positionAbs,item:b.currentItem,sender:a?a.element:null}}});d.extend(d.ui.sortable,{version:"1.8.13"})})(jQuery); -;/* - * jQuery UI Effects 1.8.13 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Effects/ - */ -jQuery.effects||function(f,j){function m(c){var a;if(c&&c.constructor==Array&&c.length==3)return c;if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c))return[parseInt(a[1],10),parseInt(a[2],10),parseInt(a[3],10)];if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c))return[parseInt(a[1], -16),parseInt(a[2],16),parseInt(a[3],16)];if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];if(/rgba\(0, 0, 0, 0\)/.exec(c))return n.transparent;return n[f.trim(c).toLowerCase()]}function s(c,a){var b;do{b=f.curCSS(c,a);if(b!=""&&b!="transparent"||f.nodeName(c,"body"))break;a="backgroundColor"}while(c=c.parentNode);return m(b)}function o(){var c=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle, -a={},b,d;if(c&&c.length&&c[0]&&c[c[0]])for(var e=c.length;e--;){b=c[e];if(typeof c[b]=="string"){d=b.replace(/\-(\w)/g,function(g,h){return h.toUpperCase()});a[d]=c[b]}}else for(b in c)if(typeof c[b]==="string")a[b]=c[b];return a}function p(c){var a,b;for(a in c){b=c[a];if(b==null||f.isFunction(b)||a in t||/scrollbar/.test(a)||!/color/i.test(a)&&isNaN(parseFloat(b)))delete c[a]}return c}function u(c,a){var b={_:0},d;for(d in a)if(c[d]!=a[d])b[d]=a[d];return b}function k(c,a,b,d){if(typeof c=="object"){d= -a;b=null;a=c;c=a.effect}if(f.isFunction(a)){d=a;b=null;a={}}if(typeof a=="number"||f.fx.speeds[a]){d=b;b=a;a={}}if(f.isFunction(b)){d=b;b=null}a=a||{};b=b||a.duration;b=f.fx.off?0:typeof b=="number"?b:b in f.fx.speeds?f.fx.speeds[b]:f.fx.speeds._default;d=d||a.complete;return[c,a,b,d]}function l(c){if(!c||typeof c==="number"||f.fx.speeds[c])return true;if(typeof c==="string"&&!f.effects[c])return true;return false}f.effects={};f.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor", -"borderTopColor","borderColor","color","outlineColor"],function(c,a){f.fx.step[a]=function(b){if(!b.colorInit){b.start=s(b.elem,a);b.end=m(b.end);b.colorInit=true}b.elem.style[a]="rgb("+Math.max(Math.min(parseInt(b.pos*(b.end[0]-b.start[0])+b.start[0],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[1]-b.start[1])+b.start[1],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[2]-b.start[2])+b.start[2],10),255),0)+")"}});var n={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0, -0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211, -211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},q=["add","remove","toggle"],t={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};f.effects.animateClass=function(c,a,b, -d){if(f.isFunction(b)){d=b;b=null}return this.queue(function(){var e=f(this),g=e.attr("style")||" ",h=p(o.call(this)),r,v=e.attr("class");f.each(q,function(w,i){c[i]&&e[i+"Class"](c[i])});r=p(o.call(this));e.attr("class",v);e.animate(u(h,r),{queue:false,duration:a,easding:b,complete:function(){f.each(q,function(w,i){c[i]&&e[i+"Class"](c[i])});if(typeof e.attr("style")=="object"){e.attr("style").cssText="";e.attr("style").cssText=g}else e.attr("style",g);d&&d.apply(this,arguments);f.dequeue(this)}})})}; -f.fn.extend({_addClass:f.fn.addClass,addClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{add:c},a,b,d]):this._addClass(c)},_removeClass:f.fn.removeClass,removeClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{remove:c},a,b,d]):this._removeClass(c)},_toggleClass:f.fn.toggleClass,toggleClass:function(c,a,b,d,e){return typeof a=="boolean"||a===j?b?f.effects.animateClass.apply(this,[a?{add:c}:{remove:c},b,d,e]):this._toggleClass(c,a):f.effects.animateClass.apply(this, -[{toggle:c},a,b,d])},switchClass:function(c,a,b,d,e){return f.effects.animateClass.apply(this,[{add:a,remove:c},b,d,e])}});f.extend(f.effects,{version:"1.8.13",save:function(c,a){for(var b=0;b<a.length;b++)a[b]!==null&&c.data("ec.storage."+a[b],c[0].style[a[b]])},restore:function(c,a){for(var b=0;b<a.length;b++)a[b]!==null&&c.css(a[b],c.data("ec.storage."+a[b]))},setMode:function(c,a){if(a=="toggle")a=c.is(":hidden")?"show":"hide";return a},getBaseline:function(c,a){var b;switch(c[0]){case "top":b= -0;break;case "middle":b=0.5;break;case "bottom":b=1;break;default:b=c[0]/a.height}switch(c[1]){case "left":c=0;break;case "center":c=0.5;break;case "right":c=1;break;default:c=c[1]/a.width}return{x:c,y:b}},createWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent();var a={width:c.outerWidth(true),height:c.outerHeight(true),"float":c.css("float")},b=f("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}); -c.wrap(b);b=c.parent();if(c.css("position")=="static"){b.css({position:"relative"});c.css({position:"relative"})}else{f.extend(a,{position:c.css("position"),zIndex:c.css("z-index")});f.each(["top","left","bottom","right"],function(d,e){a[e]=c.css(e);if(isNaN(parseInt(a[e],10)))a[e]="auto"});c.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})}return b.css(a).show()},removeWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent().replaceWith(c);return c},setTransition:function(c, -a,b,d){d=d||{};f.each(a,function(e,g){unit=c.cssUnit(g);if(unit[0]>0)d[g]=unit[0]*b+unit[1]});return d}});f.fn.extend({effect:function(c){var a=k.apply(this,arguments),b={options:a[1],duration:a[2],callback:a[3]};a=b.options.mode;var d=f.effects[c];if(f.fx.off||!d)return a?this[a](b.duration,b.callback):this.each(function(){b.callback&&b.callback.call(this)});return d.call(this,b)},_show:f.fn.show,show:function(c){if(l(c))return this._show.apply(this,arguments);else{var a=k.apply(this,arguments); -a[1].mode="show";return this.effect.apply(this,a)}},_hide:f.fn.hide,hide:function(c){if(l(c))return this._hide.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="hide";return this.effect.apply(this,a)}},__toggle:f.fn.toggle,toggle:function(c){if(l(c)||typeof c==="boolean"||f.isFunction(c))return this.__toggle.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="toggle";return this.effect.apply(this,a)}},cssUnit:function(c){var a=this.css(c),b=[];f.each(["em","px","%", -"pt"],function(d,e){if(a.indexOf(e)>0)b=[parseFloat(a),e]});return b}});f.easing.jswing=f.easing.swing;f.extend(f.easing,{def:"easeOutQuad",swing:function(c,a,b,d,e){return f.easing[f.easing.def](c,a,b,d,e)},easeInQuad:function(c,a,b,d,e){return d*(a/=e)*a+b},easeOutQuad:function(c,a,b,d,e){return-d*(a/=e)*(a-2)+b},easeInOutQuad:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a+b;return-d/2*(--a*(a-2)-1)+b},easeInCubic:function(c,a,b,d,e){return d*(a/=e)*a*a+b},easeOutCubic:function(c,a,b,d,e){return d* -((a=a/e-1)*a*a+1)+b},easeInOutCubic:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a+b;return d/2*((a-=2)*a*a+2)+b},easeInQuart:function(c,a,b,d,e){return d*(a/=e)*a*a*a+b},easeOutQuart:function(c,a,b,d,e){return-d*((a=a/e-1)*a*a*a-1)+b},easeInOutQuart:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a+b;return-d/2*((a-=2)*a*a*a-2)+b},easeInQuint:function(c,a,b,d,e){return d*(a/=e)*a*a*a*a+b},easeOutQuint:function(c,a,b,d,e){return d*((a=a/e-1)*a*a*a*a+1)+b},easeInOutQuint:function(c,a,b,d,e){if((a/= -e/2)<1)return d/2*a*a*a*a*a+b;return d/2*((a-=2)*a*a*a*a+2)+b},easeInSine:function(c,a,b,d,e){return-d*Math.cos(a/e*(Math.PI/2))+d+b},easeOutSine:function(c,a,b,d,e){return d*Math.sin(a/e*(Math.PI/2))+b},easeInOutSine:function(c,a,b,d,e){return-d/2*(Math.cos(Math.PI*a/e)-1)+b},easeInExpo:function(c,a,b,d,e){return a==0?b:d*Math.pow(2,10*(a/e-1))+b},easeOutExpo:function(c,a,b,d,e){return a==e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b},easeInOutExpo:function(c,a,b,d,e){if(a==0)return b;if(a==e)return b+d;if((a/= -e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b},easeInCirc:function(c,a,b,d,e){return-d*(Math.sqrt(1-(a/=e)*a)-1)+b},easeOutCirc:function(c,a,b,d,e){return d*Math.sqrt(1-(a=a/e-1)*a)+b},easeInOutCirc:function(c,a,b,d,e){if((a/=e/2)<1)return-d/2*(Math.sqrt(1-a*a)-1)+b;return d/2*(Math.sqrt(1-(a-=2)*a)+1)+b},easeInElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h<Math.abs(d)){h=d;c=g/4}else c=g/(2*Math.PI)*Math.asin(d/ -h);return-(h*Math.pow(2,10*(a-=1))*Math.sin((a*e-c)*2*Math.PI/g))+b},easeOutElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h<Math.abs(d)){h=d;c=g/4}else c=g/(2*Math.PI)*Math.asin(d/h);return h*Math.pow(2,-10*a)*Math.sin((a*e-c)*2*Math.PI/g)+d+b},easeInOutElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e/2)==2)return b+d;g||(g=e*0.3*1.5);if(h<Math.abs(d)){h=d;c=g/4}else c=g/(2*Math.PI)*Math.asin(d/h);if(a<1)return-0.5* -h*Math.pow(2,10*(a-=1))*Math.sin((a*e-c)*2*Math.PI/g)+b;return h*Math.pow(2,-10*(a-=1))*Math.sin((a*e-c)*2*Math.PI/g)*0.5+d+b},easeInBack:function(c,a,b,d,e,g){if(g==j)g=1.70158;return d*(a/=e)*a*((g+1)*a-g)+b},easeOutBack:function(c,a,b,d,e,g){if(g==j)g=1.70158;return d*((a=a/e-1)*a*((g+1)*a+g)+1)+b},easeInOutBack:function(c,a,b,d,e,g){if(g==j)g=1.70158;if((a/=e/2)<1)return d/2*a*a*(((g*=1.525)+1)*a-g)+b;return d/2*((a-=2)*a*(((g*=1.525)+1)*a+g)+2)+b},easeInBounce:function(c,a,b,d,e){return d-f.easing.easeOutBounce(c, -e-a,0,d,e)+b},easeOutBounce:function(c,a,b,d,e){return(a/=e)<1/2.75?d*7.5625*a*a+b:a<2/2.75?d*(7.5625*(a-=1.5/2.75)*a+0.75)+b:a<2.5/2.75?d*(7.5625*(a-=2.25/2.75)*a+0.9375)+b:d*(7.5625*(a-=2.625/2.75)*a+0.984375)+b},easeInOutBounce:function(c,a,b,d,e){if(a<e/2)return f.easing.easeInBounce(c,a*2,0,d,e)*0.5+b;return f.easing.easeOutBounce(c,a*2-e,0,d,e)*0.5+d*0.5+b}})}(jQuery); -; \ No newline at end of file diff --git a/feincms/static/feincms/jquery.alerts.js b/feincms/static/feincms/jquery.alerts.js deleted file mode 100644 index 6d04d510b..000000000 --- a/feincms/static/feincms/jquery.alerts.js +++ /dev/null @@ -1,235 +0,0 @@ -// jQuery Alert Dialogs Plugin -// -// Version 1.0 -// -// Cory S.N. LaViska -// A Beautiful Site (http://abeautifulsite.net/) -// 29 December 2008 -// -// Visit http://abeautifulsite.net/notebook/87 for more information -// -// Usage: -// jAlert( message, [title, callback] ) -// jConfirm( message, [title, callback] ) -// jPrompt( message, [value, title, callback] ) -// -// History: -// -// 1.00 - Released (29 December 2008) -// -// License: -// -// This plugin is licensed under the GNU General Public License: http://www.gnu.org/licenses/gpl.html -// -(function($) { - - $.alerts = { - - // These properties can be read/written by accessing $.alerts.propertyName from your scripts at any time - - verticalOffset: -75, // vertical offset of the dialog from center screen, in pixels - horizontalOffset: 0, // horizontal offset of the dialog from center screen, in pixels/ - repositionOnResize: true, // re-centers the dialog on window resize - overlayOpacity: .01, // transparency level of overlay - overlayColor: '#FFF', // base color of overlay - draggable: true, // make the dialogs draggable (requires UI Draggables plugin) - okButton: ' OK ', // text for the OK button - cancelButton: ' Cancel ', // text for the Cancel button - dialogClass: null, // if specified, this class will be applied to all dialogs - - // Public methods - - alert: function(message, title, callback) { - if( title == null ) title = 'Alert'; - $.alerts._show(title, message, null, 'alert', function(result) { - if( callback ) callback(result); - }); - }, - - confirm: function(message, title, callback) { - if( title == null ) title = 'Confirm'; - $.alerts._show(title, message, null, 'confirm', function(result) { - if( callback ) callback(result); - }); - }, - - prompt: function(message, value, title, callback) { - if( title == null ) title = 'Prompt'; - $.alerts._show(title, message, value, 'prompt', function(result) { - if( callback ) callback(result); - }); - }, - - // Private methods - - _show: function(title, msg, value, type, callback) { - - $.alerts._hide(); - $.alerts._overlay('show'); - - $("BODY").append( - '<div id="popup_container">' + - '<h1 id="popup_title"></h1>' + - '<div id="popup_content">' + - '<div id="popup_message"></div>' + - '</div>' + - '</div>'); - - if( $.alerts.dialogClass ) $("#popup_container").addClass($.alerts.dialogClass); - - // IE6 Fix - var pos = ($.browser.msie && parseInt($.browser.version) <= 6 ) ? 'absolute' : 'fixed'; - - $("#popup_container").css({ - position: pos, - zIndex: 99999, - padding: 0, - margin: 0 - }); - - $("#popup_title").text(title); - $("#popup_content").addClass(type); - $("#popup_message").text(msg); - $("#popup_message").html( $("#popup_message").text().replace(/\n/g, '<br />') ); - - $("#popup_container").css({ - minWidth: $("#popup_container").outerWidth(), - maxWidth: $("#popup_container").outerWidth() - }); - - $.alerts._reposition(); - $.alerts._maintainPosition(true); - - switch( type ) { - case 'alert': - $("#popup_message").after('<div id="popup_panel"><input type="button" value="' + $.alerts.okButton + '" id="popup_ok" /></div>'); - $("#popup_ok").click( function() { - $.alerts._hide(); - callback(true); - }); - $("#popup_ok").focus().keypress( function(e) { - if( e.keyCode == 13 || e.keyCode == 27 ) $("#popup_ok").trigger('click'); - }); - break; - case 'confirm': - $("#popup_message").after('<div id="popup_panel"><input type="button" value="' + $.alerts.okButton + '" id="popup_ok" /> <input type="button" value="' + $.alerts.cancelButton + '" id="popup_cancel" /></div>'); - $("#popup_ok").click( function() { - $.alerts._hide(); - if( callback ) callback(true); - }); - $("#popup_cancel").click( function() { - $.alerts._hide(); - if( callback ) callback(false); - }); - $("#popup_ok").focus(); - $("#popup_ok, #popup_cancel").keypress( function(e) { - if( e.keyCode == 13 ) $("#popup_ok").trigger('click'); - if( e.keyCode == 27 ) $("#popup_cancel").trigger('click'); - }); - break; - case 'prompt': - $("#popup_message").append('<br /><input type="text" size="30" id="popup_prompt" />').after('<div id="popup_panel"><input type="button" value="' + $.alerts.okButton + '" id="popup_ok" /> <input type="button" value="' + $.alerts.cancelButton + '" id="popup_cancel" /></div>'); - $("#popup_prompt").width( $("#popup_message").width() ); - $("#popup_ok").click( function() { - var val = $("#popup_prompt").val(); - $.alerts._hide(); - if( callback ) callback( val ); - }); - $("#popup_cancel").click( function() { - $.alerts._hide(); - if( callback ) callback( null ); - }); - $("#popup_prompt, #popup_ok, #popup_cancel").keypress( function(e) { - if( e.keyCode == 13 ) $("#popup_ok").trigger('click'); - if( e.keyCode == 27 ) $("#popup_cancel").trigger('click'); - }); - if( value ) $("#popup_prompt").val(value); - $("#popup_prompt").focus().select(); - break; - } - - // Make draggable - if( $.alerts.draggable ) { - try { - $("#popup_container").draggable({ handle: $("#popup_title") }); - $("#popup_title").css({ cursor: 'move' }); - } catch(e) { /* requires jQuery UI draggables */ } - } - }, - - _hide: function() { - $("#popup_container").remove(); - $.alerts._overlay('hide'); - $.alerts._maintainPosition(false); - }, - - _overlay: function(status) { - switch( status ) { - case 'show': - $.alerts._overlay('hide'); - $("BODY").append('<div id="popup_overlay"></div>'); - $("#popup_overlay").css({ - position: 'absolute', - zIndex: 99998, - top: '0px', - left: '0px', - width: '100%', - height: $(document).height(), - background: $.alerts.overlayColor, - opacity: $.alerts.overlayOpacity, - display: 'none' - }); - break; - case 'hide': - $("#popup_overlay").remove(); - break; - } - }, - - _reposition: function() { - var top = (($(window).height() / 2) - ($("#popup_container").outerHeight() / 2)) + $.alerts.verticalOffset; - var left = (($(window).width() / 2) - ($("#popup_container").outerWidth() / 2)) + $.alerts.horizontalOffset; - if( top < 0 ) top = 0; - if( left < 0 ) left = 0; - - // IE6 fix - if( $.browser.msie && parseInt($.browser.version) <= 6 ) top = top + $(window).scrollTop(); - - $("#popup_container").css({ - top: top + 'px', - left: left + 'px' - }); - $("#popup_overlay").height( $(document).height() ); - }, - - _maintainPosition: function(status) { - if( $.alerts.repositionOnResize ) { - switch(status) { - case true: - $(window).bind('resize', function() { - $.alerts._reposition(); - }); - break; - case false: - $(window).unbind('resize'); - break; - } - } - } - - } - - // Shortuct functions - jAlert = function(message, title, callback) { - $.alerts.alert(message, title, callback); - } - - jConfirm = function(message, title, callback) { - $.alerts.confirm(message, title, callback); - }; - - jPrompt = function(message, value, title, callback) { - $.alerts.prompt(message, value, title, callback); - }; - -})(jQuery); \ No newline at end of file diff --git a/feincms/static/feincms/jquery.cookie.js b/feincms/static/feincms/jquery.cookie.js deleted file mode 100644 index 6df1faca2..000000000 --- a/feincms/static/feincms/jquery.cookie.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Cookie plugin - * - * Copyright (c) 2006 Klaus Hartl (stilbuero.de) - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - */ - -/** - * Create a cookie with the given name and value and other optional parameters. - * - * @example $.cookie('the_cookie', 'the_value'); - * @desc Set the value of a cookie. - * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); - * @desc Create a cookie with all available options. - * @example $.cookie('the_cookie', 'the_value'); - * @desc Create a session cookie. - * @example $.cookie('the_cookie', null); - * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain - * used when the cookie was set. - * - * @param String name The name of the cookie. - * @param String value The value of the cookie. - * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. - * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. - * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. - * If set to null or omitted, the cookie will be a session cookie and will not be retained - * when the the browser exits. - * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). - * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). - * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will - * require a secure protocol (like HTTPS). - * @type undefined - * - * @name $.cookie - * @cat Plugins/Cookie - * @author Klaus Hartl/klaus.hartl@stilbuero.de - */ - -/** - * Get the value of a cookie with the given name. - * - * @example $.cookie('the_cookie'); - * @desc Get the value of a cookie. - * - * @param String name The name of the cookie. - * @return The value of the cookie. - * @type String - * - * @name $.cookie - * @cat Plugins/Cookie - * @author Klaus Hartl/klaus.hartl@stilbuero.de - */ -jQuery.cookie = function(name, value, options) { - if (typeof value != 'undefined') { // name and value given, set cookie - options = options || {}; - if (value === null) { - value = ''; - options.expires = -1; - } - var expires = ''; - if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { - var date; - if (typeof options.expires == 'number') { - date = new Date(); - date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); - } else { - date = options.expires; - } - expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE - } - // CAUTION: Needed to parenthesize options.path and options.domain - // in the following expressions, otherwise they evaluate to undefined - // in the packed version for some reason... - var path = options.path ? '; path=' + (options.path) : ''; - var domain = options.domain ? '; domain=' + (options.domain) : ''; - var secure = options.secure ? '; secure' : ''; - document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); - } else { // only name given, get cookie - var cookieValue = null; - if (document.cookie && document.cookie != '') { - var cookies = document.cookie.split(';'); - for (var i = 0; i < cookies.length; i++) { - var cookie = jQuery.trim(cookies[i]); - // Does this cookie string begin with the name we want? - if (cookie.substring(0, name.length + 1) == (name + '=')) { - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); - break; - } - } - } - return cookieValue; - } -}; \ No newline at end of file diff --git a/feincms/static/feincms/jquery.json-1.3.js b/feincms/static/feincms/jquery.json-1.3.js deleted file mode 100644 index bbdfbad38..000000000 --- a/feincms/static/feincms/jquery.json-1.3.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - * jQuery JSON Plugin - * version: 1.0 (2008-04-17) - * - * This document is licensed as free software under the terms of the - * MIT License: http://www.opensource.org/licenses/mit-license.php - * - * Brantley Harris technically wrote this plugin, but it is based somewhat - * on the JSON.org website's http://www.json.org/json2.js, which proclaims: - * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that - * I uphold. I really just cleaned it up. - * - * It is also based heavily on MochiKit's serializeJSON, which is - * copywrited 2005 by Bob Ippolito. - */ - -(function($) { - function toIntegersAtLease(n) - // Format integers to have at least two digits. - { - return n < 10 ? '0' + n : n; - } - - Date.prototype.toJSON = function(date) - // Yes, it polutes the Date namespace, but we'll allow it here, as - // it's damned usefull. - { - return this.getUTCFullYear() + '-' + - toIntegersAtLease(this.getUTCMonth()) + '-' + - toIntegersAtLease(this.getUTCDate()); - }; - - var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g; - var meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }; - - $.quoteString = function(string) - // Places quotes around a string, inteligently. - // If the string contains no control characters, no quote characters, and no - // backslash characters, then we can safely slap some quotes around it. - // Otherwise we must also replace the offending characters with safe escape - // sequences. - { - if (escapeable.test(string)) - { - return '"' + string.replace(escapeable, function (a) - { - var c = meta[a]; - if (typeof c === 'string') { - return c; - } - c = a.charCodeAt(); - return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); - }) + '"'; - } - return '"' + string + '"'; - }; - - $.toJSON = function(o, compact) - { - var type = typeof(o); - - if (type == "undefined") - return "undefined"; - else if (type == "number" || type == "boolean") - return o + ""; - else if (o === null) - return "null"; - - // Is it a string? - if (type == "string") - { - return $.quoteString(o); - } - - // Does it have a .toJSON function? - if (type == "object" && typeof o.toJSON == "function") - return o.toJSON(compact); - - // Is it an array? - if (type != "function" && typeof(o.length) == "number") - { - var ret = []; - for (var i = 0; i < o.length; i++) { - ret.push( $.toJSON(o[i], compact) ); - } - if (compact) - return "[" + ret.join(",") + "]"; - else - return "[" + ret.join(", ") + "]"; - } - - // If it's a function, we have to warn somebody! - if (type == "function") { - throw new TypeError("Unable to convert object of type 'function' to json."); - } - - // It's probably an object, then. - var ret = []; - for (var k in o) { - var name; - type = typeof(k); - - if (type == "number") - name = '"' + k + '"'; - else if (type == "string") - name = $.quoteString(k); - else - continue; //skip non-string or number keys - - var val = $.toJSON(o[k], compact); - if (typeof(val) != "string") { - // skip non-serializable values - continue; - } - - if (compact) - ret.push(name + ":" + val); - else - ret.push(name + ": " + val); - } - return "{" + ret.join(", ") + "}"; - }; - - $.compactJSON = function(o) - { - return $.toJSON(o, true); - }; - - $.evalJSON = function(src) - // Evals JSON that we know to be safe. - { - return eval("(" + src + ")"); - }; - - $.secureEvalJSON = function(src) - // Evals JSON in a way that is *more* secure. - { - var filtered = src; - filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@'); - filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); - filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); - - if (/^[\],:{}\s]*$/.test(filtered)) - return eval("(" + src + ")"); - else - throw new SyntaxError("Error parsing JSON, source is not valid."); - }; -})(feincms.jQuery); diff --git a/feincms/static/feincms/js.cookie.js b/feincms/static/feincms/js.cookie.js new file mode 100644 index 000000000..d3eab9226 --- /dev/null +++ b/feincms/static/feincms/js.cookie.js @@ -0,0 +1,88 @@ +/*! js-cookie v3.0.1 | MIT */ +!((e, t) => { + "object" === typeof exports && "undefined" !== typeof module + ? (module.exports = t()) + : "function" === typeof define && define.amd + ? define(t) + : ((e = e || self), + (() => { + var n = e.Cookies, + o = (e.Cookies = t()) + o.noConflict = () => ((e.Cookies = n), o) + })()) +})(this, () => { + function e(e) { + for (var t = 1; t < arguments.length; t++) { + var n = arguments[t] + for (var o in n) e[o] = n[o] + } + return e + } + return (function t(n, o) { + function r(t, r, i) { + if ("undefined" !== typeof document) { + "number" === typeof (i = e({}, o, i)).expires && + (i.expires = new Date(Date.now() + 864e5 * i.expires)), + i.expires && (i.expires = i.expires.toUTCString()), + (t = encodeURIComponent(t) + .replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent) + .replace(/[()]/g, escape)) + var c = "" + for (var u in i) + i[u] && + ((c += `; ${u}`), !0 !== i[u] && (c += `=${i[u].split(";")[0]}`)) + return (document.cookie = `${t}=${n.write(r, t)}${c}`) + } + } + return Object.create( + { + set: r, + get: (e) => { + if ("undefined" !== typeof document && (!arguments.length || e)) { + for ( + var t = document.cookie ? document.cookie.split("; ") : [], + o = {}, + r = 0; + r < t.length; + r++ + ) { + var i = t[r].split("="), + c = i.slice(1).join("=") + try { + var u = decodeURIComponent(i[0]) + if (((o[u] = n.read(c, u)), e === u)) break + } catch (_e) {} + } + return e ? o[e] : o + } + }, + remove: (t, n) => { + r(t, "", e({}, n, { expires: -1 })) + }, + withAttributes: function (n) { + return t(this.converter, e({}, this.attributes, n)) + }, + withConverter: function (n) { + return t(e({}, this.converter, n), this.attributes) + }, + }, + { + attributes: { value: Object.freeze(o) }, + converter: { value: Object.freeze(n) }, + }, + ) + })( + { + read: (e) => ( + '"' === e[0] && (e = e.slice(1, -1)), + e.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent) + ), + write: (e) => + encodeURIComponent(e).replace( + /%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g, + decodeURIComponent, + ), + }, + { path: "/" }, + ) +}) diff --git a/feincms/static/feincms/style.css b/feincms/static/feincms/style.css deleted file mode 100644 index adc3cf488..000000000 --- a/feincms/static/feincms/style.css +++ /dev/null @@ -1,377 +0,0 @@ -#changelist { - top: 20px; -} - -.navi_tab { - float:left; - padding: 5px 14px 5px 12px; - cursor:pointer; - border: 1px solid #ccc; - border-bottom:none; - min-width:100px; - margin-top:3px; - height:13px; - font-weight: bold; - font-size: 12px; - background-image:none; - color: #333333; - background: #eee url(img/nav-bg.gif) top repeat-x; -} -.tab_active { - margin-top: 0; - padding-top: 6px; - padding-bottom: 7px; - background-image:url('img/default-bg.gif'); - color: #ffffff; -} - -#main { - clear:both; - padding: 10px 10px 10px 10px; - border: 1px solid #ccc; - margin: 0 0 10px 0; -} -.panel { - display:none; - position:relative; - padding-bottom:50px; -} - -.order-machine { - margin-bottom:10px; -} - -.order-item { - margin: 0 0 10px 0; - position:relative; -} - -.order-item .handle { - position: absolute; - top: 0; - left: 0; - display: block; - height: 24px; - width: 15px; - cursor: move; -} - -.order-item .collapse { - cursor: pointer; - color: #444; - font-weight: normal; -} - -.item-delete { - cursor:pointer; - float:right; - margin: 2px 0px 0px 0; -} - -.highlight, .helper { - height: 25px; - margin: 0 0 10px 0; - border: none; - opacity: 0.3; - background: url('img/item_editor_form_title.png') no-repeat left bottom; -} - -.helper{ - height: 25px !important; - opacity: 1; -} - -.button { - margin:5px; padding:5px; - font-weight: bold; - background-image:url('img/nav-bg.gif'); - cursor:pointer; - border: 1px solid #678; -} - -th { vertical-align: middle; } - -select { - max-width: 580px; -} - -#main_wrapper { - margin: 10px 0 30px 0; -} - -textarea { - width: 580px; - margin-top:5px; - margin-bottom:5px; -} - -.inline-group .tabular textarea { - width: auto; -} - -.item-minimize { - width: 15px; - height:15px; - float: left; - cursor: pointer; - margin-left:-17px; -} - -.item-minimize-disabled { - width: 15px; - height:15px; - float: left; - margin-left:-17px; -} - -.item-controls { - background: url('img/wrench.png') no-repeat 12px center; - padding-left:27px; - height: 35px; -} - -.item-control-unit { - float:left; - padding: 3px 10px 0 7px; - border-left: 1px solid #eee; -} - -.item-control-unit:first-child { - border-left: none; -} - -.machine-control { - height:70px; - padding: 5px 10px 5px 10px; - border: 1px solid #ccc; - background-color: #edf3fe; - position:absolute; - left:-11px; - bottom:-30px; - width: 100%; -} - -.control-unit { - float:left; - padding-left: 5px; - padding-right:20px; - border-left: 1px solid #eee; -} - -.control-unit:first-child { - border-left: none; -} - -.control-unit span { - font-weight:bold; -} - -a.actionbutton { - display: block; - background-repeat: no-repeat; - width:50px; - height:50px; - float:left; - margin: 5px 20px 0 0; - text-indent:-7000px; -} - -a.richtextcontent { background: url(img/contenttypes.png) no-repeat 0 0; } -a.richtextcontent:hover { background-position: 0 -70px; } - -a.imagecontent { background: url(img/contenttypes.png) no-repeat -70px 0; } -a.imagecontent:hover { background-position: -70px -70px; } - -a.gallerycontent { background: url(img/contenttypes.png) no-repeat -140px 0; } -a.gallerycontent:hover { background-position: -140px -70px; } - -a.oembedcontent { background: url(img/contenttypes.png) no-repeat -280px 0; } -a.oembedcontent:hover { background-position: -280px -70px; } - -a.pdfcontent { background: url(img/contenttypes.png) no-repeat -210px 0; } -a.pdfcontent:hover { background-position: -210px -70px; } - -a.audiocontent { background: url(img/contenttypes.png) no-repeat -350px 0; } -a.audiocontent:hover { background-position: -350px -70px; } - -.control-unit select, -.control-unit input { - position: relative; - top: 15px; -} - -.empty-machine-msg { - margin:10px 0px 5px 300px; - font-style: italic; - font-size:14px; - color:#999; -} - -td span select { - width:600px; -} - -#popup_bg { - width:100%; height:100%; - background-color:white; - opacity: 0.5; - filter:alpha(opacity=50); - position:absolute; - top:0px; left:0px; -} - -#sitetree-wrapper { - margin: 0; - padding: 20px 1px 0 1px; -} - -#sitetree { - width: 100%; -} - -#sitetree tr td { - overflow: hidden; - white-space: nowrap; -} - -.suchadrag img { - cursor: move; - float: right; - margin: 0 0 0 3px; -} - -.suchadrag a { - display: block; - margin-right: 28px; - overflow: hidden; -} - -.del-page { - cursor: pointer; - margin: 0 0 1px 0; -} - -tr.level0 td {background: #f4f4f4;} -tr.level1 td {background: #fafafa;} -tr.level2 td {background: #fdfdfd;} -tr.level0 td:first-child {padding-left: 5px;} -tr.level1 td:first-child {padding-left: 15px;} -tr.level2 td:first-child {padding-left: 25px;} -tr.level3 td:first-child {padding-left: 35px;} -tr.level4 td:first-child {padding-left: 45px;} -tr.level5 td:first-child {padding-left: 55px;} -tr.level6 td:first-child {padding-left: 65px;} -tr.level7 td:first-child {padding-left: 75px;} -tr.level8 td:first-child {padding-left: 85px;} -tr.level9 td:first-child {padding-left: 95px;} -tr.level10 td:first-child {padding-left: 105px;} -tr.level11 td:first-child {padding-left: 115px;} -tr.level12 td:first-child {padding-left: 125px;} -tr td:first-child div.expander {float: left; width: 15px; height: 10px;} -tr.parent td:first-child div.expander {background: url(img/toggle-expand-dark.png) no-repeat 0 -2px; cursor: pointer;} -tr.parent.expanded td:first-child div.expander {background-image: url(img/toggle-collapse-dark.png);} -tr td.hover {background-color: #abc;} -tr td.highlight {background-color: #8f8;} -tr td.nohighlight {background-color: #fff;} - - -/* jQuery alerts */ -#popup_container { - font-family: Arial, sans-serif; - font-size: 12px; - min-width: 300px; /* Dialog will be no smaller than this */ - max-width: 600px; /* Dialog will wrap after this width */ - background: #FFF; - border: solid 1px #666; - color: #000; -} - -#popup_title { - font-size: 14px; - font-weight: bold; - text-align: center; - line-height: 1.75em; - color: #666; - background: #eee url(img/title.gif) top repeat-x; - border: solid 1px #FFF; - border-bottom: solid 1px #666; - cursor: default; - padding: 0em; - margin: 0em; -} - -#popup_content { - background: 16px 16px no-repeat url(img/info.gif); - padding: 1em 1.75em; - margin: 0em; -} - -#popup_content.alert { - background-image: url(img/info.gif); -} - -#popup_content.confirm { - background-image: url(img/important.gif); -} - -#popup_content.prompt { - background-image: url(img/help.gif); -} - -#popup_message { - padding-left: 48px; -} - -#popup_panel { - text-align: center; - margin: 1em 0em 0em 1em; -} - -#popup_prompt { - margin: .5em 0em; -} - - -/* override Django admin styles */ -.order-machine fieldset.module > h2{ - padding: 4px 5px 6px 17px; - background: url(img/item_editor_form_title.png) no-repeat left bottom; -} - -.change-template-button { - margin-left: 7em; - padding-left: 30px; -} - -/* Allow nested lists in error items */ -ul.errorlist ul { - margin-left: 1em; - padding-left: 0; - list-style-type: square; -} - -ul.errorlist li li { - /* Avoid repeating the warning image every time*/ - background-image:none; - padding: 0; -} - -/* tree editor */ - -.page_marker { display:block; text-align: right; float: left; padding: 1px 1px 0 0; } -tr.cut { border: 2px dashed black; opacity: 0.5; } -.title-column { width: 400px; } - -table#result_list :focus { background-color: #ccddff !important; outline: 0px; } - -#page_form { - clear: both; -} - -div.order-machine div.inline-related > h3{ - display: none; -} - -.hidden-form-row{ - display: none; -} diff --git a/feincms/static/feincms/toolbox.js b/feincms/static/feincms/toolbox.js deleted file mode 100644 index e3635bed5..000000000 --- a/feincms/static/feincms/toolbox.js +++ /dev/null @@ -1,77 +0,0 @@ -/* Contains universally useful functions */ - -/* Extract an object id (numeric) from a DOM id. Assumes that a "-" is used - as delimiter. Returns either the id found or 0 if something went wrong. - - extract_item_id('foo_bar_baz-327') -> 327 - */ -var extract_item_id = function(elem_id) { - var i = elem_id.indexOf('-'); - if(i >= 0) - return parseInt(elem_id.slice(i+1)); - - return 0; -} - -/* Given an html snippet (in text form), parses it to extract the id attribute, - then replace the corresponding element in the page with the snippet. The - first parameter is ignored (so the signature matches what $.each expects). - - replace_element(0, '<div id="replace_me">New Stuff!</div>') - */ -var replace_element = function(i, html) { - var r_id = $(html).attr('id'); - $('#' + r_id).replaceWith(html); -} - -/* Same as above, but processes an array of html snippets */ -var replace_elements = function(data) { - $.each(data, replace_element); -} - -/* Setup django csrf-token for ajax calls, necessary since Django 1.2.5 */ -/* See http://docs.djangoproject.com/en/1.2/releases/1.2.5/#csrf-exception-for-ajax-requests */ - -$.ajaxSetup({ - beforeSend: function(xhr, settings) { - function getCookie(name) { - var cookieValue = null; - if (document.cookie && document.cookie != '') { - var cookies = document.cookie.split(';'); - for (var i = 0; i < cookies.length; i++) { - var cookie = jQuery.trim(cookies[i]); - // Does this cookie string begin with the name we want? - if (cookie.substring(0, name.length + 1) == (name + '=')) { - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); - break; - } - } - } - return cookieValue; - } - if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { - // Only send the token to relative URLs i.e. locally. - xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); - } - } -}); - -/* OnClick handler to toggle a boolean field via AJAX */ -var inplace_toggle_boolean = function(item_id, attr) { - $.ajax({ - url: ".", - type: "POST", - dataType: "json", - data: { '__cmd': 'toggle_boolean', 'item_id': item_id, 'attr': attr }, - - success: replace_elements, - - error: function(xhr, status, err) { - alert("Unable to toggle " + attr + ": " + xhr.responseText); - } - }); - - return false; -} - - diff --git a/feincms/static/feincms/tree_editor.css b/feincms/static/feincms/tree_editor.css new file mode 100644 index 000000000..408bd9bee --- /dev/null +++ b/feincms/static/feincms/tree_editor.css @@ -0,0 +1,91 @@ +/* tree editor */ + +.page_marker { + display: block; + text-align: right; + float: left; + padding: 1px 1px 0 0; +} +tr.cut { + border: 2px dashed black; + opacity: 0.5; +} +.title-column { + width: 400px; +} + +#result_list tbody tr:focus { + background-color: #79aec8 !important; + outline: 0px; + color: #ffffff; +} + +#result_list tbody tr:focus .field-indented_short_title a { + color: white; +} + +.drag_handle { + width: 16px; + height: 16px; + background: url(img/arrow-move.png); + display: inline-block; + vertical-align: middle; +} + +.drag_handle:hover { + cursor: move; +} + +.drag_order img { + cursor: move; +} + +table tr.dragging { + opacity: 0.3; +} + +table tr.dragging td { + color: #333; +} + +table tr.folded { + background: red; +} + +div#drag_line { + position: relative; + height: 3px; + font-size: 0px; + background-color: #417690; +} + +div#drag_line div { + position: absolute; + height: 9px; + width: 9px; + background: #417690; + margin: -3px 0 0 -9px; + border-radius: 9px; +} + +.page_marker.children { + background: url(img/disclosure-down.png) no-repeat right top; +} + +.page_marker.closed { + background: url(img/disclosure-right.png) no-repeat right top; +} + +tr.non-editable a:link, +tr.non-editable a:visited, +tr.non-editable td { + color: #aaa; +} + +tr.item_inactive { + color: rgb(221, 17, 68); +} + +tr.item_inactive th a { + color: rgb(221, 17, 68); +} diff --git a/feincms/static/feincms/tree_editor.js b/feincms/static/feincms/tree_editor.js new file mode 100644 index 000000000..941eac503 --- /dev/null +++ b/feincms/static/feincms/tree_editor.js @@ -0,0 +1,457 @@ +/* global Cookies, feincms */ + +// Suppress initial rendering of result list, but only if we can show it with +// JS later on. +document.write('<style type="text/css">#result_list { display: none }</style>') + +// https://docs.djangoproject.com/en/1.4/ref/contrib/csrf/ +feincms.jQuery.ajaxSetup({ + crossDomain: false, // obviates need for sameOrigin test + beforeSend(xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type)) { + xhr.setRequestHeader( + "X-CSRFToken", + document.querySelector('input[name="csrfmiddlewaretoken"]').value, + ) + } + }, +}) + +feincms.jQuery(($) => { + $(document.body).on("click", "[data-inplace]", function () { + const elem = $(this), + id = elem.data("inplace-id"), + attr = elem.data("inplace-attribute") + + $.ajax({ + url: ".", + type: "POST", + dataType: "json", + data: { + __cmd: "toggle_boolean", + item_id: id, + attr, + }, + success(data) { + $.each(data, (_i, html) => { + const r_id = $(html).attr("id") + $(`#${r_id}`).replaceWith(html) + }) + }, + + error(xhr, _status, _err) { + alert(`Unable to toggle ${attr}: ${xhr.responseText}`) + }, + }) + }) + + /* Extract an object id (numeric) from a DOM id. Assumes that a "-" is used + as delimiter. Returns either the id found or 0 if something went wrong. + + extractItemId('foo_bar_baz-327') -> 327 + */ + function extractItemId(elem_id) { + const i = elem_id.indexOf("-") + if (i >= 0) return parseInt(elem_id.slice(i + 1), 10) + + return 0 + } + + function isExpandedNode(id) { + return feincms.collapsed_nodes.indexOf(id) === -1 + } + + function markNodeAsExpanded(id) { + // remove itemId from array of collapsed nodes + const idx = feincms.collapsed_nodes.indexOf(id) + if (idx >= 0) feincms.collapsed_nodes.splice(idx, 1) + } + + function markNodeAsCollapsed(id) { + if (isExpandedNode(id)) feincms.collapsed_nodes.push(id) + } + + // toggle children + function doToggle(id, show) { + const children = feincms.tree_structure[id] || [] + for (let i = 0; i < children.length; ++i) { + const childId = children[i] + if (show) { + $(`#page_marker-${childId}`).closest("tr").show() + // only reveal children if current node is not collapsed + if (isExpandedNode(childId)) { + doToggle(childId, show) + } + } else { + $(`#page_marker-${childId}`).closest("tr").hide() + // always recursively hide children + doToggle(childId, show) + } + } + } + + function rowLevel($row) { + const level = + feincms.node_levels[extractItemId($row.find(".page_marker").attr("id"))] + return (level || 0) + 1 + } + + /* + * FeinCMS Drag-n-drop tree reordering. + * Based upon code by bright4 for Radiant CMS, rewritten for + * FeinCMS by Bjorn Post. + * + * September 2010 + * + */ + $.extend( + ($.fn.feinTree = function () { + $.each(feincms.tree_structure, (key, _value) => { + $(`#page_marker-${key}`).addClass("children") + }) + + $("div.drag_handle").bind("mousedown", (event) => { + const BEFORE = 0 + const AFTER = 1 + const CHILD = 2 + const CHILD_PAD = 20 + const originalRow = $(event.target).closest("tr") + const rowHeight = originalRow.height() + const moveTo = new Object() + + $("body") + .addClass("dragging") + .disableSelection() + .bind("mousemove", (event) => { + // attach dragged item to mouse + const cloned = originalRow.html() + if ($("#ghost").length === 0) { + $('<div id="ghost"></div>').appendTo("body") + } + $("#ghost") + .html(cloned) + .css({ + opacity: 0.8, + position: "absolute", + top: event.pageY, + left: event.pageX - 30, + width: 600, + }) + + // check on edge of screen + if ( + event.pageY + 100 > + $(window).height() + $(window).scrollTop() + ) { + $("html,body") + .stop() + .animate({ scrollTop: $(window).scrollTop() + 250 }, 500) + } else if (event.pageY - 50 < $(window).scrollTop()) { + $("html,body") + .stop() + .animate({ scrollTop: $(window).scrollTop() - 250 }, 500) + } + + // check if drag_line element already exists, else append + if ($("#drag_line").length < 1) { + $("body").append( + '<div id="drag_line" style="position:absolute">line<div></div></div>', + ) + } + + // loop trough all rows + $("tr", originalRow.parent()).each((_index, element) => { + element = $(element) + const top = element.offset().top + + // check if mouse is over a row + if (event.pageY >= top && event.pageY < top + rowHeight) { + let targetRow = null, + targetLoc = null, + elementLevel = rowLevel(element) + + if (event.pageY >= top && event.pageY < top + rowHeight / 3) { + targetRow = element + targetLoc = BEFORE + } else if ( + event.pageY >= top + rowHeight / 3 && + event.pageY < top + (rowHeight * 2) / 3 + ) { + const next = element.next() + // there's no point in allowing adding children when there are some already + // better move the items to the correct place right away + if (!next.length || rowLevel(next) <= elementLevel) { + targetRow = element + targetLoc = CHILD + } + } else if ( + event.pageY >= top + (rowHeight * 2) / 3 && + event.pageY < top + rowHeight + ) { + const next = element.next() + if (!next.length || rowLevel(next) <= elementLevel) { + targetRow = element + targetLoc = AFTER + } + } + + if (targetRow) { + const padding = + 37 + + rowLevel(element) * CHILD_PAD + + (targetLoc === CHILD ? CHILD_PAD : 0) + + $("#drag_line").css({ + width: targetRow.width() - padding, + left: targetRow.offset().left + padding, + top: + targetRow.offset().top + + (targetLoc === AFTER || targetLoc === CHILD + ? rowHeight + : 0) - + 1, + }) + + // Store the found row and options + moveTo.hovering = element + moveTo.relativeTo = targetRow + moveTo.side = targetLoc + + return true + } + } + }) + }) + + $("body").keydown((event) => { + if (event.which === "27") { + $("#drag_line").remove() + $("#ghost").remove() + $("body") + .removeClass("dragging") + .enableSelection() + .unbind("mousemove") + .unbind("mouseup") + event.preventDefault() + } + }) + + $("body").bind("mouseup", () => { + if (moveTo.relativeTo) { + const cutItem = extractItemId( + originalRow.find(".page_marker").attr("id"), + ) + const pastedOn = extractItemId( + moveTo.relativeTo.find(".page_marker").attr("id"), + ) + // get out early if items are the same + if (cutItem !== pastedOn) { + const isParent = + rowLevel(moveTo.relativeTo.next()) > rowLevel(moveTo.relativeTo) + + let position = "" + + // determine position + if (moveTo.side === CHILD && !isParent) { + position = "last-child" + } else if (moveTo.side === BEFORE) { + position = "left" + } else { + position = "right" + } + + // save + $.post( + ".", + { + __cmd: "move_node", + position, + cut_item: cutItem, + pasted_on: pastedOn, + }, + () => { + window.location.reload() + }, + ) + } else { + $("#drag_line").remove() + $("#ghost").remove() + } + $("body") + .removeClass("dragging") + .enableSelection() + .unbind("mousemove") + .unbind("mouseup") + } + }) + }) + + return this + }), + ) + + /* Every time the user expands or collapses a part of the tree, we remember + the current state of the tree so we can restore it on a reload. + Note: We might use html5's session storage? */ + function storeCollapsedNodes(nodes) { + const v = JSON.stringify(nodes) + Cookies.set("feincms_collapsed_nodes", v, { + expires: 7, + sameSite: "strict", + }) + } + + function retrieveCollapsedNodes() { + const collapse_cookie = Cookies.get("feincms_collapsed_nodes") + return collapse_cookie ? JSON.parse(collapse_cookie) : null + } + + function expandOrCollapseNode(item) { + let show = true + + if (!item.hasClass("children")) return + + const itemId = extractItemId(item.attr("id")) + + if (!isExpandedNode(itemId)) { + item.removeClass("closed") + markNodeAsExpanded(itemId) + } else { + item.addClass("closed") + show = false + markNodeAsCollapsed(itemId) + } + + storeCollapsedNodes(feincms.collapsed_nodes) + + doToggle(itemId, show) + } + + // bind the collapse all children event + $.extend( + ($.fn.bindCollapseTreeEvent = function () { + $(this).click(() => { + rlist = $("#result_list") + rlist.hide() + $("tbody tr", rlist).each((_i, el) => { + const marker = $(".page_marker", el) + if (marker.hasClass("children")) { + const itemId = extractItemId(marker.attr("id")) + doToggle(itemId, false) + marker.addClass("closed") + markNodeAsCollapsed(itemId) + } + }) + storeCollapsedNodes(feincms.collapsed_nodes) + rlist.show() + }) + return this + }), + ) + + // bind the open all children event + $.extend( + ($.fn.bindOpenTreeEvent = function () { + $(this).click(() => { + rlist = $("#result_list") + rlist.hide() + $("tbody tr", rlist).each((_i, el) => { + const marker = $("span.page_marker", el) + if (marker.hasClass("children")) { + const itemId = extractItemId($("span.page_marker", el).attr("id")) + doToggle(itemId, true) + marker.removeClass("closed") + markNodeAsExpanded(itemId) + } + }) + storeCollapsedNodes([]) + rlist.show() + }) + return this + }), + ) + + const changelist_tab = (elem, event, direction) => { + event.preventDefault() + elem = $(elem) + const ne = + direction > 0 + ? elem.nextAll(":visible:first") + : elem.prevAll(":visible:first") + if (ne) { + elem.attr("tabindex", -1) + ne.attr("tabindex", "0") + ne.focus() + } + } + + function keyboardNavigationHandler(event) { + // console.log('keydown', this, event.keyCode); + switch (event.keyCode) { + case 40: // down + changelist_tab(this, event, 1) + break + case 38: // up + changelist_tab(this, event, -1) + break + case 37: // left + case 39: // right + expandOrCollapseNode($(this).find(".page_marker")) + break + case 13: { + const where_to = extractItemId($("span", this).attr("id")) + document.location = `${document.location.pathname + where_to}/` + break + } + default: + break + } + } + + // fire! + let rlist = $("#result_list"), + rlist_tbody = rlist.find("tbody") + + if ($("tbody tr", rlist).length > 1) { + rlist.hide() + rlist_tbody.feinTree() + + rlist.find(".page_marker").on("click", function (event) { + event.preventDefault() + event.stopPropagation() + + expandOrCollapseNode($(this)) + }) + + $("#collapse_entire_tree").bindCollapseTreeEvent() + $("#open_entire_tree").bindOpenTreeEvent() + + // Disable things user cannot do anyway (object level permissions) + const non_editable_fields = $(".tree-item-not-editable", rlist).parents( + "tr", + ) + non_editable_fields.addClass("non-editable") + $("input:checkbox", non_editable_fields).attr("disabled", "disabled") + $("a:first", non_editable_fields).click((e) => { + e.preventDefault() + }) + $(".drag_handle", non_editable_fields).removeClass("drag_handle") + + /* Enable focussing, put focus on first result, add handler for keyboard navigation */ + $("tr", rlist).attr("tabindex", -1) + $("tbody tr:first", rlist).attr("tabindex", 0).focus() + $("tr", rlist).keydown(keyboardNavigationHandler) + + feincms.collapsed_nodes = [] + const storedNodes = retrieveCollapsedNodes() + if (storedNodes == null) { + $("#collapse_entire_tree").click() + } else { + for (let i = 0; i < storedNodes.length; i++) { + expandOrCollapseNode($(`#page_marker-${storedNodes[i]}`)) + } + } + } + + rlist.show() +}) diff --git a/feincms/templates/admin/content/mediafile/init.html b/feincms/templates/admin/content/mediafile/init.html deleted file mode 100644 index f487985b9..000000000 --- a/feincms/templates/admin/content/mediafile/init.html +++ /dev/null @@ -1,26 +0,0 @@ -{% load adminmedia i18n %} -<script type="text/javascript"> -(function($){ - contentblock_init_handlers.push(function(){ - $('.order-machine input[name$=mediafile], #frontend_editor input[name$=mediafile]').each(function(){ - var elem = $(this); - - elem.siblings('span').remove().end(); - elem.css('width', '50px').after("<span class=\"mediafile\" id=\"lookup_"+this.id+"\"> <img src=\"{% admin_media_prefix %}img/admin/selector-search.gif\" alt=\"{% trans "Search" %}\" /></span>"); - }); - }); - $(function(){ - $('span.mediafile').live('click', function(){ - var name = id_to_windowname(this.id.replace(/^lookup_/, '')); - var win = window.open('{% if frontend_editing %}../../{% endif %}../../../medialibrary/mediafile/?pop=1', name, 'height=500,width=800,resizable=yes,scrollbars=yes'); - win.focus(); - return false; - }); - }); -})(feincms.jQuery); -</script> -<style type="text/css"> -span.mediafile { - cursor: pointer; -} -</style> diff --git a/feincms/templates/admin/content/richtext/init_ckeditor.html b/feincms/templates/admin/content/richtext/init_ckeditor.html index 1d4361c38..b46164608 100644 --- a/feincms/templates/admin/content/richtext/init_ckeditor.html +++ b/feincms/templates/admin/content/richtext/init_ckeditor.html @@ -2,44 +2,45 @@ <style type="text/css"> span.cke_skin_kama { border: none !important; } - span.cke_wrapper { margin-left: 107px; } + div[id*='cke_id_'], span[id*='cke_id_'] { margin-left: 170px; } </style> <script type="text/javascript"> - {% block config %} + CKEDITOR.config.versionCheck = false; + {% block config %} CKEDITOR.config.width = '787'; CKEDITOR.config.height= '300'; - CKEDITOR.config.toolbar_Full = [ - {% block toolbar %}['Maximize','-','Format','-','Bold','Italic','Underline','Strike','-','Subscript','Superscript','-','NumberedList','BulletedList','-','Anchor', 'Link','Unlink','-','Source',],{% endblock %} + CKEDITOR.config.format_tags = 'p;h1;h2;h3;h4;pre'; + CKEDITOR.config.toolbar = [ + {% block toolbar %}['Maximize','-','Format','-','Bold','Italic','Underline','Strike','-','Subscript','Superscript','-','NumberedList','BulletedList','-','Anchor', 'Link','Unlink','-','Source']{% endblock %} ]; - {% endblock %} + {% endblock %} (function($){ + function feincms_richtext_remove_ckeditor(field) { + var id = field ? field.id : this.id; + if (id in CKEDITOR.instances) { + CKEDITOR.instances[id].destroy(); + } + } + + function feincms_richtext_add_ckeditor(field) { + var id = field ? field.id : this.id; + if (!(id in CKEDITOR.instances)) { + CKEDITOR.replace(id); + } + } contentblock_init_handlers.push(function(){ - $('.order-machine textarea[class=item-richtext], #frontend_editor textarea[class=item-richtext]').each(function(){ - feincms_richtext_add_ckeditor($(this)); + $('.order-machine textarea.item-richtext').each(function(){ + feincms_richtext_add_ckeditor(this); }); contentblock_move_handlers.poorify.push(function(item) { - item.find('textarea[class=item-richtext]').each(feincms_richtext_remove_ckeditor); + item.find('textarea.item-richtext').each(feincms_richtext_remove_ckeditor); }); contentblock_move_handlers.richify.push(function(item) { - item.find('textarea[class=item-richtext]').each(feincms_richtext_add_ckeditor); + item.find('textarea.item-richtext').each(feincms_richtext_add_ckeditor); }); }); })(feincms.jQuery); - - function feincms_richtext_remove_ckeditor(field) { - var id = field ? field.id : this.id; - if (id in CKEDITOR.instances) { - CKEDITOR.instances[id].destroy(); - } - } - - function feincms_richtext_add_ckeditor(field) { - var id = field ? field.id : this.id; - if (!(id in CKEDITOR.instances)) { - CKEDITOR.replace(id); - } - } </script> diff --git a/feincms/templates/admin/content/richtext/init_tinymce.html b/feincms/templates/admin/content/richtext/init_tinymce.html index c9059bec2..0b9e19d54 100644 --- a/feincms/templates/admin/content/richtext/init_tinymce.html +++ b/feincms/templates/admin/content/richtext/init_tinymce.html @@ -1,9 +1,15 @@ -{% if TINYMCE_JS_URL %} -<script type="text/javascript" src="{{ TINYMCE_JS_URL }}"></script> -{% endif %} +{% block tinymce_script %} + {% if TINYMCE_JS_URL %} + <script type="text/javascript" src="{{ TINYMCE_JS_URL }}"></script> + {% endif %} +{% endblock %} -<script type="text/javascript"> -{% block functions %}{% endblock %} +{% block tinymce_init %} + <script type="text/javascript"> + {% block functions %}{% endblock %} + {% if TINYMCE_DOMAIN %} + document.domain = '{{ TINYMCE_DOMAIN }}'; + {% endif %} tinyMCE.init({ mode: "none", @@ -49,21 +55,21 @@ } } - var richtext_init_fn = function(){ - $('{% block selectors %}.order-machine textarea[class=item-richtext], #frontend_editor textarea[class=item-richtext]{% endblock %}').each(function(){ + function richtext_init_fn() { + $('{% block selectors %}.order-machine textarea.item-richtext{% endblock %}').each(function(){ feincms_richtext_add_tinymce(this); }); } -{% block enable %} + + {% block enable %} contentblock_init_handlers.push(richtext_init_fn); contentblock_move_handlers.poorify.push(function(item) { - item.find('textarea[class=item-richtext]').each(feincms_richtext_remove_tinymce); + item.find('textarea.item-richtext').each(feincms_richtext_remove_tinymce); }); contentblock_move_handlers.richify.push(function(item) { - item.find('textarea[class=item-richtext]').each(feincms_richtext_add_tinymce); + item.find('textarea.item-richtext').each(feincms_richtext_add_tinymce); }); -{% endblock %} - + {% endblock %} })(feincms.jQuery); - -</script> + </script> +{% endblock %} diff --git a/feincms/templates/admin/content/richtext/init_tinymce4.html b/feincms/templates/admin/content/richtext/init_tinymce4.html new file mode 100644 index 000000000..9b4bf806b --- /dev/null +++ b/feincms/templates/admin/content/richtext/init_tinymce4.html @@ -0,0 +1,69 @@ +{% block tinymce_script %} + {% if TINYMCE_JS_URL %} + <script type="text/javascript" src="{{ TINYMCE_JS_URL }}"></script> + {% endif %} +{% endblock %} + +{% block tinymce_init %} + <script type="text/javascript"> + {% block functions %}{% endblock %} + {% if TINYMCE_DOMAIN %} + document.domain = '{{ TINYMCE_DOMAIN }}'; + {% endif %} + + (function($){ + var tinymce_added = {}; + var tinymce_options = { +{% block settings %} + height: '300', + {% if TINYMCE_CONTENT_CSS_URL %}content_css: "{{ TINYMCE_CONTENT_CSS_URL }}",{% endif %} + {% if TINYMCE_LINK_LIST_URL %}link_list: "{{ TINYMCE_LINK_LIST_URL }}",{% endif %} + plugins: "{% block plugins %}fullscreen paste link{% endblock %}", + paste_auto_cleanup_on_paste: true, + relative_urls: false, + invalid_elements: 'script', + statusbar: false, + menubar : false +{% endblock %} + }; + + function feincms_richtext_remove_tinymce(field) { + var id = field ? field.id : this.id; + if(tinymce_added[id]) { + tinyMCE.execCommand('mceRemoveEditor', false, id) + tinymce_added[id] = false; + } + } + + function feincms_richtext_add_tinymce(field) { + var id = field ? field.id : this.id; + if(!tinymce_added[id]) { + tinyMCE.execCommand('mceAddEditor', false, id) + tinymce_added[id] = true; + } + } + + function richtext_init_fn() { + tinyMCE.init(tinymce_options); + $('{% block selectors %}.order-machine textarea.item-richtext{% endblock %}').each(function(){ + feincms_richtext_add_tinymce(this); + }); + } + + {% block enable %} + contentblock_init_handlers.push(richtext_init_fn); + contentblock_move_handlers.poorify.push(function(item) { + try { + tinyMCE.triggerSave(); + } catch (error) { + alert("tinymce failed: " + error); + } + item.find('textarea.item-richtext').each(feincms_richtext_remove_tinymce); + }); + contentblock_move_handlers.richify.push(function(item) { + item.find('textarea.item-richtext').each(feincms_richtext_add_tinymce); + }); + {% endblock %} + })(feincms.jQuery); + </script> +{% endblock %} diff --git a/feincms/templates/admin/content/richtext/init_tinymce7.html b/feincms/templates/admin/content/richtext/init_tinymce7.html new file mode 100644 index 000000000..65d410738 --- /dev/null +++ b/feincms/templates/admin/content/richtext/init_tinymce7.html @@ -0,0 +1,41 @@ +{% block tinymce_script %} + <script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/7.6.0/tinymce.min.js" integrity="sha512-/4EpSbZW47rO/cUIb0AMRs/xWwE8pyOLf8eiDWQ6sQash5RP1Cl8Zi2aqa4QEufjeqnzTK8CLZWX7J5ZjLcc1Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> +{% endblock %} + +{% block tinymce_init %} + <script> + (() => { + const init = (field) => { + if (!field.hasAttribute("data-initialized")) { + tinymce.init({ + selector: `#${field.id}`, + menubar: false, + statusbar: false, + plugins: "anchor autoresize link", + autoresize_bottom_margin: 20, + toolbar: "styles | bold italic sub sup | bullist numlist | anchor link unlink", + }) + field.setAttribute("data-initialized", true) + } + } + + const destroy = (field) => { + tinymce.remove(`#${field.id}`) + field.removeAttribute("data-initialized") + } + + const selector = "textarea.item-richtext" + + contentblock_init_handlers.push(() => { + Array.from(document.querySelectorAll(`.order-machine ${selector}`)).forEach(init) + }) + + contentblock_move_handlers.poorify.push((item) => { + Array.from(item[0].querySelectorAll(selector)).forEach(destroy) + }) + contentblock_move_handlers.richify.push((item) => { + Array.from(item[0].querySelectorAll(selector)).forEach(init) + }) + })(); + </script> +{% endblock %} diff --git a/feincms/templates/admin/content/table/init.html b/feincms/templates/admin/content/table/init.html deleted file mode 100644 index f9734ac81..000000000 --- a/feincms/templates/admin/content/table/init.html +++ /dev/null @@ -1,247 +0,0 @@ -{% load adminmedia i18n %} -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.json-1.3.js"></script> -<script type="text/javascript"> -//<![CDATA[ - -/* - * Copyright (c) 2008 Greg Weber greg at gregweber.info - * Dual licensed under the MIT and GPL licenses - * - * jquery plugin - * make an html table editable by the user - * user clicks on a cell, edits the value, - * then presses enter or clicks on any cell to save the new value - * pressing escape returns the cell text to its orignal text - * - * documentation at http://gregweber.info/projects/uitableedit - * - * var t = $('table') - * $.uiTableEdit( t ) // returns t - * - * options : off, mouseDown, find, dataEntered, dataVerify, editDone - * off : turns off table editing - * find : defaults to tbody > tr > td - * mousedown : called in context of the table cell (as a normal event would be) - * if mouseDown returns false, cell will not become editable - * dataVerify : called in context of the cell, - * if dataVerify returns false, cell will stay in editable state - * if dataVerify returns text, that text will replace the cell's text - * arguments are the cell's text, original text, event, jquery object for the cell - * editDone : invoked on completion - * arguments: td cell's new text, original text, event, and jquery element for the td cell -*/ -(function($){ - $.uiTableEdit = function(jq, options){ - function unbind(){ - return jq.find( options.find ).unbind('mousedown.uiTableEdit') - } - options = options || {} - options.find = options.find || 'tbody > tr > td' - if( options.off ){ - unbind().find('form').each( function(){ var f = $(this); - f.parents("td:first").text( f.find(':text').attr('value') ); - f.remove(); - }); - return jq; - } - - function bind_mouse_down( mouseDn ){ - unbind().bind('mousedown.uiTableEdit', mouseDn ) - } - function td_edit(){ - var td = $(this); - - function restore(e){ - var val = td.find('textarea').val(); - if( options.dataVerify ){ - var value = options.dataVerify.call(this, val, orig_text, e, td); - if( value === false ){ return false; } - if( value !== null && value !== undefined ) val = value; - } - td.html( "" ); - td.text( val ); - if( options.editDone ) options.editDone(val,orig_text,e,td) - bind_mouse_down( td_edit_wrapper ); - } - - function checkEscape(e){ - if (e.keyCode === 27) { - td.html( "" ); - td.text( orig_text ); - bind_mouse_down( td_edit_wrapper ); - } else if (e.keyCode === 13 && !e.shiftKey) { - restore(e); - } - } - - var orig_text = td.text(); - var w = td.width(); - var h = td.height(); - td.html( '<form name="td-editor" action="javascript:void(0);">' + - '<textarea type="text" name="td_edit">' + td.text() + '</textarea></form>' ) - .find('form').submit( restore ).mousedown( restore ).blur( restore ).keypress( checkEscape ); - - var textarea = td.find('textarea'); - textarea.css('min-width', '150px').css('min-height', Math.max(150, textarea[0].scrollHeight)+'px'); - - function focus_text(){ td.find('textarea').get(0).focus() } - - // focus bug (seen in FireFox) fixed by small delay - setTimeout(focus_text, 50); - - /* TODO: investigate removing bind_mouse_down - I also got rid of bind_mouse_down(restore), - because now that you can refocus on fields that have been blurred, - you can have multiple edits going simultaneously - */ - bind_mouse_down( restore ); - } - - var td_edit_wrapper = !options.mouseDown ? td_edit : function(){ - if( options.mouseDown.apply(this,arguments) == false ) return false; - td_edit.apply(this,arguments); - }; - bind_mouse_down( td_edit_wrapper ); - return jq; - } - - function td_edit_editDone(value, original, event, td) { - var table = $(td).parents('table'); - var data = []; - table.find('tr').each(function(){ - var row = []; - $('td', this).each(function(){ - row.push($(this).html()); - }); - if(row.length) - data.push(row); - }); - - table.siblings('textarea').val($.toJSON(data)); - } - - contentblock_init_handlers.push(function(){ - $('.order-machine textarea[name*=tablecontent]:visible').each(function(){ - var textarea = $(this); - var data = $.secureEvalJSON(textarea.val() || '[[""]]'); - - var html = '<table class="tablecontent" border="1">'; - - html += '<tr><th></th>'; - for(var i=0; i<data[0].length; i++) { - html += '<th><span class="remove-col">-</span> column <span class="insert-col">+</span></th>'; - } - html += '</tr>'; - - for(var i=0; i<data.length; i++) { - html += '<tr>'; - html += '<th><span class="remove-row">-</span> row <span class="insert-row">+</span></th>'; - for(var j=0; j<data[i].length; j++) { - html += '<td>'+data[i][j]+'</td>'; - } - html += '</tr>'; - } - - html += '</table>'; - - var table = textarea.hide().after(html).siblings('table'); - - $.uiTableEdit(table, {editDone: td_edit_editDone}); - }); - }); - - $('table.tablecontent th span.insert-col').live('click', function(){ - var row = $(this.parentNode.parentNode); - var index = row.find('th').index(this.parentNode)-1; // first row has empty TH at the beginning - var cell = $(this.parentNode); - var table = row.parents('table'); - - cell.after(cell.clone()); - table.find('tbody tr').each(function(){ - $('td:eq('+index+')', this).after('<td></td>'); - }); - - $.uiTableEdit(table, {editDone: td_edit_editDone}); - return false; - }); - - $('table.tablecontent th span.remove-col').live('click', function(){ - var row = $(this.parentNode.parentNode); - var index = row.find('th').index(this.parentNode)-1; // first row has empty TH at the beginning - var cell = $(this.parentNode); - var table = row.parents('table'); - - // prevent removal of last column - if(table.find('tr:eq(0) th').length<=2) - return false; - - cell.remove(); - table.find('tbody tr').each(function(){ - $('td:eq('+index+')', this).remove(); - }); - - $.uiTableEdit(table, {editDone: td_edit_editDone}); - return false; - }); - - $('table.tablecontent th span.insert-row').live('click', function(){ - var row = $(this.parentNode.parentNode); - var table = row.parents('table'); - - row.after(row.clone()).next().find('td').empty(); - - $.uiTableEdit(table, {editDone: td_edit_editDone}); - return false; - }); - - $('table.tablecontent th span.remove-row').live('click', function(){ - var table = $(this).parents('table'); - - if(table.find('tr').length<=2) - return false; - - $(this.parentNode.parentNode).remove(); - - // TODO regenerate JSON - - return false; - }); -})(feincms.jQuery); -//]]> - -</script> -<style type="text/css"> -table.tablecontent { - width: 620px; -} - -table.tablecontent th { - font-weight: normal; - background: #eef; - text-align: center; -} - -table.tablecontent th span { - cursor: pointer; -} - -table.tablecontent td { - border: 1px solid #cccccc; - white-space: pre-wrap; -} - -table.tablecontent form { - border: 1px dashed #000; - white-space: normal; -} - -table.tablecontent form textarea { - width: auto; - border: 0; - margin-top: 0; - margin-bottom: 0; - margin: 0 0 0 0; - padding: 0 0 0 0; - background: inherit; -} -</style> diff --git a/feincms/templates/admin/feincms/_content_type_buttons.html b/feincms/templates/admin/feincms/_content_type_buttons.html index 0c41ef8c7..72bb12ada 100644 --- a/feincms/templates/admin/feincms/_content_type_buttons.html +++ b/feincms/templates/admin/feincms/_content_type_buttons.html @@ -1,3 +1,4 @@ +{% load feincms_admin_tags %} <script type="text/javascript"> /* type: content type identifier (required) @@ -6,32 +7,54 @@ after: callback, receives new fieldset as argument keep: keep entry in content type dropdown (defaults to false) */ -{% url admin:medialibrary_mediafile_changelist as media_library_url %} +{% url "admin:medialibrary_mediafile_changelist" as media_library_url %} var CONTENT_TYPE_BUTTONS = [ { - type: 'richtextcontent' + type: 'richtextcontent', + keep: true + }, { + type: 'imagecontent', + keep: true + }, { + type: 'filecontent', + keep: true, + cssclass: 'pdfcontent' + }, { + type: 'filerimagecontent', + keep: true, + cssclass: 'imagecontent' + }, { + type: 'filerfilecontent', + keep: true, + cssclass: 'pdfcontent' + }, { + type: 'videocontent', + keep: true, + cssclass: 'oembedcontent' {% if media_library_url %} }, { type: 'mediafilecontent', keep: true, cssclass: 'imagecontent', - raw_id_picker: '{{ media_library_url }}?type__exact=image&pop=1' + raw_id_picker: '{{ media_library_url }}?type__exact=image&_popup=1' }, { - type: 'gallerycontent' + type: 'gallerycontent', + keep: true }, { type: 'mediafilecontent', keep: true, cssclass: 'pdfcontent', - raw_id_picker: '{{ media_library_url }}?type__exact=pdf&pop=1' + raw_id_picker: '{{ media_library_url }}?type__exact=pdf&_popup=1' {% endif %} }, { - type: 'oembedcontent' + type: 'oembedcontent', + keep: true {% if media_library_url %} }, { type: 'mediafilecontent', keep: true, cssclass: 'audiocontent', - raw_id_picker: '{{ media_library_url }}?type__exact=audio&pop=1' + raw_id_picker: '{{ media_library_url }}?type__exact=audio&_popup=1' {% endif %} } ]; diff --git a/feincms/templates/admin/feincms/_messages_js.html b/feincms/templates/admin/feincms/_messages_js.html index c012182bc..542deffe3 100644 --- a/feincms/templates/admin/feincms/_messages_js.html +++ b/feincms/templates/admin/feincms/_messages_js.html @@ -1,18 +1,10 @@ {% load i18n %} <script type="text/javascript" charset="utf-8"> - DELETE_MESSAGES = ["{% trans "Really delete item?" %}", "{% trans "Confirm to delete item" %}", - "{% trans "Item deleted successfully." %}", "{% trans "Cannot delete item" %}", - "{% trans "Cannot delete item, because it is parent of at least one other item." %}"]; - CHANGE_TEMPLATE_MESSAGES = ["{% trans "Change template" %}", - "{% trans "Really change template? <br />All changes are saved." %}", - "{% trans "Really change template? <br />All changes are saved and content from <strong>%(source_regions)s</strong> is moved to <strong>%(target_region)s</strong>." %}"]; - - FEINCMS_ITEM_EDITOR_GETTEXT = { - 'Hide': "{% trans "Hide" %}" - , 'Show': "{% trans "Show" %}" - , 'After': "{% trans "After" %}" - , 'Before': "{% trans "Before" %}" - , 'Insert new:': "{% trans "Insert new:" %}" - }; + var FEINCMS_ITEM_EDITOR_GETTEXT = { + 'DELETE_MESSAGE': "{% trans 'Really delete item?' %}", + 'CHANGE_TEMPLATE': "{% trans 'Really change template? All changes are saved.' %}", + 'CHANGE_TEMPLATE_WITH_MOVE': "{% trans 'Really change template? All changes are saved and content from %(source_regions)s is moved to %(target_region)s.' %}", + 'MOVE_TO_REGION': "{% trans 'Move to region:' %}" + }; </script> diff --git a/feincms/templates/admin/feincms/_regions_js.html b/feincms/templates/admin/feincms/_regions_js.html index 6bf6e6d5a..6e90dfecb 100644 --- a/feincms/templates/admin/feincms/_regions_js.html +++ b/feincms/templates/admin/feincms/_regions_js.html @@ -1,9 +1,10 @@ +{% load static %} <script type="text/javascript" charset="utf-8"> -IMG_DELETELINK_PATH = "{{ FEINCMS_ADMIN_MEDIA }}img/icon_deletelink.gif"; +window.IMG_DELETELINK_PATH = "{% static 'feincms/img/icon_deletelink.gif' %}"; -REGION_MAP = [{% for region in original.template.regions %}'{{ region.key }}'{% if not forloop.last %}, {% endif %}{% endfor %}]; -REGION_NAMES = [{% for region in original.template.regions %}'{{ region.title|capfirst }}'{% if not forloop.last %}, {% endif %}{% endfor %}]; -ACTIVE_REGION = 0; +window.REGION_MAP = [{% for region in original.template.regions %}'{{ region.key }}'{% if not forloop.last %}, {% endif %}{% endfor %}]; +window.REGION_NAMES = [{% for region in original.template.regions %}'{{ region.title|capfirst }}'{% if not forloop.last %}, {% endif %}{% endfor %}]; +window.ACTIVE_REGION = 0; CONTENT_NAMES = { {% for name, value in content_types %}'{{ value }}': '{{ name|capfirst }}'{% if not forloop.last %},{% endif %} diff --git a/feincms/templates/admin/feincms/content_editor.html b/feincms/templates/admin/feincms/content_editor.html index 6f4abb198..29172e20f 100644 --- a/feincms/templates/admin/feincms/content_editor.html +++ b/feincms/templates/admin/feincms/content_editor.html @@ -1,14 +1,24 @@ {% load i18n %} -{% load feincms_tags %} -<div id="main_wrapper"> +{% load feincms_admin_tags %} +<div id="feincmsmain_wrapper"> {% for region in original.template.regions %} <div class="navi_tab" id="{{ region.key }}_tab">{{ region.title }}</div> {% endfor %} - <div id="main"> + <div id="feincmsmain"> {% for region in original.template.regions %} <div id="{{ region.key }}_body" class="panel"> <div class="empty-machine-msg"> - {% trans "Region empty" %} + + {% if adding_translation and forloop.first %} + <div> + <p> + <input type="checkbox" name="_copy_content_from_original" id="_copy_content_from_original" checked="checked" /> <label for="_copy_content_from_original">{% trans "Copy content from the original" %} ("{{ translation_of }}", {% trans translation_of.get_language_display %})</label> + </p> + </div> + {% else %} + {% trans "Region empty" %} + {% endif %} + </div> <div class="empty-machine-msg" style="margin-left:20px; margin-top:20px;"> {% if region.inherited %} @@ -20,9 +30,7 @@ <div class="machine-control"> <div class="control-unit"> - <span>{% trans "Add new item" %}:</span> <br/> {% show_content_type_selection_widget region %} - <input type="button" class="order-machine-add-button button" value="Go" /> </div> </div> </div> diff --git a/feincms/templates/admin/feincms/content_inline.html b/feincms/templates/admin/feincms/content_inline.html index 267a0950d..10b5839cb 100644 --- a/feincms/templates/admin/feincms/content_inline.html +++ b/feincms/templates/admin/feincms/content_inline.html @@ -1,106 +1,26 @@ -{% load i18n adminmedia feincms_admin_tags %} -<div class="inline-group feincms_inline" id="{{ inline_admin_formset.formset.prefix }}-group"> - <h2>{{ inline_admin_formset.opts.verbose_name_plural|title }}</h2> +{% load i18n admin_urls static feincms_admin_tags %} +<div class="js-inline-admin-formset inline-group feincms_inline" + id="{{ inline_admin_formset.formset.prefix }}-group" + data-inline-type="stacked" + data-inline-formset="{{ inline_admin_formset.inline_formset_data }}"> +<fieldset class="module {{ inline_admin_formset.classes }}"> + <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2> {{ inline_admin_formset.formset.management_form }} {{ inline_admin_formset.formset.non_form_errors }} -{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if forloop.last %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}"> - <h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b> <span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %}</span> - {% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %} +{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if forloop.last and inline_admin_formset.has_add_permission %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}"> + <h3><b>{{ inline_admin_formset.opts.verbose_name|capfirst }}:</b> <span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %} <a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="{% if inline_admin_formset.has_change_permission %}inlinechangelink{% else %}inlineviewlink{% endif %}">{% if inline_admin_formset.has_change_permission %}{% trans "Change" %}{% else %}{% trans "View" %}{% endif %}</a>{% endif %} +{% else %}#{{ forloop.counter }}{% endif %}</span> + {% if inline_admin_form.show_url %}<a href="{{ inline_admin_form.absolute_url }}">{% trans "View on site" %}</a>{% endif %} {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %} </h3> {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %} {% for fieldset in inline_admin_form %} - <fieldset class="module aligned {{ fieldset.classes }}"> - {% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %} - {% if fieldset.description %} - <div class="description">{{ fieldset.description|safe }}</div> - {% endif %} - {% for line in fieldset|post_process_fieldsets %} - <div class="form-row{% if line.errors %} errors{% endif %}{% for field in line %} {{ field.field.name }}{% endfor %}"> - {{ line.errors }} - {% for field in line %} - <div{% if not line.fields|length_is:"1" %} class="field-box"{% endif %}> - {% if field.is_checkbox %} - {{ field.field }}{{ field.label_tag }} - {% else %} - {{ field.label_tag }} - {% if field.is_readonly %} - <p>{{ field.contents }}</p> - {% else %} - {{ field.field }} - {% endif %} - {% endif %} - {% if field.field.field.help_text %} - <p class="help">{{ field.field.field.help_text|safe }}</p> - {% endif %} - </div> - {% endfor %} - </div> - {% endfor %} - </fieldset> + {% post_process_fieldsets fieldset %} + {% include "admin/includes/fieldset.html" %} {% endfor %} - {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %} + {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %} {{ inline_admin_form.fk_field.field }} </div>{% endfor %} +</fieldset> </div> - -<script type="text/javascript"> -(function($) { - $(document).ready(function() { - var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related"; - var updateInlineLabel = function(row) { - $(rows).find(".inline_label").each(function(i) { - var count = i + 1; - $(this).html($(this).html().replace(/(#\d+)/g, "#" + count)); - }); - } - var reinitDateTimeShortCuts = function() { - // Reinitialize the calendar and clock widgets by force, yuck. - if (typeof DateTimeShortcuts != "undefined") { - $(".datetimeshortcuts").remove(); - DateTimeShortcuts.init(); - } - } - var updateSelectFilter = function() { - // If any SelectFilter widgets were added, instantiate a new instance. - if (typeof SelectFilter != "undefined"){ - $(".selectfilter").each(function(index, value){ - var namearr = value.name.split('-'); - SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% admin_media_prefix %}"); - }) - $(".selectfilterstacked").each(function(index, value){ - var namearr = value.name.split('-'); - SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% admin_media_prefix %}"); - }) - } - } - var initPrepopulatedFields = function(row) { - row.find('.prepopulated_field').each(function() { - var field = $(this); - var input = field.find('input, select, textarea'); - var dependency_list = input.data('dependency_list') || []; - var dependencies = row.find(dependency_list.join(',')).find('input, select, textarea'); - if (dependencies.length) { - input.prepopulate(dependencies, input.attr('maxlength')); - } - }); - } - $(rows).formset({ - prefix: "{{ inline_admin_formset.formset.prefix }}", - addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}", - formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}", - deleteCssClass: "inline-deletelink", - deleteText: "{% trans "Remove" %}", - emptyCssClass: "empty-form", - removed: updateInlineLabel, - added: (function(row) { - initPrepopulatedFields(row); - reinitDateTimeShortCuts(); - updateSelectFilter(); - updateInlineLabel(row); - }) - }); - }); -})(django.jQuery); -</script> diff --git a/feincms/templates/admin/feincms/content_type_selection_widget.html b/feincms/templates/admin/feincms/content_type_selection_widget.html index 0daf6633d..33b8553aa 100644 --- a/feincms/templates/admin/feincms/content_type_selection_widget.html +++ b/feincms/templates/admin/feincms/content_type_selection_widget.html @@ -1,8 +1,11 @@ +{% load i18n %} <select name="order-machine-add-select"> - {% for v,n in ungrouped %}<option value="{{ v }}">{{ n|capfirst }}</option> {% endfor %} - {% for label,cts in grouped.items %} - <optgroup label="{{ label }}"> - {% for v,n in cts %}<option value="{{ v }}">{{ n|capfirst }}</option> {% endfor %} - </optgroup> - {% endfor %} + <option selected value>{% trans 'Insert new content:' %}</option> + {% for group, cts in types.items %} + {% if group %}<optgroup label="{{ group }}">{% endif %} + {% for v, n in cts %} + <option value="{{ v }}">{{ n|capfirst }}</option> + {% endfor %} + {% if group %}</optgroup>{% endif %} + {% endfor %} </select> diff --git a/feincms/templates/admin/feincms/fe_box.html b/feincms/templates/admin/feincms/fe_box.html deleted file mode 100644 index 9d08e3b61..000000000 --- a/feincms/templates/admin/feincms/fe_box.html +++ /dev/null @@ -1 +0,0 @@ -<div class="fe_box" id="{{ identifier }}">{{ content|safe }}</div> diff --git a/feincms/templates/admin/feincms/fe_editor.html b/feincms/templates/admin/feincms/fe_editor.html deleted file mode 100644 index e817756dc..000000000 --- a/feincms/templates/admin/feincms/fe_editor.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends "admin/change_form.html" %} -{% load feincms_compat_tags i18n admin_modify adminmedia %} - - -{% block extrahead %}{{ block.super }} -{% if FEINCMS_ADMIN_MEDIA_HOTLINKING %} -<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script> -<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"></script> -{% else %} -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery-1.5.1.min.js"></script> -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery-ui-1.8.13.custom.min.js"></script> -{% endif %} - -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.alerts.js"></script> -<script type="text/javascript"> - var feincms = { - {% if FEINCMS_JQUERY_NO_CONFLICT %} - "jQuery": jQuery.noConflict(true) - {% else %} - "jQuery": jQuery - {% endif %} - }; -</script> -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}item_editor.js"></script> - -{% include "admin/feincms/_messages_js.html" %} -{% include "admin/feincms/_regions_js.html" %} - -{% for inc in object.feincms_item_editor_includes.head %}{% include inc %}{% endfor %} - -{% endblock %} - -{% block content %} -<div class="content_main" id="frontend_editor"> - -<form method="post" action="."> -{% csrf_token %} -<div> -{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %} - - <table> - {{ form }} - </table> - - <div class="submit-row" > - <input type="submit" value="{% trans "Save" %}" class="default" name="_save" /> - </div> - -</div> -</form> - -</div> -{% endblock %} diff --git a/feincms/templates/admin/feincms/fe_editor_done.html b/feincms/templates/admin/feincms/fe_editor_done.html deleted file mode 100644 index 24d9c2fd8..000000000 --- a/feincms/templates/admin/feincms/fe_editor_done.html +++ /dev/null @@ -1,27 +0,0 @@ -{% if FEINCMS_ADMIN_MEDIA_HOTLINKING %} -<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script> -<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"></script> -{% else %} -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery-1.5.1.min.js"></script> -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery-ui-1.8.13.custom.min.js"></script> -{% endif %} -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.color.js"></script> -<script type="text/javascript"> - var feincms = { - {% if FEINCMS_JQUERY_NO_CONFLICT %} - "jQuery": jQuery.noConflict(true) - {% else %} - "jQuery": jQuery - {% endif %} - }; -</script> - -<div id="{{ identifier }}-new"> - {{ content|safe }} -</div> - -<script type="text/javascript"> -opener.feincms.fe_update_content('{{ identifier }}', feincms.jQuery('#{{ identifier }}-new').html()); - -window.close(); -</script> diff --git a/feincms/templates/admin/feincms/fe_tools.html b/feincms/templates/admin/feincms/fe_tools.html deleted file mode 100644 index ee6386d14..000000000 --- a/feincms/templates/admin/feincms/fe_tools.html +++ /dev/null @@ -1,39 +0,0 @@ -{% load i18n %} - -<script type="text/javascript" charset="utf-8"> -(function () { - // load jQuery if not yet present - if (typeof(feincms) == "undefined") { - var jquery_url = {% if FEINCMS_ADMIN_MEDIA_HOTLINKING %}"http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"{% else %}"{{ FEINCMS_ADMIN_MEDIA }}jquery-1.5.1.min.js"{% endif %}; - document.write(unescape('%3Cscript src="' + jquery_url + '" type="text/javascript"%3E%3C/script%3E')); - } -})(); -</script> -<script type="text/javascript"> - var feincms = { - "admin_index": "{% url admin:index %}", - {% if FEINCMS_JQUERY_NO_CONFLICT %} - "jQuery": jQuery.noConflict(true) - {% else %} - "jQuery": jQuery - {% endif %} - }; -</script> - -<script src="{{ FEINCMS_ADMIN_MEDIA }}/frontend_editing.js" type="text/javascript" charset="utf-8"></script> -<link rel="stylesheet" href="{{ FEINCMS_ADMIN_MEDIA }}/frontend_editing.css" type="text/css" media="screen" charset="utf-8"> - -<div id="fe_controls"> - <a href="?frontend_editing=0">{% trans "Stop Editing" %}</a> - <a href="{% url admin:page_page_change feincms_page.pk %}">Admin Page</a> -</div> - -<div id="fe_tools"> - <a id="fe_tools_edit" href="#">{% trans "edit" %}</a> - <!-- - <a id="fe_tools_new" href="#">{% trans "new" %}</a> - <a id="fe_tools_up" href="#">{% trans "up" %}</a> - <a id="fe_tools_down" href="#">{% trans "down" %}</a> - <a id="fe_tools_remove" href="#">{% trans "remove" %}</a> - --> -</div> diff --git a/feincms/templates/admin/feincms/item_editor.html b/feincms/templates/admin/feincms/item_editor.html index 6a4764c0b..0f713a7ed 100644 --- a/feincms/templates/admin/feincms/item_editor.html +++ b/feincms/templates/admin/feincms/item_editor.html @@ -1,29 +1,13 @@ {% extends "admin/change_form.html" %} -{% load feincms_compat_tags i18n admin_modify adminmedia %} +{% load i18n admin_modify admin_urls static %} {% block extrahead %}{{ block.super }} {% block feincms_jquery_ui %} -{% if FEINCMS_ADMIN_MEDIA_HOTLINKING %} -<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script> -<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"></script> -{% else %} -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery-1.5.1.min.js"></script> -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery-ui-1.8.13.custom.min.js"></script> -{% endif %} +{% include "admin/feincms/load-jquery.include" %} {% endblock %} -<link rel="stylesheet" type="text/css" href="{{ FEINCMS_ADMIN_MEDIA }}style.css" /> -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.alerts.js"></script> -<script type="text/javascript"> - var feincms = { - {% if FEINCMS_JQUERY_NO_CONFLICT %} - "jQuery": jQuery.noConflict(true) - {% else %} - "jQuery": jQuery - {% endif %} - }; -</script> -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}item_editor.js"></script> +<link rel="stylesheet" type="text/css" href="{% static 'feincms/item_editor.css' %}"> +<script type="text/javascript" src="{% static 'feincms/item_editor.js' %}"></script> {% include "admin/feincms/_messages_js.html" %} {% include "admin/feincms/_regions_js.html" %} @@ -39,13 +23,16 @@ {% endblock %} {% block form_top %} - {% with original.available_translations as translations %} - {% if translations %} +{% with original.available_translations as translations %} +{% for translation in translations %} +{# look for something the translation extension's available_translations method might return #} +{% if translation.id %} + {% if forloop.first %} <div style="float: right;padding: 5px 0 0 0" id="available_translations"> {% trans "available translations" %}: - {% for translation in translations %} - <a href="../{{ translation.id }}/" title="{{ translation }}">{{ translation.language|upper }}</a>{% if not forloop.last %},{% endif %} - {% endfor %} + {% endif %} + <a href="{% url opts|admin_urlname:'change' translation.id|admin_urlquote %}" title="{{ translation }}">{{ translation.language|upper }}</a>{% if not forloop.last %},{% endif %} + {% if forloop.last %} </div> <script type="text/javascript"> feincms.jQuery(function($){ @@ -53,7 +40,8 @@ }); </script> {% endif %} - {% endwith %} +{% endif %}{% endfor %} +{% endwith %} {% endblock %} {% block after_related_objects %} {# as good a place as any... #} @@ -63,7 +51,7 @@ </div> <script type="text/javascript"> feincms.jQuery(function($){ - $("h2:contains('{{ FEINCMS_CONTENT_FIELDSET_NAME }}')").parent().replaceWith($("#main_wrapper")); + $("h2:contains('{{ FEINCMS_CONTENT_FIELDSET_NAME }}')").parent().replaceWith($("#feincmsmain_wrapper")); }); </script> {% endblock %} diff --git a/feincms/templates/admin/feincms/load-jquery.include b/feincms/templates/admin/feincms/load-jquery.include new file mode 100644 index 000000000..ac1c8b808 --- /dev/null +++ b/feincms/templates/admin/feincms/load-jquery.include @@ -0,0 +1,12 @@ +{% load static %} +{% comment %} +Include jquery, override this template if you want to use a cdn version +or load more plugins or whatnot +{% endcomment %} + +<script type="text/javascript" src="{% static 'feincms/jquery-1.11.3.min.js' %}"></script> +<script type="text/javascript" src="{% static 'feincms/jquery-ui-1.10.3.custom.min.js' %}"></script> + +<script type="text/javascript"> +var feincms = {jQuery: jQuery.noConflict(true)}; +</script> diff --git a/feincms/templates/admin/feincms/page/page/item_editor.html b/feincms/templates/admin/feincms/page/page/item_editor.html index c52219b11..94ce86d22 100644 --- a/feincms/templates/admin/feincms/page/page/item_editor.html +++ b/feincms/templates/admin/feincms/page/page/item_editor.html @@ -5,11 +5,8 @@ {% block object-tools %} {{ block.super }} <ul class="extra-object-tools"> - {% if original.is_active and FEINCMS_FRONTEND_EDITING %} - <li><a target="_blank" href="{{ original.get_absolute_url }}?frontend_editing=1" class="frontend_editing_link">{% trans "Edit on site" %}</a></li> - {% endif %} {% if not original.is_active %} - {% url feincms_preview original.get_absolute_url|slice:"1:-1" original.pk as preview_url %} + {% url "feincms_preview" original.get_absolute_url|slice:"1:-1" original.pk as preview_url %} {% if preview_url %} <li><a target="_blank" href="{{ preview_url }}" class="viewsitelink">{% trans "Preview" %}</a></li> {% endif %} @@ -19,14 +16,14 @@ {% block breadcrumbs %} <div class="breadcrumbs"> - <a href="../../../">{% trans "Home" %}</a> › - <a href="../">{{ opts.verbose_name_plural|capfirst }}</a> › + <a href="{% url 'admin:index' %}">{% trans "Home" %}</a> › + <a href="{% url 'admin:page_page_changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a> › {% if add %} {% trans "Add" %} {{ opts.verbose_name }} {% else %} {% for p in original.get_ancestors %} - <a href="../{{ p.id }}/">{{ p.short_title }}</a> : + <a href="{% url 'admin:page_page_change' p.id %}">{{ p.short_title }}</a> : {% endfor %} {{ original.short_title }} {% endif %} diff --git a/feincms/templates/admin/feincms/recover_form.html b/feincms/templates/admin/feincms/recover_form.html index dea0279c0..250d7bac1 100644 --- a/feincms/templates/admin/feincms/recover_form.html +++ b/feincms/templates/admin/feincms/recover_form.html @@ -1,18 +1,22 @@ {% extends "admin/feincms/item_editor.html" %} -{% load i18n %} +{% load i18n admin_urls %} {% block breadcrumbs %} <div class="breadcrumbs"> - <a href="{% url admin:index %}">{% trans "Home" %}</a> › - <a href="{% url admin:app_list app_label %}">{{app_label|capfirst|escape}}</a> › - <a href="{{changelist_url}}">{{opts.verbose_name_plural|capfirst}}</a> › - <a href="{{recoverlist_url}}">{% blocktrans with opts.verbose_name as verbose_name %}Recover deleted {{verbose_name}}{% endblocktrans %}</a> › + <a href="{% url 'admin:index' %}">{% trans "Home" %}</a> › + <a href="{% url 'admin:app_list' opts.app_label %}">{{opts.app_config.verbose_name}}</a> › + <a href="{% url opts|admin_urlname:'changelist' %}">{{opts.verbose_name_plural|capfirst}}</a> › + <a href="{% url opts|admin_urlname:'recoverlist' %}">{% blocktrans with opts.verbose_name as verbose_name %}Recover deleted {{verbose_name}}{% endblocktrans %}</a> › {{title}} </div> {% endblock %} +{% block object-tools %}{% endblock %} {% block form_top %} <p>{% blocktrans %}Press the save button below to recover this version of the object.{% endblocktrans %}</p> {% endblock %} + +{% block submit_buttons_top %}{% with is_popup=1 %}{{block.super}}{% endwith %}{% endblock %} +{% block submit_buttons_bottom %}{% with is_popup=1 %}{{block.super}}{% endwith %}{% endblock %} diff --git a/feincms/templates/admin/feincms/revision_form.html b/feincms/templates/admin/feincms/revision_form.html index dcae1a4fd..92cbd18ff 100644 --- a/feincms/templates/admin/feincms/revision_form.html +++ b/feincms/templates/admin/feincms/revision_form.html @@ -1,30 +1,24 @@ {% extends "admin/feincms/item_editor.html" %} -{% load i18n reversion_admin %} - - -{% block extrahead %}{{block.super|fix_jsi18n}}{% endblock %} +{% load i18n admin_urls %} +{# override of breadcrumbs only required up to Reversion 2.0 #} {% block breadcrumbs %} <div class="breadcrumbs"> - <a href="{% url admin:index %}">{% trans "Home" %}</a> › - <a href="{% url admin:app_list app_label %}">{{app_label|capfirst|escape}}</a> › - <a href="{{changelist_url}}">{{opts.verbose_name_plural|capfirst}}</a> › - <a href="{{change_url}}">{{original|truncatewords:"18"}}</a> › - <a href="../">{% trans "History" %}</a> › + <a href="{% url 'admin:index' %}">{% trans "Home" %}</a> › + <a href="{% url 'admin:app_list' opts.app_label %}">{{opts.app_config.verbose_name}}</a> › + <a href="{% url opts|admin_urlname:'changelist' %}">{{opts.verbose_name_plural|capfirst}}</a> › + <a href="{% url opts|admin_urlname:'change' object_id %}">{{original|truncatewords:"18"}}</a> › + <a href="{% url opts|admin_urlname:'history' object_id %}">{% trans "History" %}</a> › {% blocktrans with opts.verbose_name as verbose_name %}Revert {{verbose_name}}{% endblocktrans %} </div> {% endblock %} - -{% block content %} - {% with 1 as is_popup %} - {{block.super}} - {% endwith %} -{% endblock %} - +{% block object-tools %}{% endblock %} {% block form_top %} <p>{% blocktrans %}Press the save button below to revert to this version of the object.{% endblocktrans %}</p> {% endblock %} +{% block submit_buttons_top %}{% with is_popup=1 %}{{block.super}}{% endwith %}{% endblock %} +{% block submit_buttons_bottom %}{% with is_popup=1 %}{{block.super}}{% endwith %}{% endblock %} diff --git a/feincms/templates/admin/feincms/tree_editor.html b/feincms/templates/admin/feincms/tree_editor.html index 35f3bfa58..c31cdfc1d 100644 --- a/feincms/templates/admin/feincms/tree_editor.html +++ b/feincms/templates/admin/feincms/tree_editor.html @@ -1,47 +1,32 @@ {% extends "admin/change_list.html" %} -{% load adminmedia admin_list i18n %} +{% load admin_list i18n static %} {% block extrahead %} {{ block.super }} -<link rel="stylesheet" type="text/css" href="{{ FEINCMS_ADMIN_MEDIA }}style.css" /> -<link rel="stylesheet" type="text/css" href="{{ FEINCMS_ADMIN_MEDIA }}fein_tree.css" /> +<link rel="stylesheet" type="text/css" href="{% static 'feincms/tree_editor.css' %}"> -{% if FEINCMS_ADMIN_MEDIA_HOTLINKING %} -<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script> -<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"></script> -{% else %} -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery-1.5.1.min.js"></script> -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery-ui-1.8.13.custom.min.js"></script> -{% endif %} +{% include "admin/feincms/load-jquery.include" %} <script type="text/javascript"> - var feincms = { - {% if FEINCMS_JQUERY_NO_CONFLICT %} - "jQuery": jQuery.noConflict(true) - {% else %} - "jQuery": jQuery - {% endif %} - , "tree_structure": {{ tree_structure|default:"null" }} - }; + feincms.tree_structure = JSON.parse('{{ tree_structure|default:"{}" }}'); + feincms.node_levels = JSON.parse('{{ node_levels|default:"{}" }}'); </script> -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}ie_compat.js"></script> -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.cookie.js"></script> -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}toolbox.js"></script> -<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}fein_tree.js"></script> +<script type="text/javascript" src="{% static 'feincms/js.cookie.js' %}"></script> +<script type="text/javascript" src="{% static 'feincms/tree_editor.js' %}"></script> {% endblock %} {% block filters %} - {% if cl.has_filters %} - <div id="changelist-filter"> - <h2>{% trans 'Shortcuts' %}</h2> - <ul> - <li><a id="collapse_entire_tree" href="#">{% trans 'Collapse tree' %}</a></li> - <li><a id="open_entire_tree" href="#">{% trans 'Expand tree' %}</a></li> - </ul> + <div id="changelist-filter"> + <h2>{% trans 'Shortcuts' %}</h2> + <ul> + <li><a id="collapse_entire_tree" href="#">{% trans 'Collapse tree' %}</a></li> + <li><a id="open_entire_tree" href="#">{% trans 'Expand tree' %}</a></li> + </ul> - <h2>{% trans 'Filter' %}</h2> - {% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %} - </div> - {% endif %} + {% if cl.filter_specs %} + <h2>{% trans 'Filter' %}</h2> + {% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %} + {% endif %} + </div> {% endblock %} diff --git a/feincms/templates/admin/filter.html b/feincms/templates/admin/filter.html index 937698810..354447388 100644 --- a/feincms/templates/admin/filter.html +++ b/feincms/templates/admin/filter.html @@ -20,4 +20,4 @@ <h3>{% blocktrans with title as filter_title %} By {{ filter_title }} {% endbloc {% endfor %} {% endif %} -</ul> \ No newline at end of file +</ul> diff --git a/feincms/templates/admin/medialibrary/mediafile/change_list.html b/feincms/templates/admin/medialibrary/mediafile/change_list.html index a6d484b73..e38062107 100644 --- a/feincms/templates/admin/medialibrary/mediafile/change_list.html +++ b/feincms/templates/admin/medialibrary/mediafile/change_list.html @@ -1,23 +1,25 @@ {% extends "admin/change_list.html" %} -{% load i18n %} - -{% block content %} -{{ block.super }} +{% load i18n %} +{% block content %}{{ block.super }} <div class="module"> - <form action="{% url admin:mediafile_bulk_upload %}" method="POST" enctype="multipart/form-data"> + <form action="{% url "admin:mediafile_bulk_upload" %}" method="POST" enctype="multipart/form-data"> {% csrf_token %} <p style="color: #666; font-size: 11px;"> <label for="data">{% trans "Bulk upload a ZIP file:" %} <input type="file" name="data"> </label> + | {% trans "Category" %} <select name="category"> <option value="">---</option> {% for cat in categories %} - <option value="{{ cat.id }}">{{ cat.title }}</option> + <option value="{{ cat.id }}">{{ cat.path }}</option> {% endfor %} </select> + | {% trans "Overwrite" %} + <input type="checkbox" name="overwrite"> + | <input type="submit" value="{% trans "Send" %}"> </p> </form> diff --git a/feincms/templates/breadcrumbs.html b/feincms/templates/breadcrumbs.html index 52fd8c6af..5322eb37c 100644 --- a/feincms/templates/breadcrumbs.html +++ b/feincms/templates/breadcrumbs.html @@ -8,4 +8,4 @@ {% endif %} </li> {% endfor %} -</ul> \ No newline at end of file +</ul> diff --git a/feincms/templates/content/comments/default.html b/feincms/templates/content/comments/default.html deleted file mode 100644 index 60cf6b6a0..000000000 --- a/feincms/templates/content/comments/default.html +++ /dev/null @@ -1,39 +0,0 @@ -{% load comments i18n %} - -{% block comment-separator %} - <br /> -{% endblock %} - -{% get_comment_count for parent as comment_count %} - -{% if comment_count %} - {% blocktrans %}{{ comment_count }} comments.{% endblocktrans %} - - {% block comment-list %} - <ul class="comment-list"> - {% get_comment_list for parent as comment_list %} - {% for comment in comment_list %} - <li class="comment-list-entry"> - {% block comment-list-entry %} - {% blocktrans with comment.user.username as comment_username and comment.submit_date|date:"Y-m-d H:i" as comment_submit_date %} - {{ comment_username }} said on {{ comment_submit_date }}<br /> - {% endblocktrans %} - {{ comment.comment }} - {% endblock comment-list-entry %} - </li> - {% endfor %} - </ul> - {% endblock comment-list %} -{% else %} - {% trans "No comments." %} -{% endif %} - -{% block comment-form %} - {% if content.comments_enabled %} - <form action="{{ feincms_page.get_absolute_url }}" method="post" id="form-comment"> - {% csrf_token %} - {{ form.as_p }} - <input type="submit" name="post" id="submit" value="{% trans "Post Comment" %}" /> - </form> - {% endif %} -{% endblock comment-form %} diff --git a/feincms/templates/content/file/default.html b/feincms/templates/content/file/default.html index 27683559b..2f1d5baf0 100644 --- a/feincms/templates/content/file/default.html +++ b/feincms/templates/content/file/default.html @@ -1 +1,4 @@ -<a href="{{ content.file.url }}">{{ content.title }}</a> +<a class="file-content" href="{{ content.file.url }}"> + <span class="file-title">{{ content.title }}</span> + <span class="file-size">({{ content.file.size|filesizeformat }})</span> +</a> diff --git a/feincms/templates/content/filer/default.html b/feincms/templates/content/filer/default.html new file mode 100644 index 000000000..60c5a8579 --- /dev/null +++ b/feincms/templates/content/filer/default.html @@ -0,0 +1,3 @@ +{% with mediafile=content.mediafile %} +{{ mediafile }} +{% endwith %} diff --git a/feincms/templates/content/filer/download.html b/feincms/templates/content/filer/download.html new file mode 100644 index 000000000..b1c8491a2 --- /dev/null +++ b/feincms/templates/content/filer/download.html @@ -0,0 +1,8 @@ +{% with mediafile=content.mediafile %} +<a href="{{ mediafile.file.url }}"> + {{ mediafile.name|default:mediafile.file.name }} + {% if mediafile.description %} + <small>{{ mediafile.description }}</small> + {% endif %} +</a> +{% endwith %} diff --git a/feincms/templates/content/filer/image.html b/feincms/templates/content/filer/image.html new file mode 100644 index 000000000..79c18acf6 --- /dev/null +++ b/feincms/templates/content/filer/image.html @@ -0,0 +1,6 @@ +{% load thumbnail %}{% with image=content.mediafile %} +<img src="{% thumbnail image 200x300 crop upscale %}" alt="{{ image.default_alt_text|default_if_none:image.label }}" /> +{% if image.default_caption %}<br><span class="caption">{{ image.default_caption }}</span>{% endif %} +{% endwith %} + +{#{% thumbnail obj.img 200x300 crop upscale subject_location=obj.img.subject_location %}#} diff --git a/feincms/templates/content/image/default.html b/feincms/templates/content/image/default.html index 78a35a1e0..2df317749 100644 --- a/feincms/templates/content/image/default.html +++ b/feincms/templates/content/image/default.html @@ -1 +1 @@ -<img src="{{ content.image.url }}" alt="" /> +<div class="image-content{% if content.position %} image-content-{{ content.position }}{% endif %}{% if content.format %} image-content-format-{{ content.format|slugify }} image-content-format-{{ content.get_format_display|slugify }}{% endif %}"><img src="{{ content.get_image.url }}" alt="{{ content.alt_text }}" />{% if content.caption %}<div class="caption">{{ content.caption }}</div>{% endif %}</div> diff --git a/feincms/templates/content/mediafile/mp3.html b/feincms/templates/content/mediafile/mp3.html index 2bddf3373..6d99a1517 100644 --- a/feincms/templates/content/mediafile/mp3.html +++ b/feincms/templates/content/mediafile/mp3.html @@ -1,6 +1,5 @@ <p id="audioplayer_{{ content.id }}">{{ content.mediafile.translation.caption }}</p> -<script type="text/javascript"> -AudioPlayer.embed("audioplayer_{{ content.id }}", {soundFile: "{{ content.mediafile.file.url }}"}); -</script> +<script type="text/javascript"> +AudioPlayer.embed("audioplayer_{{ content.id }}", {soundFile: "{{ content.mediafile.file.url }}"}); +</script> <p>{{ content.mediafile.translation.caption }} <a href="{{ content.mediafile.file.url }}">(Download)</a></p> - diff --git a/feincms/templates/content/richtext/default.html b/feincms/templates/content/richtext/default.html index 6e874ab4f..f0c5340af 100644 --- a/feincms/templates/content/richtext/default.html +++ b/feincms/templates/content/richtext/default.html @@ -1 +1 @@ -{{ content.text|safe }} \ No newline at end of file +{{ content.text|safe }} diff --git a/feincms/templates/content/rss/content.html b/feincms/templates/content/rss/content.html deleted file mode 100644 index c1eaaf923..000000000 --- a/feincms/templates/content/rss/content.html +++ /dev/null @@ -1,8 +0,0 @@ -<h2><a href="{{ feed_link }}">{{ feed_title }}</a></h2> - -<ul> -{% for entry in entries %} - <li><a href="{{ entry.link }}">{{ entry.title }}</a></li> -{% endfor %} -</ul> - diff --git a/feincms/templates/content/section/default.html b/feincms/templates/content/section/default.html index 62e33c52a..c55ccee9f 100644 --- a/feincms/templates/content/section/default.html +++ b/feincms/templates/content/section/default.html @@ -1,5 +1,7 @@ <h2>{{ content.title }}</h2> -<a href="{{ content.mediafile.file.url }}">{{ content.mediafile }}</a> +{% if content.mediafile %} + <a href="{{ content.mediafile.file.url }}">{{ content.mediafile }}</a> +{% endif %} {{ content.richtext|safe }} diff --git a/feincms/templates/content/video/vimeo.html b/feincms/templates/content/video/vimeo.html index bf2a848fe..6600ad21b 100644 --- a/feincms/templates/content/video/vimeo.html +++ b/feincms/templates/content/video/vimeo.html @@ -1,6 +1,2 @@ -<object width="650" height="365"> - <param name="allowfullscreen" value="true" /> - <param name="allowscriptaccess" value="always" /> - <param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id={{ id }}&server=vimeo.com&show_title=1&show_byline=1&show_portrait=0&color=&fullscreen=1" /> - <embed src="http://vimeo.com/moogaloop.swf?clip_id={{ id }}&server=vimeo.com&show_title=1&show_byline=1&show_portrait=0&color=&fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="650" height="365"></embed> -</object> +<iframe src="//player.vimeo.com/video/{{ id }}" title="Video Player" width="650" height="365" +frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe> diff --git a/feincms/templates/content/video/youtube.html b/feincms/templates/content/video/youtube.html index f87eb4806..2b5fec51c 100644 --- a/feincms/templates/content/video/youtube.html +++ b/feincms/templates/content/video/youtube.html @@ -1,6 +1,6 @@ <object width="425" height="344"> -<param name="movie" value="http://www.youtube.com/v/{{ v }}&hl=en&fs=1&"></param> +<param name="movie" value="//www.youtube.com/v/{{ v }}?hl=en&fs=1&"></param> <param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param> -<embed src="http://www.youtube.com/v/{{ v }}&hl=en&fs=1&" type="application/x-shockwave-flash" +<embed src="//www.youtube.com/v/{{ v }}?hl=en&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed> </object> diff --git a/feincms/templatetags/applicationcontent_tags.py b/feincms/templatetags/applicationcontent_tags.py index d13cd2877..fa4223d70 100644 --- a/feincms/templatetags/applicationcontent_tags.py +++ b/feincms/templatetags/applicationcontent_tags.py @@ -1,8 +1,19 @@ from django import template +from django.template import TemplateSyntaxError +from django.template.defaulttags import kwarg_re +from django.urls import NoReverseMatch +from django.utils.encoding import smart_str + +from feincms.content.application.models import ( + ApplicationContent, + app_reverse as do_app_reverse, +) +from feincms.templatetags.feincms_tags import _render_content # backwards compatibility import from feincms.templatetags.fragment_tags import fragment, get_fragment, has_fragment + register = template.Library() register.tag(fragment) @@ -24,8 +35,112 @@ def feincms_render_region_appcontent(page, region, request): {% feincms_render_region_appcontent feincms_page "main" request %} {% endif %} """ - from feincms.content.application.models import ApplicationContent - from feincms.templatetags.feincms_tags import _render_content + return "".join( + _render_content(content, request=request) + for content in page.content.all_of_type(ApplicationContent) + if content.region == region + ) + + +def _current_app(context): + try: + return context.request.current_app + except AttributeError: + try: + return context.request.resolver_match.namespace + except AttributeError: + return getattr(context, "current_app", None) + + +class AppReverseNode(template.Node): + def __init__(self, view_name, urlconf, args, kwargs, asvar): + self.view_name = view_name + self.urlconf = urlconf + self.args = args + self.kwargs = kwargs + self.asvar = asvar + + def render(self, context): + args = [arg.resolve(context) for arg in self.args] + kwargs = { + smart_str(k, "ascii"): v.resolve(context) for k, v in self.kwargs.items() + } + view_name = self.view_name.resolve(context) + urlconf = self.urlconf.resolve(context) + + try: + url = do_app_reverse( + view_name, + urlconf, + args=args, + kwargs=kwargs, + current_app=_current_app(context), + ) + except NoReverseMatch: + if self.asvar is None: + raise + url = "" + + if self.asvar: + context[self.asvar] = url + return "" + else: + return url + + +@register.tag +def app_reverse(parser, token): + """ + Returns an absolute URL for applications integrated with ApplicationContent + + The tag mostly works the same way as Django's own {% url %} tag:: + + {% load applicationcontent_tags %} + {% app_reverse "mymodel_detail" "myapp.urls" arg1 arg2 %} + + or + + {% load applicationcontent_tags %} + {% app_reverse "mymodel_detail" "myapp.urls" name1=value1 %} + + The first argument is a path to a view. The second argument is the URLconf + under which this app is known to the ApplicationContent. The second + argument may also be a request object if you want to reverse an URL + belonging to the current application content. + + Other arguments are space-separated values that will be filled in place of + positional and keyword arguments in the URL. Don't mix positional and + keyword arguments. + + If you want to store the URL in a variable instead of showing it right away + you can do so too:: + + {% app_reverse "mymodel_detail" "myapp.urls" arg1 arg2 as url %} + """ + bits = token.split_contents() + if len(bits) < 3: + raise TemplateSyntaxError( + "'%s' takes at least two arguments (path to a view and a urlconf)" % bits[0] + ) + viewname = parser.compile_filter(bits[1]) + urlconf = parser.compile_filter(bits[2]) + args = [] + kwargs = {} + asvar = None + bits = bits[3:] + if len(bits) >= 2 and bits[-2] == "as": + asvar = bits[-1] + bits = bits[:-2] + + if len(bits): + for bit in bits: + match = kwarg_re.match(bit) + if not match: + raise TemplateSyntaxError("Malformed arguments to app_reverse tag") + name, value = match.groups() + if name: + kwargs[name] = parser.compile_filter(value) + else: + args.append(parser.compile_filter(value)) - return u''.join(_render_content(content, request=request) for content in\ - page.content.all_of_type(ApplicationContent) if content.region == region) + return AppReverseNode(viewname, urlconf, args, kwargs, asvar) diff --git a/feincms/templatetags/feincms_admin_tags.py b/feincms/templatetags/feincms_admin_tags.py index 04ba5e997..253737587 100644 --- a/feincms/templatetags/feincms_admin_tags.py +++ b/feincms/templatetags/feincms_admin_tags.py @@ -1,18 +1,81 @@ +from collections import OrderedDict + from django import template +from django.contrib.auth import get_permission_codename register = template.Library() -@register.filter -def post_process_fieldsets(fieldset): +@register.simple_tag(takes_context=True) +def post_process_fieldsets(context, fieldset): """ Removes a few fields from FeinCMS admin inlines, those being ``id``, ``DELETE`` and ``ORDER`` currently. + + Additionally, it ensures that dynamically added fields (i.e. + ``ApplicationContent``'s ``admin_fields`` option) are shown. + """ + # abort if fieldset is customized + if fieldset.model_admin.fieldsets: + return fieldset + + fields_to_include = set(fieldset.form.fields.keys()) + for f in ("id", "DELETE", "ORDER"): + fields_to_include.discard(f) + + def _filter_recursive(fields): + ret = [] + for f in fields: + if isinstance(f, (list, tuple)): + # Several fields on one line + sub = _filter_recursive(f) + # Only add if there's at least one field left + if sub: + ret.append(sub) + elif f in fields_to_include: + ret.append(f) + fields_to_include.discard(f) + return ret + + new_fields = _filter_recursive(fieldset.fields) + # Add all other fields (ApplicationContent's admin_fields) to + # the end of the fieldset + for f in fields_to_include: + new_fields.append(f) + + if context.get("request"): + new_fields.extend( + list( + fieldset.model_admin.get_readonly_fields( + context.get("request"), context.get("original") + ) + ) + ) + + fieldset.fields = new_fields + return "" + + +@register.inclusion_tag( + "admin/feincms/content_type_selection_widget.html", takes_context=True +) +def show_content_type_selection_widget(context, region): + """ + {% show_content_type_selection_widget region %} """ + user = context["request"].user + types = OrderedDict({None: []}) + + for ct in region._content_types: + # Skip cts that we shouldn't be adding anyway + opts = ct._meta + perm = opts.app_label + "." + get_permission_codename("add", opts) + if not user.has_perm(perm): + continue - excluded_fields = ('id', 'DELETE', 'ORDER') - fieldset.fields = [f for f in fieldset.form.fields.keys() if f not in excluded_fields] + types.setdefault(getattr(ct, "optgroup", None), []).append( + (ct.__name__.lower, ct._meta.verbose_name) + ) - for line in fieldset: - yield line + return {"types": types} diff --git a/feincms/templatetags/feincms_compat_tags.py b/feincms/templatetags/feincms_compat_tags.py deleted file mode 100644 index e2a7707f6..000000000 --- a/feincms/templatetags/feincms_compat_tags.py +++ /dev/null @@ -1,20 +0,0 @@ -from django import template - - -register = template.Library() - - -def csrf_token(): - """ - Dummy implementation for older versions of Django - """ - - # Should be deprecated, Django versions prior to 1.2 aren't supported - # anymore. - return u'' - - -try: - from django.template.defaulttags import csrf_token -except ImportError: - register.simple_tag(csrf_token) diff --git a/feincms/templatetags/feincms_page_tags.py b/feincms/templatetags/feincms_page_tags.py new file mode 100644 index 000000000..5fa5334ff --- /dev/null +++ b/feincms/templatetags/feincms_page_tags.py @@ -0,0 +1,519 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + +import logging +import sys +import traceback + +from django import template +from django.apps import apps +from django.conf import settings +from django.http import HttpRequest + +from feincms import settings as feincms_settings +from feincms.module.page.extensions.navigation import PagePretender +from feincms.utils.templatetags import ( + SimpleAssignmentNodeWithVarAndArgs, + do_simple_assignment_node_with_var_and_args_helper, +) + + +logger = logging.getLogger("feincms.templatetags.page") + +register = template.Library() + + +def _get_page_model(): + return apps.get_model(*feincms_settings.FEINCMS_DEFAULT_PAGE_MODEL.split(".")) + + +# ------------------------------------------------------------------------ +# TODO: Belongs in some utility module +def format_exception(e): + top = traceback.extract_tb(sys.exc_info()[2])[-1] + return "'%s' in %s line %d" % (e, top[0], top[1]) + + +# ------------------------------------------------------------------------ +@register.simple_tag(takes_context=True) +def feincms_nav(context, feincms_page, level=1, depth=1, group=None): + """ + Saves a list of pages into the given context variable. + """ + + page_class = _get_page_model() + + if not feincms_page: + return [] + + if isinstance(feincms_page, HttpRequest): + try: + feincms_page = page_class.objects.for_request(feincms_page, best_match=True) + except page_class.DoesNotExist: + return [] + + mptt_opts = feincms_page._mptt_meta + + # mptt starts counting at zero + mptt_level_range = [level - 1, level + depth - 1] + + queryset = feincms_page.__class__._default_manager.in_navigation().filter( + **{ + "%s__gte" % mptt_opts.level_attr: mptt_level_range[0], + "%s__lt" % mptt_opts.level_attr: mptt_level_range[1], + } + ) + + page_level = getattr(feincms_page, mptt_opts.level_attr) + + # Used for subset filtering (level>1) + parent = None + + if level > 1: + # A subset of the pages is requested. Determine it depending + # upon the passed page instance + + if level - 2 == page_level: + # The requested pages start directly below the current page + parent = feincms_page + + elif level - 2 < page_level: + # The requested pages start somewhere higher up in the tree + parent = feincms_page.get_ancestors()[level - 2] + + elif level - 1 > page_level: + # The requested pages are grandchildren of the current page + # (or even deeper in the tree). If we would continue processing, + # this would result in pages from different subtrees being + # returned directly adjacent to each other. + queryset = page_class.objects.none() + + if parent: + if getattr(parent, "navigation_extension", None): + # Special case for navigation extensions + return list( + parent.extended_navigation( + depth=depth, request=context.get("request") + ) + ) + + # Apply descendant filter + queryset &= parent.get_descendants() + + if depth > 1: + # Filter out children with inactive parents + # None (no parent) is always allowed + parents = {None} + if parent: + # Subset filtering; allow children of parent as well + parents.add(parent.id) + + def _parentactive_filter(iterable): + for elem in iterable: + if elem.parent_id in parents: + yield elem + parents.add(elem.id) + + queryset = _parentactive_filter(queryset) + + if group is not None: + # navigationgroups extension support + def _navigationgroup_filter(iterable): + for elem in iterable: + if getattr(elem, "navigation_group", None) == group: + yield elem + + queryset = _navigationgroup_filter(queryset) + + if hasattr(feincms_page, "navigation_extension"): + # Filter out children of nodes which have a navigation extension + def _navext_filter(iterable): + current_navextension_node = None + for elem in iterable: + # Eliminate all subitems of last processed nav extension + if ( + current_navextension_node is not None + and current_navextension_node.is_ancestor_of(elem) + ): + continue + + yield elem + if getattr(elem, "navigation_extension", None): + current_navextension_node = elem + try: + for extended in elem.extended_navigation( + depth=depth, request=context.get("request") + ): + # Only return items from the extended navigation + # which are inside the requested level+depth + # values. The "-1" accounts for the differences in + # MPTT and navigation level counting + this_level = getattr(extended, mptt_opts.level_attr, 0) + if this_level < level + depth - 1: + yield extended + except Exception as e: + logger.warn( + "feincms_nav caught exception in navigation" + " extension for page %d: %s", + current_navextension_node.id, + format_exception(e), + ) + else: + current_navextension_node = None + + queryset = _navext_filter(queryset) + + # Return a list, not a generator so that it can be consumed + # several times in a template. + return list(queryset) + + +# ------------------------------------------------------------------------ +class LanguageLinksNode(SimpleAssignmentNodeWithVarAndArgs): + """ + :: + + {% feincms_languagelinks for feincms_page as links [args] %} + + This template tag needs the translations extension. + + Arguments can be any combination of: + + * all or existing: Return all languages or only those where a translation + exists + * excludecurrent: Excludes the item in the current language from the list + * request=request: The current request object, only needed if you are using + AppContents and need to append the "extra path" + + The default behavior is to return an entry for all languages including the + current language. + + Example:: + + {% feincms_languagelinks for feincms_page as links all,excludecurrent %} + {% for key, name, link in links %} + <a href="{% if link %}{{ link }}{% else %}/{{ key }}/{% endif %}"> + {% trans name %}</a> + {% endfor %} + """ + + def what(self, page, args): + only_existing = args.get("existing", False) + exclude_current = args.get("excludecurrent", False) + + # Preserve the trailing path when switching languages if extra_path + # exists (this is mostly the case when we are working inside an + # ApplicationContent-managed page subtree) + trailing_path = "" + request = args.get("request", None) + if request: + # Trailing path without first slash + trailing_path = request._feincms_extra_context.get("extra_path", "")[1:] + + translations = {t.language: t for t in page.available_translations()} + translations[page.language] = page + + links = [] + for key, name in settings.LANGUAGES: + if exclude_current and key == page.language: + continue + + # hardcoded paths... bleh + if key in translations: + links.append( + (key, name, translations[key].get_absolute_url() + trailing_path) + ) + elif not only_existing: + links.append((key, name, None)) + + return links + + +register.tag( + "feincms_languagelinks", + do_simple_assignment_node_with_var_and_args_helper(LanguageLinksNode), +) + + +# ------------------------------------------------------------------------ +def _translate_page_into(page, language, default=None): + """ + Return the translation for a given page + """ + # Optimisation shortcut: No need to dive into translations if page already + # what we want + try: + if page.language == language: + return page + + if language is not None: + translations = {t.language: t for t in page.available_translations()} + if language in translations: + return translations[language] + except AttributeError: + pass + + if hasattr(default, "__call__"): + return default(page=page) + return default + + +# ------------------------------------------------------------------------ +class TranslatedPageNode(SimpleAssignmentNodeWithVarAndArgs): + """ + :: + + {% feincms_translatedpage for feincms_page as feincms_transpage + language=en %} + {% feincms_translatedpage for feincms_page as originalpage %} + {% feincms_translatedpage for some_page as translatedpage + language=feincms_page.language %} + + This template tag needs the translations extension. + + Returns the requested translation of the page if it exists. If the language + argument is omitted the primary language will be returned (the first + language specified in settings.LANGUAGES). + + Note: To distinguish between a bare language code and a variable we check + whether settings LANGUAGES contains that code -- so naming a variable "en" + will probably not do what is intended. + """ + + def what(self, page, args, default=None): + language = args.get("language", None) + + if language is None: + language = settings.LANGUAGES[0][0] + else: + if language not in (x[0] for x in settings.LANGUAGES): + try: + language = template.Variable(language).resolve(self.render_context) + except template.VariableDoesNotExist: + language = settings.LANGUAGES[0][0] + + return _translate_page_into(page, language, default=default) + + +register.tag( + "feincms_translatedpage", + do_simple_assignment_node_with_var_and_args_helper(TranslatedPageNode), +) + + +# ------------------------------------------------------------------------ +class TranslatedPageNodeOrBase(TranslatedPageNode): + def what(self, page, args): + return super().what( + page, args, default=getattr(page, "get_original_translation", page) + ) + + +register.tag( + "feincms_translatedpage_or_base", + do_simple_assignment_node_with_var_and_args_helper(TranslatedPageNodeOrBase), +) + + +# ------------------------------------------------------------------------ +@register.filter +def feincms_translated_or_base(pages, language=None): + if not hasattr(pages, "__iter__"): + pages = [pages] + for page in pages: + yield _translate_page_into( + page, language, default=page.get_original_translation + ) + + +# ------------------------------------------------------------------------ +@register.inclusion_tag("breadcrumbs.html") +def feincms_breadcrumbs(page, include_self=True): + """ + Generate a list of the page's ancestors suitable for use as breadcrumb + navigation. + + By default, generates an unordered list with the id "breadcrumbs" - + override breadcrumbs.html to change this. + + :: + + {% feincms_breadcrumbs feincms_page %} + """ + + ancs = page.get_ancestors() + + bc = [(anc.get_absolute_url(), anc.short_title()) for anc in ancs] + + if include_self: + bc.append((None, page.short_title())) + + return {"trail": bc} + + +# ------------------------------------------------------------------------ +@register.filter +def is_parent_of(page1, page2): + """ + Determines whether a given page is the parent of another page + + Example:: + + {% if page|is_parent_of:feincms_page %} ... {% endif %} + """ + + try: + return page1.is_ancestor_of(page2) + except (AttributeError, ValueError): + return False + + +# ------------------------------------------------------------------------ +@register.filter +def is_equal_or_parent_of(page1, page2): + """ + Determines whether a given page is equal to or the parent of another page. + This is especially handy when generating the navigation. The following + example adds a CSS class ``current`` to the current main navigation entry:: + + {% for page in navigation %} + <a + {% if page|is_equal_or_parent_of:feincms_page %} + class="current" + {% endif %} + >{{ page.title }}</a> + {% endfor %} + """ + try: + return page1.is_ancestor_of(page2, include_self=True) + except (AttributeError, ValueError): + return False + + +# ------------------------------------------------------------------------ +def _is_sibling_of(page1, page2): + return page1.parent_id == page2.parent_id + + +@register.filter +def is_sibling_of(page1, page2): + """ + Determines whether a given page is a sibling of another page + + :: + + {% if page|is_sibling_of:feincms_page %} ... {% endif %} + """ + + try: + return _is_sibling_of(page1, page2) + except AttributeError: + return False + + +# ------------------------------------------------------------------------ +@register.filter +def siblings_along_path_to(page_list, page2): + """ + Filters a list of pages so that only those remain that are either: + + * An ancestor of the current page + * A sibling of an ancestor of the current page + + A typical use case is building a navigation menu with the active + path to the current page expanded:: + + {% feincms_nav feincms_page level=1 depth=3 as navitems %} + {% with navitems|siblings_along_path_to:feincms_page as navtree %} + ... whatever ... + {% endwith %} + + """ + + if page_list: + try: + # Try to avoid hitting the database: If the current page is + # in_navigation, then all relevant pages are already in the + # incoming list, no need to fetch ancestors or children. + + # NOTE: This assumes that the input list actually is complete (ie. + # comes from feincms_nav). We'll cope with the fall-out of that + # assumption when it happens... + ancestors = [ + a_page + for a_page in page_list + if a_page.is_ancestor_of(page2, include_self=True) + ] + top_level = min(a_page.level for a_page in page_list) + + if not ancestors: + # Happens when we sit on a page outside the navigation tree so + # fake an active root page to avoid a get_ancestors() db call + # which would only give us a non-navigation root page anyway. + page_class = _get_page_model() + + p = page_class( + title="dummy", tree_id=-1, parent_id=None, in_navigation=False + ) + ancestors = (p,) + + siblings = [ + a_page + for a_page in page_list + if ( + a_page.parent_id == page2.id + or a_page.level == top_level + or any(_is_sibling_of(a_page, a) for a in ancestors) + ) + ] + + return siblings + except (AttributeError, ValueError) as e: + logger.warn( + "siblings_along_path_to caught exception: %s", format_exception(e) + ) + + return () + + +# ------------------------------------------------------------------------ +@register.simple_tag(takes_context=True) +def page_is_active(context, page, feincms_page=None, path=None): + """ + Usage example:: + + {% feincms_nav feincms_page level=1 as toplevel %} + <ul> + {% for page in toplevel %} + {% page_is_active page as is_active %} + <li {% if is_active %}class="active"{% endif %}> + <a href="{{ page.get_navigation_url }}">{{ page.title }}</a> + <li> + {% endfor %} + </ul> + """ + if isinstance(page, PagePretender): + if path is None: + path = context["request"].path_info + return path.startswith(page.get_absolute_url()) + + else: + if feincms_page is None: + feincms_page = context["feincms_page"] + return page.is_ancestor_of(feincms_page, include_self=True) + + +# ------------------------------------------------------------------------ +@register.simple_tag +def feincms_parentlink(of_, feincms_page, **kwargs): + level = int(kwargs.get("level", 1)) + if feincms_page.level + 1 == level: + return feincms_page.get_absolute_url() + elif feincms_page.level + 1 < level: + return "#" + + try: + return feincms_page.get_ancestors()[level - 1].get_absolute_url() + except IndexError: + return "#" diff --git a/feincms/templatetags/feincms_tags.py b/feincms/templatetags/feincms_tags.py index bd3cd8898..2a1849d1a 100644 --- a/feincms/templatetags/feincms_tags.py +++ b/feincms/templatetags/feincms_tags.py @@ -1,8 +1,14 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + +import logging + from django import template -from django.template.loader import render_to_string -from feincms import settings as feincms_settings +from django.conf import settings +from django.utils.safestring import mark_safe -from feincms import utils +from feincms.utils import get_singleton, get_singleton_url register = template.Library() @@ -12,120 +18,86 @@ def _render_content(content, **kwargs): # Track current render level and abort if we nest too deep. Avoids # crashing in recursive page contents (eg. a page list that contains # itself or similar). - request = kwargs.get('request') + request = kwargs.get("request") if request is not None: - level = getattr(request, 'feincms_render_level', 0) + level = getattr(request, "feincms_render_level", 0) if level > 10: - # TODO: Log this + logging.getLogger("feincms").error( + f"Refusing to render {content!r}, render level is already {level}" + ) return - setattr(request, 'feincms_render_level', level + 1) + setattr(request, "feincms_render_level", level + 1) - try: - r = content.fe_render(**kwargs) - except AttributeError: - r = content.render(**kwargs) + r = content.render(**kwargs) if request is not None: - level = getattr(request, 'feincms_render_level', 1) - setattr(request, 'feincms_render_level', max(level - 1, 0)) + level = getattr(request, "feincms_render_level", 1) + setattr(request, "feincms_render_level", max(level - 1, 0)) - return r + if isinstance(r, (list, tuple)): + # Modeled after feincms3's TemplatePluginRenderer + context = kwargs["context"] + plugin_template, plugin_context = r + + if not hasattr(plugin_template, "render"): # Quacks like a template? + try: + engine = context.template.engine + except AttributeError: + from django.template.engine import Engine + engine = Engine.get_default() -class RenderRegionNode(template.Node): - def __init__(self, feincms_object, region, request): - self.feincms_object = template.Variable(feincms_object) - self.region = template.Variable(region) - self.request = template.Variable(request) + if isinstance(plugin_template, (list, tuple)): + plugin_template = engine.select_template(plugin_template) + else: + plugin_template = engine.get_template(plugin_template) - def render(self, context): - feincms_object = self.feincms_object.resolve(context) - region = self.region.resolve(context) - request = self.request.resolve(context) + with context.push(plugin_context): + return plugin_template.render(context) - return u''.join(_render_content(content, request=request, context=context)\ - for content in getattr(feincms_object.content, region)) + return r -@register.tag -def feincms_render_region(parser, token): +@register.simple_tag(takes_context=True) +def feincms_render_region(context, feincms_object, region, request=None): """ {% feincms_render_region feincms_page "main" request %} """ - try: - tag_name, feincms_object, region, request = token.contents.split() - except ValueError: - raise template.TemplateSyntaxError, 'Invalid syntax for feincms_render_region: %s' % token.contents - - return RenderRegionNode(feincms_object, region, request) - - -class RenderContentNode(template.Node): - def __init__(self, content, request): - self.content = template.Variable(content) - self.request = template.Variable(request) + if not feincms_object: + return "" - def render(self, context): - content = self.content.resolve(context) - request = self.request.resolve(context) + return mark_safe( + "".join( + _render_content(content, request=request, context=context) + for content in getattr(feincms_object.content, region) + ) + ) - return _render_content(content, request=request, context=context) - -@register.tag -def feincms_render_content(parser, token): +@register.simple_tag(takes_context=True) +def feincms_render_content(context, content, request=None): """ - {% feincms_render_content contentblock request %} + {% feincms_render_content content request %} """ - try: - tag_name, content, request = token.contents.split() - except ValueError: - raise template.TemplateSyntaxError, 'Invalid syntax for feincms_render_content: %s' % token.contents + if not content: + return "" - return RenderContentNode(content, request) + return _render_content(content, request=request, context=context) @register.simple_tag -def feincms_prefill_entry_list(queryset, attrs, region=None): +def feincms_load_singleton(template_key, cls=None): """ - {% feincms_prefill_entry_list queryset "authors,richtextcontent_set" [region] %} + {% feincms_load_singleton template_key %} -- return a FeinCMS + Base object which uses a Template with singleton=True. """ - - queryset = utils.prefill_entry_list(queryset, region=region, *(attrs.split(','))) - return u'' - + return get_singleton(template_key, cls, raise_exception=settings.DEBUG) @register.simple_tag -def feincms_frontend_editing(cms_obj, request): +def feincms_singleton_url(template_key, cls=None): """ - {% feincms_frontend_editing feincms_page request %} + {% feincms_singleton_url template_key %} -- return the URL of a FeinCMS + Base object which uses a Template with singleton=True. """ - - if hasattr(request, 'session') and request.session.get('frontend_editing'): - context = template.RequestContext(request, { - "feincms_page": cms_obj, - 'FEINCMS_ADMIN_MEDIA': feincms_settings.FEINCMS_ADMIN_MEDIA, - 'FEINCMS_ADMIN_MEDIA_HOTLINKING': feincms_settings.FEINCMS_ADMIN_MEDIA_HOTLINKING - }) - return render_to_string('admin/feincms/fe_tools.html', context) - - return u'' - -@register.inclusion_tag('admin/feincms/content_type_selection_widget.html', takes_context=True) -def show_content_type_selection_widget(context, region): - """ - {% show_content_type_selection_widget region %} - """ - grouped = {} - ungrouped = [] - for ct in region._content_types: - ct_info = (ct.__name__.lower(), ct._meta.verbose_name) - if hasattr(ct, 'optgroup'): - if ct.optgroup in grouped: - grouped[ct.optgroup].append(ct_info) - else: - grouped[ct.optgroup] = [ct_info] - else: - ungrouped.append(ct_info) - return {'grouped': grouped, 'ungrouped': ungrouped} + return get_singleton_url(template_key, cls, raise_exception=settings.DEBUG) diff --git a/feincms/templatetags/feincms_thumbnail.py b/feincms/templatetags/feincms_thumbnail.py index 8c1a59961..01127a0c5 100644 --- a/feincms/templatetags/feincms_thumbnail.py +++ b/feincms/templatetags/feincms_thumbnail.py @@ -1,32 +1,220 @@ -import os -from cStringIO import StringIO -try: - from PIL import Image -except ImportError: - # Django seems to silently swallow the ImportError under certain - # circumstances. Raise a generic exception explaining why we are - # unable to proceed. - raise Exception, 'FeinCMS requires PIL to be installed' +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + +import logging +import re +from io import BytesIO from django import template -from django.conf import settings -from django.utils.encoding import force_unicode -from django.core.files.storage import default_storage +from django.core.cache import cache from django.core.files.base import ContentFile +from django.core.files.storage import default_storage +from django.utils.encoding import force_str +from PIL import Image + +from feincms import settings +logger = logging.getLogger("feincms.templatetags.thumbnail") register = template.Library() -def tryint(v): - try: - return int(v) - except ValueError: - return 999999 # Arbitrarily big number +class Thumbnailer: + THUMBNAIL_SIZE_RE = re.compile(r"^(?P<w>\d+)x(?P<h>\d+)$") + MARKER = "_thumb_" + BROWSER_SUPPORTED_FORMATS = ( + "jpg", + "jpeg", + "png", + "webp", + ) # browser supported image formats + TRANSPARENCY_SUPPORTING_FORMATS = ("png", "webp") # formats with alpha channel + + def __init__(self, filename, size="200x200"): + self.filename = filename + self.size = size + + @property + def url(self): + return str(self) + + def __str__(self): + match = self.THUMBNAIL_SIZE_RE.match(self.size) + if not (self.filename and match): + return "" + + matches = match.groupdict() + + # figure out storage + if hasattr(self.filename, "storage"): + storage = self.filename.storage + else: + storage = default_storage + + # figure out name + if hasattr(self.filename, "name"): + filename = self.filename.name + else: + filename = force_str(self.filename) + + # defining the filename and the miniature filename + try: + basename, format = filename.rsplit(".", 1) + except ValueError: + basename, format = filename, "jpg" + + miniature = "".join( + [ + settings.FEINCMS_THUMBNAIL_DIR, + basename, + self.MARKER, + self.size, + ".", + format, + ] + ) + + if settings.FEINCMS_THUMBNAIL_CACHE_TIMEOUT != 0: + cache_key = "thumb_url_%s" % miniature + url = cache.get(cache_key) + if url: + return url + + if not storage.exists(miniature): + generate = True + else: + try: + generate = storage.modified_time(miniature) < storage.modified_time( + filename + ) + except (NotImplementedError, AttributeError): + # storage does NOT support modified_time + generate = False + except OSError: + # Someone might have deleted the file + return "" + + if generate: + try: + self.generate( + storage=storage, + original=filename, + size=matches, + miniature=miniature, + ) + except Exception as exc: + logger.exception("Rendering a thumbnail failed: %s", exc) + # PIL raises a plethora of Exceptions if reading the image + # is not possible. Since we cannot be sure what Exception will + # happen, catch them all so the thumbnailer will never fail. + return storage.url(filename) + + url = storage.url(miniature) + if settings.FEINCMS_THUMBNAIL_CACHE_TIMEOUT != 0: + cache.set(cache_key, url, timeout=settings.FEINCMS_THUMBNAIL_CACHE_TIMEOUT) + return url + + def generate(self, storage, original, size, miniature): + with storage.open(original) as original_handle: + with BytesIO(original_handle.read()) as original_bytes: + image = Image.open(original_bytes) + + # defining the size + w, h = int(size["w"]), int(size["h"]) + + assert image.format is not None + format = image.format.lower() # Save format for the save() call later + image.thumbnail([w, h], Image.Resampling.LANCZOS) + buf = BytesIO() + if ( + format not in self.BROWSER_SUPPORTED_FORMATS + ): # browser supported image formats + format = "jpeg" + if ( + format in self.TRANSPARENCY_SUPPORTING_FORMATS + and image.mode not in ("RGBA", "RGB", "L") + ): + image = image.convert("RGBA") + elif ( + format not in self.TRANSPARENCY_SUPPORTING_FORMATS + and image.mode not in ("RGB", "L") + ): + image = image.convert("RGB") + image.save(buf, format, quality=90) + raw_data = buf.getvalue() + buf.close() + + storage.delete(miniature) + storage.save(miniature, ContentFile(raw_data)) + + image.close() + + +class CropscaleThumbnailer(Thumbnailer): + THUMBNAIL_SIZE_RE = re.compile(r"^(?P<w>\d+)x(?P<h>\d+)(-(?P<x>\d+)x(?P<y>\d+))?$") + MARKER = "_cropscale_" + + def generate(self, storage, original, size, miniature): + with storage.open(original) as original_handle: + with BytesIO(original_handle.read()) as original_bytes: + image = Image.open(original_bytes) + + w, h = int(size["w"]), int(size["h"]) + + if size["x"] and size["y"]: + x, y = int(size["x"]), int(size["y"]) + else: + x, y = 50, 50 + + src_width, src_height = image.size + src_ratio = float(src_width) / float(src_height) + dst_width, dst_height = w, h + dst_ratio = float(dst_width) / float(dst_height) + + if dst_ratio < src_ratio: + crop_height = src_height + crop_width = crop_height * dst_ratio + x_offset = int(float(src_width - crop_width) * x / 100) + y_offset = 0 + else: + crop_width = src_width + crop_height = crop_width / dst_ratio + x_offset = 0 + y_offset = int(float(src_height - crop_height) * y / 100) + + assert image.format is not None + format = image.format.lower() # Save format for the save() call later + image = image.crop( + ( + x_offset, + y_offset, + x_offset + int(crop_width), + y_offset + int(crop_height), + ) + ) + image = image.resize((dst_width, dst_height), Image.Resampling.LANCZOS) + + buf = BytesIO() + if format not in self.BROWSER_SUPPORTED_FORMATS: + format = "jpeg" + if image.mode not in ("RGBA", "RGB", "L"): + if format in self.TRANSPARENCY_SUPPORTING_FORMATS: + image = image.convert("RGBA") + else: + image = image.convert("RGB") + image.save(buf, format, quality=90) + raw_data = buf.getvalue() + buf.close() + + storage.delete(miniature) + storage.save(miniature, ContentFile(raw_data)) + + image.close() @register.filter -def thumbnail(filename, size='200x200'): +def thumbnail(filename, size="200x200"): """ Creates a thumbnail from the image passed, returning its path:: @@ -47,130 +235,14 @@ def thumbnail(filename, size='200x200'): {{ object.image|thumbnail:"300x999999" }} """ - if not (filename and 'x' in size): - # Better return empty than crash - return u'' - - # figure out storage - if hasattr(filename, 'storage'): - storage = filename.storage - else: - storage = default_storage - - # figure out name - if hasattr(filename, 'name'): - filename = filename.name - else: - filename = force_unicode(filename) - - # defining the size - x, y = [tryint(x) for x in size.split('x')] - # defining the filename and the miniature filename - try: - basename, format = filename.rsplit('.', 1) - except ValueError: - basename, format = filename, 'jpg' - miniature = basename + '_thumb_' + size + '.' + format - - if not storage.exists(miniature): - generate = True - else: - try: - generate = storage.modified_time(miniature)<storage.modified_time(filename) - except (NotImplementedError, AttributeError): - # storage does NOT support modified_time - generate = False + return Thumbnailer(filename, size) - if generate: - try: - image = Image.open(StringIO(storage.open(filename).read())) - except IOError: - # Do not crash if file does not exist for some reason - return storage.url(filename) - - image.thumbnail([x, y], Image.ANTIALIAS) - buf = StringIO() - if image.mode not in ('RGB', 'L'): - image = image.convert('RGB') - image.save(buf, image.format or 'jpeg', quality=100) - raw_data = buf.getvalue() - buf.close() - storage.save(miniature, ContentFile(raw_data)) - - return storage.url(miniature) @register.filter -def cropscale(filename, size='200x200'): +def cropscale(filename, size="200x200"): """ Scales the image down and crops it so that its size equals exactly the size passed (as long as the initial image is bigger than the specification). """ - if not (filename and 'x' in size): - # Better return empty than crash - return u'' - - # figure out storage - if hasattr(filename, 'storage'): - storage = filename.storage - else: - storage = default_storage - - # figure out name - if hasattr(filename, 'name'): - filename = filename.name - else: - filename = force_unicode(filename) - - w, h = [tryint(x) for x in size.split('x')] - - try: - basename, format = filename.rsplit('.', 1) - except ValueError: - basename, format = filename, 'jpg' - miniature = basename + '_cropscale_' + size + '.' + format - - if not storage.exists(miniature): - generate = True - else: - try: - generate = storage.modified_time(miniature)<storage.modified_time(filename) - except (NotImplementedError, AttributeError): - # storage does NOT support modified_time - generate = False - - if generate: - try: - image = Image.open(StringIO(storage.open(filename).read())) - except IOError: - # Do not crash if file does not exist for some reason - return storage.url(filename) - - src_width, src_height = image.size - src_ratio = float(src_width) / float(src_height) - dst_width, dst_height = w, h - dst_ratio = float(dst_width) / float(dst_height) - - if dst_ratio < src_ratio: - crop_height = src_height - crop_width = crop_height * dst_ratio - x_offset = float(src_width - crop_width) / 2 - y_offset = 0 - else: - crop_width = src_width - crop_height = crop_width / dst_ratio - x_offset = 0 - y_offset = float(src_height - crop_height) / 2 - - image = image.crop((x_offset, y_offset, x_offset+int(crop_width), y_offset+int(crop_height))) - image = image.resize((dst_width, dst_height), Image.ANTIALIAS) - - buf = StringIO() - if image.mode not in ('RGB', 'L'): - image = image.convert('RGB') - image.save(buf, image.format or 'jpeg', quality=100) - raw_data = buf.getvalue() - buf.close() - storage.save(miniature, ContentFile(raw_data)) - - return storage.url(miniature) + return CropscaleThumbnailer(filename, size) diff --git a/feincms/templatetags/fragment_tags.py b/feincms/templatetags/fragment_tags.py index 7a126805e..dcc1e4e32 100644 --- a/feincms/templatetags/fragment_tags.py +++ b/feincms/templatetags/fragment_tags.py @@ -1,10 +1,11 @@ from django import template + register = template.Library() class FragmentNode(template.Node): - def __init__(self, nodelist, request, identifier, mode='append'): + def __init__(self, nodelist, request, identifier, mode="append"): self.nodelist = nodelist self.request_var = template.Variable(request) self.identifier_var = template.Variable(identifier) @@ -15,19 +16,19 @@ def render(self, context): identifier = self.identifier_var.resolve(context) rendered = self.nodelist.render(context) - if not hasattr(request, '_feincms_fragments'): + if not hasattr(request, "_feincms_fragments"): request._feincms_fragments = {} - old = request._feincms_fragments.get(identifier, u'') + old = request._feincms_fragments.get(identifier, "") - if self.mode == 'prepend': + if self.mode == "prepend": request._feincms_fragments[identifier] = rendered + old - elif self.mode == 'replace': + elif self.mode == "replace": request._feincms_fragments[identifier] = rendered - else: # append + else: # append request._feincms_fragments[identifier] = old + rendered - return u'' + return "" @register.tag @@ -42,10 +43,12 @@ def fragment(parser, token): or:: - {% fragment request "title" (prepend|replace|append) %} content ... {% endfragment %} + {% fragment request "title" (prepend|replace|append) %} + content ... + {% endfragment %} """ - nodelist = parser.parse(('endfragment'),) + nodelist = parser.parse("endfragment") parser.delete_first_token() return FragmentNode(nodelist, *token.contents.split()[1:]) @@ -64,11 +67,11 @@ def render(self, context): try: value = request._feincms_fragments[fragment] except (AttributeError, KeyError): - value = u'' + value = "" if self.as_var: context[self.as_var] = value - return u'' + return "" return value @@ -90,9 +93,11 @@ def get_fragment(parser, token): if len(fragments) == 3: return GetFragmentNode(fragments[1], fragments[2]) - elif len(fragments) == 5 and fragments[3] == 'as': + elif len(fragments) == 5 and fragments[3] == "as": return GetFragmentNode(fragments[1], fragments[2], fragments[4]) - raise template.TemplateSyntaxError, 'Invalid syntax for get_fragment: %s' % token.contents + raise template.TemplateSyntaxError( + "Invalid syntax for get_fragment: %s" % token.contents + ) @register.filter @@ -102,4 +107,4 @@ def has_fragment(request, identifier): {% if request|has_fragment:"title" %} ... {% endif %} """ - return getattr(request, '_feincms_fragments', {}).get(identifier) + return getattr(request, "_feincms_fragments", {}).get(identifier) diff --git a/feincms/templatetags/utils.py b/feincms/templatetags/utils.py deleted file mode 100644 index 5391fbb9e..000000000 --- a/feincms/templatetags/utils.py +++ /dev/null @@ -1,7 +0,0 @@ -import warnings -warnings.warn( - 'Please use feincms.utils.templatetags instead of feincms.templatetags.utils', - DeprecationWarning - ) - -from feincms.utils.templatetags import * diff --git a/feincms/tests/__init__.py b/feincms/tests/__init__.py deleted file mode 100644 index 75bcfd878..000000000 --- a/feincms/tests/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -import sys - -from feincms import settings - -try: - any -except NameError: - from feincms.compat import c_any as any - - -def is_feincms_test(): - return any('feincms' in arg for arg in sys.argv[2:]) - - -if settings.FEINCMS_RUN_TESTS or is_feincms_test(): - from feincms.tests.base import * diff --git a/feincms/tests/applicationcontent_urls.py b/feincms/tests/applicationcontent_urls.py deleted file mode 100644 index 92a370b69..000000000 --- a/feincms/tests/applicationcontent_urls.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -This is a dummy module used to test the ApplicationContent -""" - -from django import template -from django.conf.urls.defaults import * -from django.http import HttpResponse, HttpResponseRedirect - -from feincms.views.decorators import standalone - - -def module_root(request): - return 'module_root' - - -def args_test(request, kwarg1, kwarg2): - return HttpResponse(u'%s-%s' % (kwarg1, kwarg2)) - - -def reverse_test(request): - t = template.Template('home:{% url ac_module_root %} args:{% url ac_args_test "xy" "zzy" %} base:{% url feincms_handler "test" %}') - return t.render(template.Context()) - - -def full_reverse_test(request): - t = template.Template('home:{% url feincms.tests.applicationcontent_urls/ac_module_root %} args:{% url feincms.tests.applicationcontent_urls/ac_args_test "xy" "zzy" %} base:{% url feincms_handler "test" %}') - return t.render(template.Context()) - - -def alias_reverse_test(request): - t = template.Template('home:{% url whatever/ac_module_root %} args:{% url whatever/ac_args_test "xy" "zzy" %} base:{% url feincms_handler "test" %}') - return t.render(template.Context()) - - -def fragment(request): - t = template.Template('{% load applicationcontent_tags %}{% fragment request "something" %}some things{% endfragment %}') - return t.render(template.Context({'request': request})) - - -def redirect(request): - return HttpResponseRedirect('../') - - -def response(request): - return HttpResponse('Anything') - - -urlpatterns = patterns('', - url(r'^$', module_root, name='ac_module_root'), - url(r'^args_test/([^/]+)/([^/]+)/$', args_test, name='ac_args_test'), - url(r'^kwargs_test/(?P<kwarg2>[^/]+)/(?P<kwarg1>[^/]+)/$', args_test), - url(r'^reverse_test/$', reverse_test), - url(r'^full_reverse_test/$', full_reverse_test), - url(r'^alias_reverse_test/$', alias_reverse_test), - url(r'^fragment/$', fragment), - url(r'^redirect/$', redirect), - url(r'^response/$', response), - url(r'^response_decorated/$', standalone(response)), -) diff --git a/feincms/tests/base.py b/feincms/tests/base.py deleted file mode 100644 index f05b63f46..000000000 --- a/feincms/tests/base.py +++ /dev/null @@ -1,1440 +0,0 @@ -# coding=utf-8 - -from datetime import datetime, timedelta -import os - -from django import forms, template -from django.conf import settings -from django.contrib.auth.models import User, AnonymousUser -from django.contrib.contenttypes.models import ContentType -from django.core import mail -from django.core.exceptions import ImproperlyConfigured -from django.core.urlresolvers import reverse -from django.db import models -from django.contrib.sites.models import Site -from django.http import Http404, HttpResponseBadRequest -from django.template import TemplateDoesNotExist -from django.template.defaultfilters import slugify -from django.test import TestCase - -from feincms import settings as feincms_settings -from feincms.content.application.models import ApplicationContent, _empty_reverse_cache, reverse -from feincms.content.contactform.models import ContactFormContent, ContactForm -from feincms.content.file.models import FileContent -from feincms.content.image.models import ImageContent -from feincms.content.raw.models import RawContent -from feincms.content.richtext.models import RichTextContent -from feincms.content.video.models import VideoContent - -from feincms.context_processors import add_page_if_missing -from feincms.models import Region, Template, Base, ContentProxy -from feincms.module.blog.models import Entry -from feincms.module.medialibrary.models import Category, MediaFile -from feincms.module.page.models import Page -from feincms.templatetags import feincms_tags -from feincms.translations import short_language_code -from feincms.utils import collect_dict_values, get_object, prefill_entry_list, \ - prefilled_attribute - - -class Empty(object): - """ - Helper class to use as request substitute (or whatever) - """ - - pass - -class TranslationsTest(TestCase): - def test_short_language_code(self): - # this is quite stupid, but it's the first time I do something - # with TestCase - - import feincms.translations - import doctest - - doctest.testmod(feincms.translations) - - -class ModelsTest(TestCase): - def test_region(self): - # Creation should not fail - - r = Region('region', 'region title') - t = Template('base template', 'base.html', ( - ('region', 'region title'), - Region('region2', 'region2 title'), - )) - - # I'm not sure whether this test tests anything at all - self.assertEqual(r.key, t.regions[0].key) - self.assertEqual(unicode(r), 'region title') - - -class UtilsTest(TestCase): - def test_get_object(self): - self.assertRaises(AttributeError, lambda: get_object('feincms.does_not_exist')) - self.assertRaises(ImportError, lambda: get_object('feincms.does_not_exist.fn')) - - self.assertEqual(get_object, get_object('feincms.utils.get_object')) - - def test_collect_dict_values(self): - self.assertEqual({'a': [1, 2], 'b': [3]}, - collect_dict_values([('a', 1), ('a', 2), ('b', 3)])) - - -class ExampleCMSBase(Base): - pass - -ExampleCMSBase.register_regions(('region', 'region title'), ('region2', 'region2 title')) - - -class ExampleCMSBase2(Base): - pass - -ExampleCMSBase2.register_regions(('region', 'region title'), - ('region2', 'region2 title')) - - -class CMSBaseTest(TestCase): - def test_01_simple_content_type_creation(self): - self.assertEqual(ExampleCMSBase.content_type_for(FileContent), None) - - ExampleCMSBase.create_content_type(ContactFormContent) - ExampleCMSBase.create_content_type(FileContent, regions=('region2',)) - - # no POSITION_CHOICES, should raise - self.assertRaises(ImproperlyConfigured, - lambda: ExampleCMSBase.create_content_type(ImageContent)) - - ExampleCMSBase.create_content_type(RawContent) - ExampleCMSBase.create_content_type(RichTextContent) - - # test creating a cotent with arguments, but no initialize_type classmethod - ExampleCMSBase.create_content_type(VideoContent, arbitrary_arg='arbitrary_value') - - # content_type_for should return None if it does not have a subclass registered - self.assertEqual(ExampleCMSBase.content_type_for(Empty), None) - - self.assertTrue('filecontent' not in dict(ExampleCMSBase.template.regions[0].content_types).keys()) - self.assertTrue('filecontent' in dict(ExampleCMSBase.template.regions[1].content_types).keys()) - - def test_02_rsscontent_creation(self): - # this test resides in its own method because the required feedparser - # module is not available everywhere - from feincms.content.rss.models import RSSContent - type = ExampleCMSBase.create_content_type(RSSContent) - obj = type() - - self.assertTrue('yahoo' not in obj.render()) - - obj.link = 'http://rss.news.yahoo.com/rss/topstories' - obj.cache_content(save=False) - - self.assertTrue('yahoo' in obj.render()) - - #Creating a content type twice isn't forbidden anymore - #def test_03_double_creation(self): - # # creating a content type twice is forbidden - # self.assertRaises(ImproperlyConfigured, - # lambda: ExampleCMSBase.create_content_type(RawContent)) - - def test_04_mediafilecontent_creation(self): - # the medialibrary needs to be enabled, otherwise this test fails - - from feincms.content.medialibrary.models import MediaFileContent - - # no POSITION_CHOICES, should raise - self.assertRaises(ImproperlyConfigured, - lambda: ExampleCMSBase.create_content_type(MediaFileContent)) - - def test_05_non_abstract_content_type(self): - # Should not be able to create a content type from a non-abstract base type - class TestContentType(models.Model): - pass - - self.assertRaises(ImproperlyConfigured, - lambda: ExampleCMSBase.create_content_type(TestContentType)) - - def test_06_videocontent(self): - type = ExampleCMSBase.content_type_for(VideoContent) - obj = type() - obj.video = 'http://www.youtube.com/watch?v=zmj1rpzDRZ0' - - self.assertTrue('x-shockwave-flash' in obj.render()) - - self.assertEqual(getattr(type, 'arbitrary_arg'), 'arbitrary_value') - - obj.video = 'http://www.example.com/' - - self.assertTrue(obj.video in obj.render()) - - def test_07_default_render_method(self): - class SomethingElse(models.Model): - class Meta: - abstract = True - - def render_region(self): - return 'hello' - - type = ExampleCMSBase.create_content_type(SomethingElse) - obj = type() - self.assertRaises(NotImplementedError, lambda: obj.render()) - - obj.region = 'region' - self.assertEqual(obj.render(), 'hello') - - def test_08_creating_two_content_types_in_same_application(self): - ExampleCMSBase.create_content_type(RawContent) - ct = ExampleCMSBase.content_type_for(RawContent) - self.assertEqual(ct._meta.db_table, 'tests_examplecmsbase_rawcontent') - - ExampleCMSBase2.create_content_type(RawContent, class_name='RawContent2') - ct2 = ExampleCMSBase2.content_type_for(RawContent) - self.assertEqual(ct2._meta.db_table, 'tests_examplecmsbase2_rawcontent2') - - - -Page.register_extensions('datepublisher', 'navigation', 'seo', 'symlinks', - 'titles', 'translations', 'seo', 'changedate', - 'ct_tracker') -Page.create_content_type(ContactFormContent, form=ContactForm) -Page.create_content_type(FileContent) -Page.register_request_processors(Page.etag_request_processor) -Page.register_response_processors(Page.etag_response_processor) -Page.register_response_processors(Page.debug_sql_queries_response_processor()) - - -class PagesTestCase(TestCase): - def setUp(self): - u = User(username='test', is_active=True, is_staff=True, is_superuser=True) - u.set_password('test') - u.save() - - - self.site_1 = Site.objects.all()[0] - - Page.register_templates({ - 'key': 'base', - 'title': 'Standard template', - 'path': 'feincms_base.html', - 'regions': ( - ('main', 'Main content area'), - ('sidebar', 'Sidebar', 'inherited'), - ), - }, { - 'key': 'theother', - 'title': 'This actually exists', - 'path': 'base.html', - 'regions': ( - ('main', 'Main content area'), - ('sidebar', 'Sidebar', 'inherited'), - ), - }) - feincms_settings.FEINCMS_USE_CACHE = True - - def login(self): - self.assertTrue(self.client.login(username='test', password='test')) - - def create_page(self, title='Test page', parent='', **kwargs): - dic = { - 'title': title, - 'slug': kwargs.get('slug', slugify(title)), - 'parent': parent, - 'template_key': 'base', - 'publication_date_0': '2009-01-01', - 'publication_date_1': '00:00:00', - 'initial-publication_date_0': '2009-01-01', - 'initial-publication_date_1': '00:00:00', - 'language': 'en', - 'site': self.site_1.id, - - 'rawcontent_set-TOTAL_FORMS': 0, - 'rawcontent_set-INITIAL_FORMS': 0, - 'rawcontent_set-MAX_NUM_FORMS': 10, - - 'mediafilecontent_set-TOTAL_FORMS': 0, - 'mediafilecontent_set-INITIAL_FORMS': 0, - 'mediafilecontent_set-MAX_NUM_FORMS': 10, - - 'imagecontent_set-TOTAL_FORMS': 0, - 'imagecontent_set-INITIAL_FORMS': 0, - 'imagecontent_set-MAX_NUM_FORMS': 10, - - 'contactformcontent_set-TOTAL_FORMS': 0, - 'contactformcontent_set-INITIAL_FORMS': 0, - 'contactformcontent_set-MAX_NUM_FORMS': 10, - - 'filecontent_set-TOTAL_FORMS': 0, - 'filecontent_set-INITIAL_FORMS': 0, - 'filecontent_set-MAX_NUM_FORMS': 10, - - 'applicationcontent_set-TOTAL_FORMS': 0, - 'applicationcontent_set-INITIAL_FORMS': 0, - 'applicationcontent_set-MAX_NUM_FORMS': 10, - } - dic.update(kwargs) - return self.client.post('/admin/page/page/add/', dic) - - def create_default_page_set(self): - self.login() - self.create_page() - return self.create_page('Test child page', 1) - - def is_published(self, url, should_be=True): - try: - self.client.get(url) - except TemplateDoesNotExist, e: - if should_be: - if e.args != ('feincms_base.html',): - raise - else: - if e.args != ('404.html',): - raise - - def test_01_tree_editor(self): - self.login() - self.assertEqual(self.client.get('/admin/page/page/').status_code, 200) - - self.assertRedirects(self.client.get('/admin/page/page/?anything=anything'), - '/admin/page/page/?e=1') - - def test_02_add_page(self): - self.login() - self.assertRedirects(self.create_page(title='Test page ' * 10, slug='test-page'), - '/admin/page/page/') - self.assertEqual(Page.objects.count(), 1) - self.assertContains(self.client.get('/admin/page/page/'), '…') - - def test_03_item_editor(self): - self.login() - self.assertRedirects(self.create_page(_continue=1), '/admin/page/page/1/') - self.assertEqual(self.client.get('/admin/page/page/1/').status_code, 200) - self.is_published('/admin/page/page/42/', should_be=False) - - def test_03_add_another(self): - self.login() - self.assertRedirects(self.create_page(_addanother=1), '/admin/page/page/add/') - - def test_04_add_child(self): - response = self.create_default_page_set() - self.assertRedirects(response, '/admin/page/page/') - self.assertEqual(Page.objects.count(), 2) - - page = Page.objects.get(pk=2) - self.assertEqual(page.get_absolute_url(), '/test-page/test-child-page/') - - page.active = True - page.in_navigation = True - page.save() - - # page2 inherited the inactive flag from the toplevel page - self.assertContains(self.client.get('/admin/page/page/'), 'inherited') - - page1 = Page.objects.get(pk=1) - page1.active = True - page1.save() - - self.assertEqual(len(self.client.get('/admin/page/page/').content.split('checked="checked"')), 4) - - def test_05_override_url(self): - self.create_default_page_set() - - page = Page.objects.get(pk=1) - page.override_url = '/something/' - page.save() - - page2 = Page.objects.get(pk=2) - self.assertEqual(page2.get_absolute_url(), '/something/test-child-page/') - - page.override_url = '/' - page.save() - page2 = Page.objects.get(pk=2) - self.assertEqual(page2.get_absolute_url(), '/test-child-page/') - - # This goes through feincms.views.base.handler instead of the applicationcontent handler - self.is_published('/', False) - page.active = True - page.template_key = 'theother' - page.save() - self.is_published('/', True) - - def test_06_tree_editor_save(self): - self.create_default_page_set() - - page1 = Page.objects.get(pk=1) - page2 = Page.objects.get(pk=2) - - page3 = Page.objects.create(title='page3', slug='page3', parent=page2) - page4 = Page.objects.create(title='page4', slug='page4', parent=page1) - page5 = Page.objects.create(title='page5', slug='page5', parent=None) - - self.assertEqual(page3.get_absolute_url(), '/test-page/test-child-page/page3/') - self.assertEqual(page4.get_absolute_url(), '/test-page/page4/') - self.assertEqual(page5.get_absolute_url(), '/page5/') - - self.client.post('/admin/page/page/', { - '__cmd': 'move_node', - 'position': 'last-child', - 'cut_item': '1', - 'pasted_on': '5', - }, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - - self.assertEqual(Page.objects.get(pk=1).get_absolute_url(), - '/page5/test-page/') - self.assertEqual(Page.objects.get(pk=5).get_absolute_url(), - '/page5/') - self.assertEqual(Page.objects.get(pk=3).get_absolute_url(), - '/page5/test-page/test-child-page/page3/') - - def test_07_tree_editor_toggle_boolean(self): - self.create_default_page_set() - - self.assertEqual(Page.objects.get(pk=1).in_navigation, False) - - self.assertContains(self.client.post('/admin/page/page/', { - '__cmd': 'toggle_boolean', - 'item_id': 1, - 'attr': 'in_navigation', - }, HTTP_X_REQUESTED_WITH='XMLHttpRequest'), - r'checked=\"checked\"') - self.assertEqual(Page.objects.get(pk=1).in_navigation, True) - self.assertNotContains(self.client.post('/admin/page/page/', { - '__cmd': 'toggle_boolean', - 'item_id': 1, - 'attr': 'in_navigation', - }, HTTP_X_REQUESTED_WITH='XMLHttpRequest'), - 'checked="checked"') - self.assertEqual(Page.objects.get(pk=1).in_navigation, False) - - self.assertTrue(isinstance(self.client.post('/admin/page/page/', { - '__cmd': 'toggle_boolean', - 'item_id': 1, - 'attr': 'notexists', - }, HTTP_X_REQUESTED_WITH='XMLHttpRequest'), HttpResponseBadRequest)) - - def test_07_tree_editor_invalid_ajax(self): - self.login() - self.assertContains(self.client.post('/admin/page/page/', { - '__cmd': 'notexists', - }, HTTP_X_REQUESTED_WITH='XMLHttpRequest'), - 'Oops. AJAX request not understood.', - status_code=400) - - def test_08_publishing(self): - self.create_default_page_set() - - page = Page.objects.get(pk=1) - page2 = Page.objects.get(pk=2) - self.is_published(page.get_absolute_url(), should_be=False) - self.is_published(page2.get_absolute_url(), should_be=False) - - page.active = True - page.save() - page2.active = True - page2.save() - self.is_published(page.get_absolute_url(), should_be=True) - self.is_published(page2.get_absolute_url(), should_be=True) - - old_publication = page.publication_date - page.publication_date = datetime.now() + timedelta(days=1) - page.save() - self.is_published(page.get_absolute_url(), should_be=False) - - # Should be not accessible because of its parent's inactivity - self.is_published(page2.get_absolute_url(), should_be=False) - - page.publication_date = old_publication - page.publication_end_date = datetime.now() - timedelta(days=1) - page.save() - self.is_published(page.get_absolute_url(), should_be=False) - - # Should be not accessible because of its parent's inactivity - self.is_published(page2.get_absolute_url(), should_be=False) - - page.publication_end_date = datetime.now() + timedelta(days=1) - page.save() - self.is_published(page.get_absolute_url(), should_be=True) - self.is_published(page2.get_absolute_url(), should_be=True) - - def create_pagecontent(self, page, **kwargs): - data = { - 'title': page.title, - 'slug': page.slug, - #'parent': page.parent_id, # this field is excluded from the form - 'template_key': page.template_key, - 'publication_date_0': '2009-01-01', - 'publication_date_1': '00:00:00', - 'initial-publication_date_0': '2009-01-01', - 'initial-publication_date_1': '00:00:00', - 'language': 'en', - 'site': self.site_1.id, - - 'rawcontent_set-TOTAL_FORMS': 1, - 'rawcontent_set-INITIAL_FORMS': 0, - 'rawcontent_set-MAX_NUM_FORMS': 10, - - 'rawcontent_set-0-parent': 1, - 'rawcontent_set-0-region': 'main', - 'rawcontent_set-0-ordering': 0, - 'rawcontent_set-0-text': 'This is some example content', - - 'mediafilecontent_set-TOTAL_FORMS': 1, - 'mediafilecontent_set-INITIAL_FORMS': 0, - 'mediafilecontent_set-MAX_NUM_FORMS': 10, - - 'mediafilecontent_set-0-parent': 1, - 'mediafilecontent_set-0-position': 'block', - - 'imagecontent_set-TOTAL_FORMS': 1, - 'imagecontent_set-INITIAL_FORMS': 0, - 'imagecontent_set-MAX_NUM_FORMS': 10, - - 'imagecontent_set-0-parent': 1, - 'imagecontent_set-0-position': 'default', - - 'contactformcontent_set-TOTAL_FORMS': 1, - 'contactformcontent_set-INITIAL_FORMS': 0, - 'contactformcontent_set-MAX_NUM_FORMS': 10, - - 'filecontent_set-TOTAL_FORMS': 1, - 'filecontent_set-INITIAL_FORMS': 0, - 'filecontent_set-MAX_NUM_FORMS': 10, - - 'applicationcontent_set-TOTAL_FORMS': 1, - 'applicationcontent_set-INITIAL_FORMS': 0, - 'applicationcontent_set-MAX_NUM_FORMS': 10, - } - data.update(kwargs) - - return self.client.post('/admin/page/page/%s/' % page.pk, data) - - def test_09_pagecontent(self): - self.create_default_page_set() - - page = Page.objects.get(pk=1) - response = self.create_pagecontent(page) - self.assertRedirects(response, '/admin/page/page/') - self.assertEqual(page.content.main[0].__class__.__name__, 'RawContent') - - page2 = Page.objects.get(pk=2) - page2.symlinked_page = page - - # Test that all_of_type works correctly even before accessing - # other content methods - self.assertEqual(len(page2.content.all_of_type(RawContent)), 1) - - self.assertEqual(page2.content.main[0].__class__.__name__, 'RawContent') - self.assertEqual(unicode(page2.content.main[0]), - 'main on Test page, ordering 0') - - self.assertEqual(len(page2.content.main), 1) - self.assertEqual(len(page2.content.sidebar), 0) - self.assertEqual(len(page2.content.nonexistant_region), 0) - - self.assertTrue(isinstance(page2.content.media, forms.Media)) - - self.assertEqual(len(page2.content.all_of_type(RawContent)), 1) - self.assertEqual(len(page2.content.all_of_type((ImageContent,))), 0) - self.assertEqual(len(page2.content.all_of_type([ImageContent])), 0) - - def test_10_mediafile_and_imagecontent(self): - self.create_default_page_set() - - page = Page.objects.get(pk=1) - self.create_pagecontent(page) - - path = os.path.join(settings.MEDIA_ROOT, 'somefile.jpg') - f = open(path, 'wb') - f.write('blabla') - f.close() - - category = Category.objects.create(title='Category', parent=None) - category2 = Category.objects.create(title='Something', parent=category) - - self.assertEqual(unicode(category2), 'Category - Something') - self.assertEqual(unicode(category), 'Category') - - mediafile = MediaFile.objects.create(file='somefile.jpg') - mediafile.categories = [category] - page.mediafilecontent_set.create( - mediafile=mediafile, - region='main', - position='block', - ordering=1) - - self.assertEqual(unicode(mediafile), 'somefile.jpg') - - mediafile.translations.create(caption='something', - language_code='%s-ha' % short_language_code()) - - self.assertTrue('something' in unicode(mediafile)) - - mf = page.content.main[1].mediafile - - self.assertEqual(mf.translation.caption, 'something') - self.assertEqual(mf.translation.short_language_code(), short_language_code()) - self.assertNotEqual(mf.get_absolute_url(), '') - self.assertEqual(unicode(mf), 'something') - self.assertEqual(unicode(mf.file_type()), u'Binary') # Ok, so it's not really an image... - - self.assertEqual(MediaFile.objects.only_language('de').count(), 0) - self.assertEqual(MediaFile.objects.only_language('en').count(), 0) - self.assertEqual(MediaFile.objects.only_language('%s-ha' % short_language_code()).count(), - 1) - - self.assertTrue('%s-ha' % short_language_code() in mf.available_translations) - - os.unlink(path) - - # this should not raise - self.client.get('/admin/page/page/1/') - - #self.assertTrue('alt="something"' in page.content.main[1].render()) Since it isn't an image - - page.imagecontent_set.create(image='somefile.jpg', region='main', position='default', ordering=2) - page.filecontent_set.create(file='somefile.jpg', title='thetitle', region='main', ordering=3) - - # Reload page, reset _ct_inventory - page = Page.objects.get(pk=page.pk) - page._ct_inventory = None - - self.assertTrue('somefile.jpg' in page.content.main[2].render()) - self.assertTrue('<a href="/media/somefile.jpg">thetitle</a>' in page.content.main[3].render()) - - page.mediafilecontent_set.update(mediafile=3) - # this should not raise - self.client.get('/admin/page/page/1/') - - field = MediaFile._meta.get_field('file') - old = (field.upload_to, field.storage, field.generate_filename) - from django.core.files.storage import FileSystemStorage - MediaFile.reconfigure(upload_to=lambda: 'anywhere', - storage=FileSystemStorage(location='/wha/', base_url='/whe/')) - mediafile = MediaFile.objects.get(pk=1) - self.assertEqual(mediafile.file.url, '/whe/somefile.jpg') - - # restore settings - (field.upload_to, field.storage, field.generate_filename) = old - - mediafile = MediaFile.objects.get(pk=1) - self.assertEqual(mediafile.file.url, '/media/somefile.jpg') - - def test_11_translations(self): - self.create_default_page_set() - - page1 = Page.objects.get(pk=1) - self.assertEqual(len(page1.available_translations()), 0) - - page1 = Page.objects.get(pk=1) - page2 = Page.objects.get(pk=2) - - page2.language = 'de' - page2.save() - - self.assertEqual(len(page2.available_translations()), 0) - - page2.translation_of = page1 - page2.save() - - self.assertEqual(len(page2.available_translations()), 1) - self.assertEqual(len(page1.available_translations()), 1) - - self.assertEqual(page1, page1.original_translation) - self.assertEqual(page1, page2.original_translation) - - def test_12_titles(self): - self.create_default_page_set() - - page = Page.objects.get(pk=1) - - self.assertEqual(page.page_title, page.title) - self.assertEqual(page.content_title, page.title) - - page._content_title = 'Something\nawful' - page._page_title = 'Hello world' - page.save() - - self.assertEqual(page.page_title, 'Hello world') - self.assertEqual(page.content_title, 'Something') - self.assertEqual(page.content_subtitle, 'awful') - - page._content_title = 'Only one line' - self.assertEqual(page.content_title, 'Only one line') - self.assertEqual(page.content_subtitle, '') - - page._content_title = '' - self.assertEqual(page.content_title, page.title) - self.assertEqual(page.content_subtitle, '') - - def test_13_inheritance_and_ct_tracker(self): - self.create_default_page_set() - - page = Page.objects.get(pk=1) - page.rawcontent_set.create( - region='sidebar', - ordering=0, - text='Something') - page.rawcontent_set.create( - region='main', - ordering=0, - text='Anything') - - page2 = Page.objects.get(pk=2) - page2.rawcontent_set.create( - region='main', - ordering=0, - text='Something else') - page2.rawcontent_set.create( - region='main', - ordering=1, - text='Whatever') - - # Set default, non-caching content proxy - page2.content_proxy_class = ContentProxy - - if hasattr(self, 'assertNumQueries'): - # 4 queries: Two to get the content types of page and page2, one to - # fetch all ancestor PKs of page2 and one to materialize the RawContent - # instances belonging to page's sidebar and page2's main. - self.assertNumQueries(4, lambda: [page2.content.main, page2.content.sidebar]) - self.assertNumQueries(0, lambda: page2.content.sidebar[0].render()) - - self.assertEqual(u''.join(c.render() for c in page2.content.main), - 'Something elseWhatever') - self.assertEqual(page2.content.sidebar[0].render(), 'Something') - - page2 = Page.objects.get(pk=2) - self.assertEqual(page2._ct_inventory, {}) - - # Prime Django content type cache - for ct in Page._feincms_content_types: - ContentType.objects.get_for_model(ct) - - if hasattr(self, 'assertNumQueries'): - # 5 queries: Two to get the content types of page and page2, one to - # fetch all ancestor PKs of page2 and one to materialize the RawContent - # instances belonging to page's sidebar and page2's main and a few - # queries to update the pages _ct_inventory attributes: - # - one update to update page2 - # - one update to clobber the _ct_inventory attribute of all descendants - # of page2 - self.assertNumQueries(5, lambda: [page2.content.main, page2.content.sidebar]) - self.assertNumQueries(0, lambda: page2.content.sidebar[0].render()) - - self.assertEqual(page2.content.sidebar[0].render(), 'Something') - - # Reload, again, to test ct_tracker extension - page2 = Page.objects.get(pk=2) - - if hasattr(self, 'assertNumQueries'): - self.assertNumQueries(1, lambda: [page2.content.main, page2.content.sidebar]) - - self.assertNotEqual(page2._ct_inventory, {}) - - def test_14_richtext(self): - # only create the content type to test the item editor - # customization hooks - tmp = Page._feincms_content_types[:] - type = Page.create_content_type(RichTextContent, regions=('notexists',)) - Page._feincms_content_types = tmp - - from django.utils.safestring import SafeData - obj = type() - obj.text = 'Something' - self.assertTrue(isinstance(obj.render(), SafeData)) - - def test_15_frontend_editing(self): - self.create_default_page_set() - page = Page.objects.get(pk=1) - self.create_pagecontent(page) - - # this should return a 404 - self.is_published('/admin/page/page/10|rawcontent|1/', should_be=False) - self.is_published('/admin/page/page/1|rawcontent|10/', should_be=False) - - self.assertEqual(self.client.get('/admin/page/page/1|rawcontent|1/').status_code, 200) - self.assertEqual(self.client.post('/admin/page/page/1|rawcontent|1/', { - 'rawcontent-text': 'blablabla', - }).status_code, 200) - - self.assertEqual(page.content.main[0].render(), 'blablabla') - self.assertEqual(feincms_tags.feincms_frontend_editing(page, {}), u'') - - request = Empty() - request.session = {'frontend_editing': True} - - self.assertTrue('class="fe_box"' in\ - page.content.main[0].fe_render(request=request)) - - self.assertFalse('class="fe_box"' in self.client.get(page.get_absolute_url() + '?frontend_editing=1').content) - - def test_16_template_tags(self): - # Directly testing template tags doesn't make any sense since - # feincms_render_* do not use simple_tag anymore - pass - - def test_17_page_template_tags(self): - self.create_default_page_set() - - page1 = Page.objects.get(pk=1) - page2 = Page.objects.get(pk=2) - - page2.language = 'de' - page2.translation_of = page1 - page2.active = True - page2.in_navigation = True - page2.save() - - page3 = Page.objects.create(parent=page2, - title='page3', - slug='page3', - language='en', - active=True, - in_navigation=True, - publication_date=datetime(2001, 1, 1), - ) - - # reload these two, their mptt attributes have changed - page1 = Page.objects.get(pk=1) - page2 = Page.objects.get(pk=2) - - context = template.Context({'feincms_page': page2, 'page3': page3}) - - t = template.Template('{% load feincms_page_tags %}{% feincms_parentlink of feincms_page level=1 %}') - self.assertEqual(t.render(context), '/test-page/') - - t = template.Template('{% load feincms_page_tags %}{% feincms_languagelinks for feincms_page as links %}{% for key, name, link in links %}{{ key }}:{{ link }}{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), 'en:/test-page/,de:/test-page/test-child-page/') - - t = template.Template('{% load feincms_page_tags %}{% feincms_languagelinks for page3 as links %}{% for key, name, link in links %}{{ key }}:{{ link }}{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), 'en:/test-page/test-child-page/page3/,de:None') - - t = template.Template('{% load feincms_page_tags %}{% feincms_languagelinks for page3 as links existing %}{% for key, name, link in links %}{{ key }}:{{ link }}{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), 'en:/test-page/test-child-page/page3/') - - t = template.Template('{% load feincms_page_tags %}{% feincms_languagelinks for feincms_page as links excludecurrent=1 %}{% for key, name, link in links %}{{ key }}:{{ link }}{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), 'en:/test-page/') - - t = template.Template('{% load feincms_page_tags %}{% feincms_navigation of feincms_page as nav level=1 %}{% for p in nav %}{{ p.get_absolute_url }}{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), '') - - # XXX should the other template tags not respect the in_navigation setting too? - page1.active = True - page1.in_navigation = True - page1.save() - - self.assertEqual(t.render(context), '/test-page/') - - t = template.Template('{% load feincms_page_tags %}{% feincms_navigation of feincms_page as nav level=2 %}{% for p in nav %}{{ p.get_absolute_url }}{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), '/test-page/test-child-page/') - - t = template.Template('{% load feincms_page_tags %}{% feincms_navigation of request as nav level=2 %}{% for p in nav %}{{ p.get_absolute_url }}{% if not forloop.last %},{% endif %}{% endfor %}') - from django.http import HttpRequest - request = HttpRequest() - request.path = '/test-page/' - self.assertEqual(t.render(template.Context({'request': request})), '/test-page/test-child-page/') - - t = template.Template('{% load feincms_page_tags %}{% feincms_navigation of feincms_page as nav level=99 %}{% for p in nav %}{{ p.get_absolute_url }}{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), '') - - t = template.Template('{% load feincms_page_tags %}{% feincms_breadcrumbs feincms_page %}') - rendered = t.render(context) - self.assertTrue("Test child page" in rendered) - self.assertTrue('href="/test-page/">Test page</a>' in rendered, msg="The parent page should be a breadcrumb link") - self.assertTrue('href="/test-page/test-child-page/"' not in rendered, msg="The current page should not be a link in the breadcrumbs") - - t = template.Template('{% load feincms_page_tags %}{% feincms_navigation of feincms_page as nav level=2,depth=2 %}{% for p in nav %}{{ p.get_absolute_url }}{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), '/test-page/test-child-page/,/test-page/test-child-page/page3/') - - t = template.Template('{% load feincms_page_tags %}{% feincms_navigation of feincms_page as nav level=1,depth=2 %}{% for p in nav %}{{ p.get_absolute_url }}{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), '/test-page/,/test-page/test-child-page/') - - t = template.Template('{% load feincms_page_tags %}{% feincms_navigation of feincms_page as nav level=1,depth=3 %}{% for p in nav %}{{ p.get_absolute_url }}{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), '/test-page/,/test-page/test-child-page/,/test-page/test-child-page/page3/') - - t = template.Template('{% load feincms_page_tags %}{% feincms_navigation of feincms_page as nav level=3,depth=2 %}{% for p in nav %}{{ p.get_absolute_url }}{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), '/test-page/test-child-page/page3/') - - t = template.Template('{% load feincms_page_tags %}{% if feincms_page|is_parent_of:page3 %}yes{% endif %}|{% if page3|is_parent_of:feincms_page %}yes{% endif %}') - self.assertEqual(t.render(context), 'yes|') - - t = template.Template('{% load feincms_page_tags %}{% if feincms_page|is_equal_or_parent_of:page3 %}yes{% endif %}|{% if page3|is_equal_or_parent_of:feincms_page %}yes{% endif %}') - self.assertEqual(t.render(context), 'yes|') - - t = template.Template('{% load feincms_page_tags %}{% feincms_translatedpage for feincms_page as t1 language=de %}{% feincms_translatedpage for feincms_page as t2 %}{{ t1.id }}|{{ t2.id }}') - self.assertEqual(t.render(context), '2|1') - - def test_17_feincms_navigation(self): - """ - Test feincms_navigation some more - """ - - self.login() - - self.create_page('Page 1') - self.create_page('Page 1.1', 1) - self.create_page('Page 1.2', 1) - self.create_page('Page 1.2.1', 3) - self.create_page('Page 1.2.2', 3) - self.create_page('Page 1.2.3', 3) - self.create_page('Page 1.3', 1) - - self.create_page('Page 2') - self.create_page('Page 2.1', 8) - self.create_page('Page 2.2', 8) - self.create_page('Page 2.3', 8) - - self.create_page('Page 3') - self.create_page('Page 3.1', 12) - self.create_page('Page 3.2', 12) - self.create_page('Page 3.3', 12) - self.create_page('Page 3.3.1', 15) - self.create_page('Page 3.3.1.1', 16) - self.create_page('Page 3.3.2', 15) - - self.create_page('Page 4') - self.create_page('Page 4.1', 19) - self.create_page('Page 4.2', 19) - - Page.objects.all().update(active=True, in_navigation=True) - Page.objects.filter(id__in=(5, 9, 19)).update(in_navigation=False) - - tests = [ - ( - {'feincms_page': Page.objects.get(pk=1)}, - '{% load feincms_page_tags %}{% feincms_navigation of feincms_page as nav level=1,depth=2 %}{% for p in nav %}{{ p.get_absolute_url }}{% if not forloop.last %},{% endif %}{% endfor %}', - '/page-1/,/page-1/page-11/,/page-1/page-12/,/page-1/page-13/,/page-2/,/page-2/page-22/,/page-2/page-23/,/page-3/,/page-3/page-31/,/page-3/page-32/,/page-3/page-33/', - ), - ( - {'feincms_page': Page.objects.get(pk=14)}, - '{% load feincms_page_tags %}{% feincms_navigation of feincms_page as nav level=2,depth=2 %}{% for p in nav %}{{ p.get_absolute_url }}{% if not forloop.last %},{% endif %}{% endfor %}', - '/page-3/page-31/,/page-3/page-32/,/page-3/page-33/,/page-3/page-33/page-331/,/page-3/page-33/page-332/', - ), - ( - {'feincms_page': Page.objects.get(pk=14)}, - '{% load feincms_page_tags %}{% feincms_navigation of feincms_page as nav level=2,depth=3 %}{% for p in nav %}{{ p.get_absolute_url }}{% if not forloop.last %},{% endif %}{% endfor %}', - '/page-3/page-31/,/page-3/page-32/,/page-3/page-33/,/page-3/page-33/page-331/,/page-3/page-33/page-331/page-3311/,/page-3/page-33/page-332/', - ), - ( - {'feincms_page': Page.objects.get(pk=19)}, - '{% load feincms_page_tags %}{% feincms_navigation of feincms_page as nav level=1,depth=2 %}{% for p in nav %}{{ p.get_absolute_url }}{% if not forloop.last %},{% endif %}{% endfor %}', - '/page-1/,/page-1/page-11/,/page-1/page-12/,/page-1/page-13/,/page-2/,/page-2/page-22/,/page-2/page-23/,/page-3/,/page-3/page-31/,/page-3/page-32/,/page-3/page-33/', - ), - ] - - for c, t, r in tests: - self.assertEqual( - template.Template(t).render(template.Context(c)), - r) - - def test_18_default_render_method(self): - """ - Test the default render() behavior of selecting render_<region> methods - to do the (not so) heavy lifting. - """ - - class Something(models.Model): - class Meta: - abstract = True - - def render_main(self): - return u'Hello' - - # do not register this model in the internal FeinCMS bookkeeping structures - tmp = Page._feincms_content_types[:] - type = Page.create_content_type(Something, regions=('notexists',)) - Page._feincms_content_types = tmp - - s = type(region='main', ordering='1') - - self.assertEqual(s.render(), 'Hello') - - def test_19_page_manager(self): - self.create_default_page_set() - - page = Page.objects.get(pk=2) - page.active = True - page.save() - - self.assertEqual(page, Page.objects.page_for_path(page.get_absolute_url())) - self.assertEqual(page, Page.objects.best_match_for_path(page.get_absolute_url() + 'something/hello/')) - - self.assertRaises(Http404, lambda: Page.objects.best_match_for_path('/blabla/blabla/', raise404=True)) - self.assertRaises(Http404, lambda: Page.objects.page_for_path('/asdf/', raise404=True)) - self.assertRaises(Page.DoesNotExist, lambda: Page.objects.best_match_for_path('/blabla/blabla/')) - self.assertRaises(Page.DoesNotExist, lambda: Page.objects.page_for_path('/asdf/')) - - request = Empty() - request.path = page.get_absolute_url() - request.method = 'GET' - request.get_full_path = lambda: '/xyz/' - request.GET = {} - request.META = {} - request.user = AnonymousUser() - - # tadaa - from django.utils import translation - translation.activate(page.language) - - page.active = False - page.save() - - self.assertRaises(Http404, lambda: Page.objects.for_request(request, raise404=True)) - - page.active = True - page.save() - - self.assertRaises(Http404, lambda: Page.objects.for_request(request, raise404=True)) - - page.parent.active = True - page.parent.save() - self.assertEqual(page, Page.objects.for_request(request)) - - old = feincms_settings.FEINCMS_ALLOW_EXTRA_PATH - request.path += 'hello/' - - feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = False - self.assertEqual(self.client.get(request.path).status_code, 404) - - feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = True - self.assertEqual(self.client.get(request.path).status_code, 200) - self.assertEqual(page, Page.objects.for_request(request, best_match=True)) - - feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = old - - page_id = id(request._feincms_page) - p = Page.objects.for_request(request) - self.assertEqual(id(p), page_id) - - def test_20_redirects(self): - self.create_default_page_set() - page1 = Page.objects.get(pk=1) - page2 = Page.objects.get(pk=2) - - page2.active = True - page2.publication_date = datetime.now() - timedelta(days=1) - page2.override_url = '/blablabla/' - page2.redirect_to = page1.get_absolute_url() - page2.save() - - # regenerate cached URLs in the whole tree - page1.active = True - page1.save() - - page2 = Page.objects.get(pk=2) - - # page2 has been modified too, but its URL should not have changed - try: - self.assertRedirects(self.client.get('/blablabla/'), page1.get_absolute_url()) - except TemplateDoesNotExist, e: - # catch the error from rendering page1 - if e.args != ('feincms_base.html',): - raise - - def test_21_copy_content(self): - self.create_default_page_set() - page = Page.objects.get(pk=1) - self.create_pagecontent(page) - - page2 = Page.objects.get(pk=2) - page2.copy_content_from(page) - self.assertEqual(len(page2.content.main), 1) - - def test_22_contactform(self): - self.create_default_page_set() - page = Page.objects.get(pk=1) - page.active = True - page.template_key = 'theother' - page.save() - - page.contactformcontent_set.create(email='mail@example.com', subject='bla', - region='main', ordering=0) - - request = Empty() - request.method = 'GET' - request.GET = {} - request.META = {} - request.user = Empty() - request.user.is_authenticated = lambda: False - request.user.get_and_delete_messages = lambda: () - - page.content.main[0].process(request) - self.assertTrue('form' in page.content.main[0].render(request=request)) - - self.client.post(page.get_absolute_url(), { - 'name': 'So what\'s your name, dude?', - 'email': 'another@example.com', - 'subject': 'This is a test. Please calm down', - 'content': 'Hell on earth.', - }) - - self.assertEquals(len(mail.outbox), 1) - self.assertEquals(mail.outbox[0].subject, 'This is a test. Please calm down') - - def test_23_navigation_extension(self): - self.create_default_page_set() - - page = Page.objects.get(pk=1) - - self.assertEqual(len(page.extended_navigation()), 0) - - page.navigation_extension = 'feincms.tests.navigation_extensions.PassthroughExtension' - - page2 = Page.objects.get(pk=2) - page2.active = True - page2.in_navigation = True - page2.save() - - self.assertEqual(list(page.extended_navigation()), [page2]) - - page.navigation_extension = 'feincms.tests.navigation_extensions.ThisExtensionDoesNotExist' - - self.assertEqual(len(page.extended_navigation()), 1) - - page.navigation_extension = 'feincms.tests.navigation_extensions.PretenderExtension' - - self.assertEqual(page.extended_navigation()[0].get_absolute_url(), '/asdsa/') - - def test_24_admin_redirects(self): - self.create_default_page_set() - page = Page.objects.get(pk=1) - - response = self.create_pagecontent(page, _continue=1) - self.assertRedirects(response, '/admin/page/page/1/') - - response = self.create_pagecontent(page, _addanother=1) - self.assertRedirects(response, '/admin/page/page/add/') - - response = self.create_pagecontent(page) - self.assertRedirects(response, '/admin/page/page/') - - def test_25_applicationcontent(self): - self.create_default_page_set() - - page1 = Page.objects.get(pk=1) - page1.active = True - page1.save() - - page = Page.objects.get(pk=2) - page.active = True - page.template_key = 'theother' - page.save() - - # Should not be published because the page has no application contents and should - # therefore not catch anything below it. - self.is_published(page1.get_absolute_url() + 'anything/', False) - - page.applicationcontent_set.create( - region='main', ordering=0, - urlconf_path='feincms.tests.applicationcontent_urls') - - self.assertContains(self.client.get(page.get_absolute_url()), - 'module_root') - self.assertContains(self.client.get(page.get_absolute_url() + 'args_test/abc/def/'), - 'abc-def') - self.assertContains(self.client.get(page.get_absolute_url() + 'kwargs_test/abc/def/'), - 'def-abc') - - response = self.client.get(page.get_absolute_url() + 'reverse_test/') - self.assertContains(response, 'home:/test-page/test-child-page/') - self.assertContains(response, 'args:/test-page/test-child-page/args_test/xy/zzy/') - self.assertContains(response, 'base:/test/') - - response = self.client.get(page.get_absolute_url() + 'full_reverse_test/') - self.assertContains(response, 'home:/test-page/test-child-page/') - self.assertContains(response, 'args:/test-page/test-child-page/args_test/xy/zzy/') - self.assertContains(response, 'base:/test/') - - self.assertEqual(reverse('feincms.tests.applicationcontent_urls/ac_module_root'), - '/test-page/test-child-page/') - - if hasattr(self, 'assertNumQueries'): - self.assertNumQueries(0, - lambda: reverse('feincms.tests.applicationcontent_urls/ac_module_root')) - - _empty_reverse_cache() - - self.assertNumQueries(1, - lambda: reverse('feincms.tests.applicationcontent_urls/ac_module_root')) - self.assertNumQueries(0, - lambda: reverse('feincms.tests.applicationcontent_urls/ac_module_root')) - - # This should not raise - self.assertEquals(self.client.get(page.get_absolute_url() + 'notexists/').status_code, 404) - - self.assertContains(self.client.get(page.get_absolute_url() + 'fragment/'), - '<span id="something">some things</span>') - - self.assertRedirects(self.client.get(page.get_absolute_url() + 'redirect/'), - page.get_absolute_url()) - - self.assertEqual(reverse('feincms.tests.applicationcontent_urls/ac_module_root'), - page.get_absolute_url()) - - response = self.client.get(page.get_absolute_url() + 'response/') - self.assertContains(response, 'Anything') - self.assertContains(response, '<h2>Main content</h2>') # Ensure response has been wrapped - - # Test standalone behavior - self.assertEqual( - self.client.get(page.get_absolute_url() + 'response/', - HTTP_X_REQUESTED_WITH='XMLHttpRequest').content, - self.client.get(page.get_absolute_url() + 'response_decorated/').content) - - # Test reversing of URLs (with overridden urls too) - page.applicationcontent_set.create( - region='main', - ordering=1, - urlconf_path='blog_urls') - page1.applicationcontent_set.create( - region='main', - ordering=0, - urlconf_path='whatever') - - response = self.client.get(page.get_absolute_url() + 'alias_reverse_test/') - self.assertContains(response, 'home:/test-page/') - self.assertContains(response, 'args:/test-page/args_test/xy/zzy/') - self.assertContains(response, 'base:/test/') - - self.assertEqual(reverse('blog_urls/blog_entry_list'), '/test-page/test-child-page/') - self.assertEqual(reverse('feincms.tests.applicationcontent_urls/ac_module_root'), - '/test-page/test-child-page/') - self.assertEqual(reverse('whatever/ac_module_root'), '/test-page/') - - page.applicationcontent_set.get(urlconf_path='feincms.tests.applicationcontent_urls').delete() - - self.assertEqual(reverse('blog_urls/blog_entry_list'), '/test-page/test-child-page/') - self.assertEqual(reverse('whatever/ac_module_root'), '/test-page/') - - def test_26_page_form_initial(self): - self.create_default_page_set() - - self.assertEqual(self.client.get('/admin/page/page/add/?translation_of=1&lang=de').status_code, 200) - self.assertEqual(self.client.get('/admin/page/page/add/?parent=1').status_code, 200) - self.assertEqual(self.client.get('/admin/page/page/add/?parent=2').status_code, 200) - - def test_27_cached_url_clash(self): - self.create_default_page_set() - - page1 = Page.objects.get(pk=1) - page2 = Page.objects.get(pk=2) - - page1.override_url = '/' - page1.active = True - page1.save() - - self.assertContains(self.create_pagecontent(page2, active=True, override_url='/'), - 'already taken by') - - def test_28_applicationcontent_reverse(self): - self.create_default_page_set() - page1 = Page.objects.get(pk=1) - page1.active = True - page1.save() - - page = Page.objects.get(pk=2) - page.active = True - page.template_key = 'theother' - page.save() - page.applicationcontent_set.create( - region='main', ordering=0, - urlconf_path='feincms.tests.applicationcontent_urls') - - from django.core.urlresolvers import reverse - - # test reverse replacement - self.assertEqual(reverse('feincms.tests.applicationcontent_urls/ac_module_root'), - page.get_absolute_url()) - - - # when specific applicationcontent exists more then once reverse should return url - # for the one that has tree_id same as current feincms page - self.create_page(title='Home DE', language='de', active=True) - page_de = Page.objects.get(title='Home DE') - self.create_page(title='Child 1 DE', language='de', parent=page_de.id, active=True) - page_de_1 = Page.objects.get(title='Child 1 DE') - page_de_1.applicationcontent_set.create( - region='main', ordering=0, - urlconf_path='feincms.tests.applicationcontent_urls') - _empty_reverse_cache() - - settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'templates'),) - self.client.get(page_de.get_absolute_url()) - self.assertEqual(reverse('feincms.tests.applicationcontent_urls/ac_module_root'), - page_de_1.get_absolute_url()) - - self.client.get(page1.get_absolute_url()) - self.assertEqual(reverse('feincms.tests.applicationcontent_urls/ac_module_root'), - page.get_absolute_url()) - - def test_29_medialibrary_admin(self): - self.create_default_page_set() - - page = Page.objects.get(pk=1) - - path = os.path.join(settings.MEDIA_ROOT, 'somefile.jpg') - f = open(path, 'wb') - f.write('blabla') - f.close() - - mediafile = MediaFile.objects.create(file='somefile.jpg') - page.mediafilecontent_set.create( - mediafile=mediafile, - region='main', - position='block', - ordering=1) - - self.assertContains(self.client.get('/admin/medialibrary/mediafile/'), 'somefile.jpg') - - import zipfile - zf = zipfile.ZipFile('test.zip', 'w') - for i in range(10): - zf.writestr('test%d.jpg' % i, 'test%d' % i) - zf.close() - - self.assertRedirects(self.client.post('/admin/medialibrary/mediafile/mediafile-bulk-upload/', { - 'data': open('test.zip'), - }), '/admin/medialibrary/mediafile/') - - self.assertEqual(MediaFile.objects.count(), 11) - - self.assertRedirects(self.client.post('/admin/medialibrary/mediafile/add/', { - 'file': open(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), - 'docs', 'images', 'tree_editor.png')), - 'translations-TOTAL_FORMS': 0, - 'translations-INITIAL_FORMS': 0, - 'translations-MAX_NUM_FORMS': 10, - }), '/admin/medialibrary/mediafile/') - - self.assertContains(self.client.get('/admin/medialibrary/mediafile/'), - '100x100.png" alt="" />') - - stats = list(MediaFile.objects.values_list('type', flat=True)) - self.assertEqual(stats.count('image'), 1) - self.assertEqual(stats.count('other'), 11) - - def test_30_context_processors(self): - self.create_default_page_set() - Page.objects.update(active=True, in_navigation=True) - - request = Empty() - request.GET = {} - request.META = {} - request.method = 'GET' - request.path = '/test-page/test-child-page/abcdef/' - request.get_full_path = lambda: '/test-page/test-child-page/abcdef/' - - ctx = add_page_if_missing(request) - self.assertEqual(ctx['feincms_page'], request._feincms_page) - - def test_31_sites_framework_associating_with_single_site(self): - self.login() - site_2 = Site.objects.create(name='site 2', domain='2.example.com') - self.create_page('site 1 homepage', override_url='/', active=True) - self.create_page('site 2 homepage', override_url='/', - site=site_2.id, active=True) - self.assertEqual(Page.objects.count(), 2) - self.assertEqual(Page.objects.active().count(), 1) - - -Entry.register_extensions('seo', 'translations', 'seo', 'ct_tracker') -class BlogTestCase(TestCase): - def setUp(self): - u = User(username='test', is_active=True, is_staff=True, is_superuser=True) - u.set_password('test') - u.save() - - Entry.register_regions(('main', 'Main region'), ('another', 'Another region')) - Entry.prefilled_categories = prefilled_attribute('categories') - Entry.prefilled_rawcontent_set = prefilled_attribute('rawcontent_set') - - def login(self): - self.assertTrue(self.client.login(username='test', password='test')) - - def create_entry(self): - entry = Entry.objects.create( - published=True, - title='Something', - slug='something', - language='en') - - entry.rawcontent_set.create( - region='main', - ordering=0, - text='Something awful') - - return entry - - def create_entries(self): - entry = self.create_entry() - - Entry.objects.create( - published=True, - title='Something 2', - slug='something-2', - language='de', - translation_of=entry) - - Entry.objects.create( - published=True, - title='Something 3', - slug='something-3', - language='de') - - def test_01_prefilled_attributes(self): - self.create_entry() - - # This should return silently - objects = prefill_entry_list(Entry.objects.none(), 'rawcontent_set', 'categories') - - objects = prefill_entry_list(Entry.objects.published(), 'rawcontent_set', 'categories') - - self.assertEqual(len(objects[0]._prefill_categories), 0) - self.assertEqual(len(objects[0]._prefill_rawcontent_set), 1) - self.assertEqual(unicode(objects[0]), 'Something') - - objects = Entry.objects.published() - - self.assertEqual(len(objects[0].prefilled_categories), 0) - self.assertEqual(len(objects[0].prefilled_rawcontent_set), 1) - - objects = prefill_entry_list(Entry.objects.published(), 'rawcontent_set', 'categories', region='another') - - self.assertEqual(len(objects[0]._prefill_categories), 0) - self.assertEqual(len(objects[0]._prefill_rawcontent_set), 0) - - self.login() - self.assertEqual(self.client.get('/admin/blog/entry/').status_code, 200) - self.assertEqual(self.client.get('/admin/blog/entry/1/').status_code, 200) - - def test_02_translations(self): - self.create_entries() - - entries = Entry.objects.in_bulk((1, 2, 3)) - - self.assertEqual(len(entries[1].available_translations()), 1) - self.assertEqual(len(entries[2].available_translations()), 1) - self.assertEqual(len(entries[3].available_translations()), 0) - - def test_03_admin(self): - self.login() - self.create_entries() - self.assertEqual(self.client.get('/admin/blog/entry/').status_code, 200) - self.assertEqual(self.client.get('/admin/blog/entry/1/').status_code, 200) - - -class CleanseTestCase(TestCase): - def test_01_cleanse(self): - from feincms.utils.html.cleanse import cleanse_html - - entries = [ - (u'<p> </p>', u''), - (u'<span style="font-weight: bold;">Something</span><p></p>', u'<strong>Something</strong>'), - (u'<p>abc <span>def <em>ghi</em> jkl</span> mno</p>', u'<p>abc def <em>ghi</em> jkl mno</p>'), - (u'<span style="font-style: italic;">Something</span><p></p>', u'<em>Something</em>'), - (u'<p>abc<br />def</p>', u'<p>abc<br />def</p>'), - (u'<p><p><p> </p> </p><p><br /></p></p>', u' '), - ] - - for a, b in entries: - self.assertEqual(cleanse_html(a), b) diff --git a/feincms/tests/navigation_extensions.py b/feincms/tests/navigation_extensions.py deleted file mode 100644 index 1547bab7d..000000000 --- a/feincms/tests/navigation_extensions.py +++ /dev/null @@ -1,17 +0,0 @@ -from feincms.module.page.extensions.navigation import NavigationExtension, PagePretender - - -class PassthroughExtension(NavigationExtension): - # See PagesTestCase.test_23_navigation_extension - name = 'passthrough extension' - - def children(self, page): - for p in page.children.in_navigation(): - yield p - - -class PretenderExtension(NavigationExtension): - name = 'pretender extension' - - def children(self, page): - return [PagePretender(title='blabla', url='/asdsa/')] diff --git a/feincms/translations.py b/feincms/translations.py index 840c3fd04..840e24d68 100644 --- a/feincms/translations.py +++ b/feincms/translations.py @@ -6,7 +6,7 @@ class News(models.Model, TranslatedObjectMixin): active = models.BooleanField(default=False) - created = models.DateTimeField(default=datetime.now) + created = models.DateTimeField(default=timezone.now) class NewsTranslation(Translation(News)): @@ -14,18 +14,18 @@ class NewsTranslation(Translation(News)): body = models.TextField() -Print the titles of all news entries either in the current language (if available) -or in any other language:: +Print the titles of all news entries either in the current language (if +available) or in any other language:: for news in News.objects.all(): - print news.translation.title + print(news.translation.title) Print all the titles of all news entries which have an english translation:: from django.utils import translation translation.activate('en') for news in News.objects.filter(translations__language_code='en'): - print news.translation.title + print(news.translation.title) """ from django.conf import settings @@ -35,17 +35,25 @@ class NewsTranslation(Translation(News)): from django.db import models from django.db.models import Q from django.utils import translation -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ + +from feincms.utils import queryset_transform + + +class _NoTranslation: + """Simple marker for when no translations exist for a certain object + + Only used for caching.""" def short_language_code(code=None): """ - Extract the short language code from its argument (or return the default language code). + Extract the short language code from its argument (or return the default + language code). - >>> from django.conf import settings - >>> short_language_code('de') + >>> str(short_language_code('de')) 'de' - >>> short_language_code('de-at') + >>> str(short_language_code('de-at')) 'de' >>> short_language_code() == short_language_code(settings.LANGUAGE_CODE) True @@ -53,7 +61,7 @@ def short_language_code(code=None): if code is None: code = translation.get_language() - pos = code.find('-') + pos = code.find("-") if pos > -1: return code[:pos] return code @@ -61,8 +69,9 @@ def short_language_code(code=None): def is_primary_language(language=None): """ - Returns true if current or passed language is the primary language for this site. - (The primary language is defined as the first language in settings.LANGUAGES.) + Returns true if current or passed language is the primary language for this + site. (The primary language is defined as the first language in + settings.LANGUAGES.) """ if not language: @@ -71,7 +80,75 @@ def is_primary_language(language=None): return language == settings.LANGUAGES[0][0] -class TranslatedObjectManager(models.Manager): +def lookup_translations(language_code=None): + """ + Pass the return value of this function to .transform() to automatically + resolve translation objects + + The current language is used if ``language_code`` isn't specified. + """ + + def _transform(qs): + lang_ = language_code if language_code else translation.get_language() + + instance_dict = {} + + # Don't do anything for those who already have a cached translation + # available + for instance in qs: + trans = cache.get(instance.get_translation_cache_key(lang_)) + if trans: + if trans is _NoTranslation: + instance._cached_translation = None + else: + instance._cached_translation = trans + else: + instance_dict[instance.pk] = instance + + # We really, really need something in here to continue + if not instance_dict: + return + + candidates = list(instance_dict.values())[ + 0 + ].translations.model._default_manager.all() + + if instance_dict: + _process(candidates, instance_dict, lang_, "iexact") + if instance_dict: + _process(candidates, instance_dict, settings.LANGUAGE_CODE, "istartswith") + if instance_dict: + for candidate in candidates.filter(parent__pk__in=instance_dict.keys()): + if candidate.parent_id in instance_dict: + _found(instance_dict, candidate) + + # No translations for the rest + for instance in instance_dict.values(): + instance._cached_translation = None + + def _found(instance_dict, candidate): + parent = instance_dict[candidate.parent_id] + cache.set(parent.get_translation_cache_key(), candidate) + parent._cached_translation = candidate + candidate.parent = parent + del instance_dict[candidate.parent_id] + + def _process(candidates, instance_dict, lang_, op_): + candidates = candidates.filter( + Q(parent__pk__in=instance_dict.keys()), + Q(**{"language_code__" + op_: lang_}) + | Q(**{"language_code__" + op_: short_language_code(lang_)}), + ).order_by("-language_code") + + for candidate in candidates: + # The candidate's parent might already have a translation by now + if candidate.parent_id in instance_dict: + _found(instance_dict, candidate) + + return _transform + + +class TranslatedObjectManager(queryset_transform.TransformManager): """ This manager offers convenience methods. """ @@ -83,10 +160,12 @@ def only_language(self, language=short_language_code): Uses the currently active language by default. """ - return self.filter(translations__language_code=language) + return self.filter( + translations__language_code=(language() if callable(language) else language) + ) -class TranslatedObjectMixin(object): +class TranslatedObjectMixin: """ Mixin with helper methods. """ @@ -94,15 +173,19 @@ class TranslatedObjectMixin(object): def _get_translation_object(self, queryset, language_code): try: return queryset.filter( - Q(language_code=language_code) - | Q(language_code=short_language_code(language_code)) - ).order_by('-language_code')[0] + Q(language_code__iexact=language_code) + | Q(language_code__iexact=short_language_code(language_code)) + ).order_by("-language_code")[0] except IndexError: try: return queryset.filter( Q(language_code__istartswith=settings.LANGUAGE_CODE) - | Q(language_code__istartswith=short_language_code(settings.LANGUAGE_CODE)) - ).order_by('-language_code')[0] + | Q( + language_code__istartswith=short_language_code( + settings.LANGUAGE_CODE + ) + ) + ).order_by("-language_code")[0] except IndexError: try: return queryset.all()[0] @@ -110,14 +193,13 @@ def _get_translation_object(self, queryset, language_code): raise queryset.model.DoesNotExist def get_translation_cache_key(self, language_code=None): - """Return the cache key used to cache this object's translations so we can purge on-demand""" + """Return the cache key used to cache this object's translations so we + can purge on-demand""" if not language_code: language_code = translation.get_language() - return '-'.join(['%s' % s for s in - self._meta.db_table, - self.id, - language_code, - ]) + return ("FEINCMS:%d:XLATION:" % getattr(settings, "SITE_ID", 0)) + "-".join( + ["%s" % s for s in (self._meta.db_table, self.id, language_code)] + ) def get_translation(self, language_code=None): if not language_code: @@ -128,27 +210,39 @@ def get_translation(self, language_code=None): trans = cache.get(key) if trans is None: - trans = self._get_translation_object(self.translations.all(), language_code) + try: + trans = self._get_translation_object( + self.translations.all(), language_code + ) + except ObjectDoesNotExist: + trans = _NoTranslation cache.set(key, trans) + if trans is _NoTranslation: + return None + # Assign self to prevent additional database queries trans.parent = self return trans - translation = property(get_translation) + @property + def translation(self): + if not hasattr(self, "_cached_translation"): + self._cached_translation = self.get_translation() + return self._cached_translation @property def available_translations(self): - return self.translations.values_list('language_code', flat=True) + return self.translations.values_list("language_code", flat=True) - def __unicode__(self): + def __str__(self): try: translation = self.translation except ObjectDoesNotExist: return self.__class__.__name__ if translation: - return unicode(translation) + return "%s" % translation return self.__class__.__name__ @@ -160,6 +254,11 @@ def purge_translation_cache(self): for lang in self.available_translations: cache.delete(self.get_translation_cache_key(lang)) + try: + del self._cached_translation + except AttributeError: + pass + def Translation(model): """ @@ -167,22 +266,38 @@ def Translation(model): """ class Inner(models.Model): - parent = models.ForeignKey(model, related_name='translations') - language_code = models.CharField(_('language'), max_length=10, - choices=settings.LANGUAGES, default=settings.LANGUAGES[0][0], - editable=len(settings.LANGUAGES) > 1) + parent = models.ForeignKey( + model, related_name="translations", on_delete=models.CASCADE + ) + language_code = models.CharField( + _("language"), + max_length=10, + choices=settings.LANGUAGES, + default=settings.LANGUAGES[0][0], + editable=len(settings.LANGUAGES) > 1, + ) class Meta: + unique_together = ("parent", "language_code") + # (beware the above will not be inherited automatically if you + # provide a Meta class within your translation subclass) abstract = True def short_language_code(self): return short_language_code(self.language_code) def save(self, *args, **kwargs): - super(Inner, self).save(*args, **kwargs) + super().save(*args, **kwargs) + self.parent.purge_translation_cache() + save.alters_data = True + + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) self.parent.purge_translation_cache() + delete.alters_data = True + return Inner @@ -200,6 +315,7 @@ def admin_translationinline(model, inline_class=admin.StackedInline, **kwargs): ) """ - kwargs['max_num'] = len(settings.LANGUAGES) - kwargs['model'] = model - return type(model.__class__.__name__ + 'Inline', (inline_class,), kwargs) + kwargs["extra"] = 1 + kwargs["max_num"] = len(settings.LANGUAGES) + kwargs["model"] = model + return type(str(model.__class__.__name__ + "Inline"), (inline_class,), kwargs) diff --git a/feincms/urls.py b/feincms/urls.py index aca579814..da949533a 100644 --- a/feincms/urls.py +++ b/feincms/urls.py @@ -1,8 +1,11 @@ -from django.conf.urls.defaults import * +from django.urls import path, re_path -from feincms.views.base import handler +from feincms.views import Handler -urlpatterns = patterns('', - url(r'^$', handler, name='feincms_home'), - url(r'^(.*)/$', handler, name='feincms_handler'), -) + +handler = Handler.as_view() + +urlpatterns = [ + path("", handler, name="feincms_home"), + re_path(r"^(.*)/$", handler, name="feincms_handler"), +] diff --git a/feincms/utils/__init__.py b/feincms/utils/__init__.py index f1faf312a..0a761f61f 100644 --- a/feincms/utils/__init__.py +++ b/feincms/utils/__init__.py @@ -1,185 +1,79 @@ # ------------------------------------------------------------------------ -# coding=utf-8 # ------------------------------------------------------------------------ -""" -Prefilled attributes -==================== +import re +from importlib import import_module -The two functions prefilled_attribute and prefill_entry_list help you avoid -massive amounts of database queries when displaying a list of CMS items with -content objects. This is especially useful if f.e. your blog content is derived -from FeinCMS and you want to show a list of recent blog entries. +from django.apps import apps +from django.core.exceptions import ImproperlyConfigured +from django.db.models import AutoField, CharField -Example:: +from feincms import settings - from django.utils.translation import ugettext_lazy as _ - - from feincms.content.image.models import ImageContent - from feincms.content.richtext.models import RichTextContent - from feincms.models import Base - from feincms.utils import prefilled_attribute, prefill_entry_list - - class Author(models.Model): - # ... - - class Entry(Base): - authors = models.ManyToManyField - - author_list = prefilled_attr('authors') - richtexts = prefilled_attr('richtextcontent_set') - images = prefilled_attr('imagecontent_set') - - Entry.create_content_type(RichTextContent) - Entry.create_content_type(ImageContent, POSITION_CHOICES=( - ('block', _('block')), - ('left', _('left')), - ('right', _('right')), - ) - - -Then, inside your view function or inside a template tag, call -prefill_entry_list with the attribute names:: - - prefill_entry_list(queryset, 'authors', 'richtextcontent_set', 'imagecontent_set') - -or:: - - {% load feincms_tags %} - {% feincms_prefill_entry_list object_list "authors,richtextcontent_set,imagecontent_set" %} -""" - -from django.db import connection -from django.db.models import AutoField -from django.db.models.fields import related -from django.utils.importlib import import_module # ------------------------------------------------------------------------ def get_object(path, fail_silently=False): # Return early if path isn't a string (might already be an callable or # a class or whatever) - if not isinstance(path, (str, unicode)): + if not isinstance(path, str): # XXX bytes? return path try: - dot = path.rindex('.') - mod, fn = path[:dot], path[dot+1:] - except ValueError: - mod, fn = callback, '' + return import_module(path) + except ImportError: + try: + dot = path.rindex(".") + mod, fn = path[:dot], path[dot + 1 :] - try: - return getattr(import_module(mod), fn) - except (AttributeError, ImportError): - if not fail_silently: - raise - -# ------------------------------------------------------------------------ -def prefilled_attribute(name): - key = '_prefill_%s' % name - - def _prop(self): - if not hasattr(self, key): - setattr(self, key, list(getattr(self, name).all())) - - return getattr(self, key) - - return property(_prop) + return getattr(import_module(mod), fn) + except (AttributeError, ImportError): + if not fail_silently: + raise -# ------------------------------------------------------------------------ -def collect_dict_values(data): - dic = {} - for key, value in data: - dic.setdefault(key, []).append(value) - return dic # ------------------------------------------------------------------------ -def prefill_entry_list(queryset, *attrs, **kwargs): - """ - Prefill a queryset with related data. Instead of querying the related tables - over and over for every single entry of the queryset, the absolute minimum of - queries is performed per related field, one for reverse foreign keys, two for - many to many fields. The returned data is assigned to the individual entries - afterwards, where it can be made easily accessible by using the - prefilled_attribute property generator above. - - You may optionally pass a region argument here, which will be applied to - reverse foreign key relations. This is obviously most useful for fetching - content objects. +def get_model_instance(app_label, model_name, pk): """ + Find an object instance given an app_label, a model name and the + object's pk. - region = kwargs.get('region', None) - - # Evaluate queryset. We need a list of objects, because we need to iterate over - # to find out - queryset = list(queryset) - - if not queryset: - return queryset - - # Get an arbitrary object of the queryset. We need this to determine the field - # type alter - arbitrary = queryset[0] - cls = arbitrary.__class__ - - from_fk = [] - from_m2m = [] - - for attr in attrs: - related_model = getattr(arbitrary, attr).model - descriptor = getattr(cls, attr) - - if isinstance(descriptor, related.ReverseManyRelatedObjectsDescriptor): - # Process many to many fields - f = arbitrary._meta.get_field(attr) - qn = connection.ops.quote_name - - # Query the table linking the two models - sql = 'SELECT DISTINCT %s, %s FROM %s WHERE %s in (%s)' % ( - qn(f.m2m_column_name()), - qn(f.m2m_reverse_name()), - qn(f.m2m_db_table()), - qn(f.m2m_column_name()), - ', '.join(['%s'] * len(queryset))) + This is used for page's get_link_target but can be used for other + content types that accept e.g. either an internal or external link. + """ - cursor = connection.cursor() - cursor.execute(sql, [entry.id for entry in queryset]) - mapping = cursor.fetchall() + model = apps.get_model(app_label, model_name) + if not model: + return None - # Get all related models which are linked with any entry in the queryset - related_objects = dict((o.id, o) for o in related_model.objects.filter( - id__in=[v for k, v in mapping])) + try: + instance = model._default_manager.get(pk=pk) + return instance + except model.DoesNotExist: + pass - assigned_objects = {} + return None - for entry, obj_id in mapping: - assigned_objects.setdefault(entry, set()).add(related_objects[obj_id]) - from_m2m.append((attr, assigned_objects)) - else: - # Process reverse foreign keys +# ------------------------------------------------------------------------ +REDIRECT_TO_RE = re.compile(r"^(?P<app_label>\w+).(?P<model_name>\w+):(?P<pk>\d+)$") - related_queryset = related_model.objects.filter( - parent__in=queryset).select_related('parent', 'region').order_by('ordering') - # Apply region filtering if a region has been passed - # We do not need to apply the same filtering to m2m relations, because - # the region field (as we know it) only exists for content types created - # using create_content_type - if region: - related_queryset = related_queryset.filter(region=region) +def match_model_string(s): + """ + Try to parse a string in format "app_label.model_name:pk", as is used + Page.get_link_target() - from_fk.append((attr, - collect_dict_values((o.parent_id, o) for o in related_queryset))) + Returns a tuple app_label, model_name, pk or None if the string + does not match the expected format. + """ - # Assign the collected values onto the individual queryset objects - for entry in queryset: - for attr, dic in from_fk: - setattr(entry, '_prefill_%s' % attr, dic.get(entry.id, [])) - for attr, dic in from_m2m: - setattr(entry, '_prefill_%s' % attr, dic.get(entry.id, [])) + match = REDIRECT_TO_RE.match(s) + if not match: + return None + matches = match.groupdict() + return (matches["app_label"], matches["model_name"], int(matches["pk"])) - return queryset # ------------------------------------------------------------------------ def copy_model_instance(obj, exclude=None): @@ -189,28 +83,87 @@ def copy_model_instance(obj, exclude=None): """ exclude = exclude or () - initial = dict([(f.name, getattr(obj, f.name)) - for f in obj._meta.fields - if not isinstance(f, AutoField) and \ - not f.name in exclude and \ - not f in obj._meta.parents.values()]) + initial = { + f.name: getattr(obj, f.name) + for f in obj._meta.fields + if not isinstance(f, AutoField) + and f.name not in exclude + and f not in obj._meta.parents.values() + } return obj.__class__(**initial) + # ------------------------------------------------------------------------ -def shorten_string(str, max_length = 50): +def shorten_string(str, max_length=50, ellipsis=" … "): """ Shorten a string for display, truncate it intelligently when too long. - Try to cut it in 2/3 + ellipsis + 1/3 of the original title. The first part - also try to cut at white space instead of in mid-word. + Try to cut it in 2/3 + ellipsis + 1/3 of the original title. Also try to + cut the first part off at a white space boundary instead of in mid-word. """ if len(str) >= max_length: first_part = int(max_length * 0.6) - next_space = str[first_part:(max_length / 2 - first_part)].find(' ') - if next_space >= 0: + next_space = str[first_part : (max_length // 2 - first_part)].find(" ") + if next_space >= 0 and first_part + next_space + len(ellipsis) < max_length: first_part += next_space - return str[:first_part] + u' … ' + str[-(max_length - first_part):] + return ( + str[:first_part] + + ellipsis + + str[-(max_length - first_part - len(ellipsis)) :] + ) return str + # ------------------------------------------------------------------------ -# ------------------------------------------------------------------------ +def get_singleton(template_key, cls=None, raise_exception=True): + cls = cls or settings.FEINCMS_DEFAULT_PAGE_MODEL + try: + model = apps.get_model(*cls.split(".")) + if not model: + raise ImproperlyConfigured('Cannot load model "%s"' % cls) + try: + assert model._feincms_templates[template_key].singleton + except AttributeError as e: + raise ImproperlyConfigured( + f"{model!r} does not seem to be a valid FeinCMS base class ({e!r})" + ) + except KeyError: + raise ImproperlyConfigured( + f"{template_key!r} is not a registered template for {model!r}!" + ) + except AssertionError: + raise ImproperlyConfigured( + f"{template_key!r} is not a *singleton* template for {model!r}!" + ) + try: + return model._default_manager.get(template_key=template_key) + except model.DoesNotExist: + raise # not yet created? + except model.MultipleObjectsReturned: + raise # hmm, not exactly a singleton... + except Exception: + if raise_exception: + raise + else: + return None + + +def get_singleton_url(template_key, cls=None, raise_exception=True): + obj = get_singleton(template_key, cls, raise_exception) + return obj.get_absolute_url() if obj else "#broken-link" + + +class ChoicesCharField(CharField): + """ + ``models.CharField`` with choices, which makes the migration framework + always ignore changes to ``choices``, ever. + """ + + def __init__(self, *args, **kwargs): + kwargs.setdefault("choices", [("", "")]) # Non-empty choices for get_*_display + super().__init__(*args, **kwargs) + + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + kwargs["choices"] = [("", "")] + return name, "django.db.models.CharField", args, kwargs diff --git a/feincms/utils/html/__init__.py b/feincms/utils/html/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/feincms/utils/html/cleanse.py b/feincms/utils/html/cleanse.py deleted file mode 100644 index d55348e85..000000000 --- a/feincms/utils/html/cleanse.py +++ /dev/null @@ -1,158 +0,0 @@ -import lxml.html -import lxml.html.clean -import re - - -cleanse_html_allowed = { - 'a': ('href', 'name', 'target', 'title'), - 'h2': (), - 'h3': (), - 'strong': (), - 'em': (), - 'p': (), - 'ul': (), - 'ol': (), - 'li': (), - 'span': (), - 'br': (), - 'sub': (), - 'sup': (), - 'anything': (), - } - -cleanse_html_allowed_empty_tags = ('br',) - -cleanse_html_merge = ('h2', 'h3', 'strong', 'em', 'ul', 'ol', 'sub', 'sup') - - -def cleanse_html(html): - """ - Clean HTML code from ugly copy-pasted CSS and empty elements - - Removes everything not explicitly allowed in ``cleanse_html_allowed``. - - Requires ``lxml`` and ``beautifulsoup``. - """ - - doc = lxml.html.fromstring('<anything>%s</anything>' % html) - try: - ignore = lxml.html.tostring(doc, encoding=unicode) - except UnicodeDecodeError: - # fall back to slower BeautifulSoup if parsing failed - from lxml.html import soupparser - doc = soupparser.fromstring(u'<anything>%s</anything>' % html) - - cleaner = lxml.html.clean.Cleaner( - allow_tags=cleanse_html_allowed.keys() + ['style'], - remove_unknown_tags=False, # preserve surrounding 'anything' tag - style=False, safe_attrs_only=False, # do not strip out style - # attributes; we still need - # the style information to - # convert spans into em/strong - # tags - ) - - cleaner(doc) - - # walk the tree recursively, because we want to be able to remove - # previously emptied elements completely - for element in reversed(list(doc.iterdescendants())): - if element.tag == 'style': - element.drop_tree() - continue - - # convert span elements into em/strong if a matching style rule - # has been found. strong has precedence, strong & em at the same - # time is not supported - elif element.tag == 'span': - style = element.attrib.get('style') - if style: - if 'bold' in style: - element.tag = 'strong' - elif 'italic' in style: - element.tag = 'em' - - if element.tag == 'span': # still span - element.drop_tag() # remove tag, but preserve children and text - continue - - # remove empty tags if they are not <br /> - elif not element.text and element.tag not in \ - cleanse_html_allowed_empty_tags and not \ - len(list(element.iterdescendants())): - element.drop_tag() - continue - - # remove all attributes which are not explicitly allowed - allowed = cleanse_html_allowed.get(element.tag, []) - for key in element.attrib.keys(): - if key not in allowed: - del element.attrib[key] - - # just to be sure, run cleaner again, but this time with even more - # strict settings - cleaner = lxml.html.clean.Cleaner( - allow_tags=cleanse_html_allowed.keys(), - remove_unknown_tags=False, # preserve surrounding 'anything' tag - style=True, safe_attrs_only=True - ) - - cleaner(doc) - - html = lxml.html.tostring(doc, method='xml') - - # remove all sorts of newline characters - html = html.replace('\n', ' ').replace('\r', ' ') - html = html.replace(' ', ' ').replace(' ', ' ') - html = html.replace(' ', ' ').replace(' ', ' ') - - # remove wrapping tag needed by XML parser - html = re.sub(r'</?anything>', '', html) - - # remove elements containing only whitespace or linebreaks - whitespace_re = re.compile(r'<([a-z0-9]+)>(<br\s*/>|\ |\ |\s)*</\1>') - while True: - new = whitespace_re.sub('', html) - if new == html: - break - html = new - - # merge tags - for tag in cleanse_html_merge: - merge_str = u'</%s><%s>' - while True: - new = html.replace(merge_str, u'') - if new == html: - break - html = new - - # fix p-in-p tags - p_in_p_start_re = re.compile(r'<p>(\ |\ |\s)*<p>') - p_in_p_end_re = re.compile('</p>(\ |\ |\s)*</p>') - - for tag in cleanse_html_merge: - merge_start_re = re.compile('<p>(\\ |\\ |\\s)*<%s>(\\ |\\ |\\s)*<p>' % tag) - merge_end_re = re.compile('</p>(\\ |\\ |\\s)*</%s>(\\ |\\ |\\s)*</p>' % tag) - - while True: - new = merge_start_re.sub('<p>', html) - new = merge_end_re.sub('</p>', new) - new = p_in_p_start_re.sub('<p>', new) - new = p_in_p_end_re.sub('</p>', new) - - if new == html: - break - html = new - - # remove list markers with <li> tags before them - html = re.sub(r'<li>(\ |\ |\s)*(-|\*|·)(\ |\ |\s)*', '<li>', html) - - # remove p-in-li tags - html = re.sub(r'<li>(\ |\ |\s)*<p>', '<li>', html) - html = re.sub(r'</p>(\ |\ |\s)*</li>', '</li>', html) - - # add a space before the closing slash in empty tags - html = re.sub(r'<([^/>]+)/>', r'<\1 />', html) - - return html - diff --git a/feincms/utils/html/tidy.py b/feincms/utils/html/tidy.py deleted file mode 100644 index 00ab3b696..000000000 --- a/feincms/utils/html/tidy.py +++ /dev/null @@ -1,83 +0,0 @@ -# encoding: utf-8 - -import re -import tidylib - -# Based on http://stackoverflow.com/questions/92438/stripping-non-printable-characters-from-a-string-in-python -# -# We omit chars 9-13 (tab, newline, vertical tab, form feed, return) and 32 -# (space) to avoid clogging our reports with warnings about common, -# non-problematic codes but still allow stripping things which will cause lxml -# to choke - -CONTROL_CHAR_RE = re.compile('[%s]' % "".join( - re.escape(unichr(c)) for c in range(0, 8) + range(14, 31) + range(127, 160) -)) - - -def tidy_html(html): - """ - Process an input string containing HTML and return a tuple (xhtml, - errors, warnings) containing the output of tidylib and lists of - validation errors and warnings. - - Input must be unicode. - Output will be valid XHTML. - """ - if not isinstance(html, unicode): - raise ValueError("tidyhtml must be called with a Unicode string!") - - warnings = list() - - # First, deal with embedded control codes: - html, sub_count = CONTROL_CHAR_RE.subn(" ", html) - if sub_count: - warnings.append("Stripped %d control characters from body: %s" % ( - sub_count, - set(ord(i) for i in CONTROL_CHAR_RE.findall(html)) - )) - - # tidylib.tidy_fragment will choke if given a full HTML document. This is a - # primitive content sniff to decide whether to call tidy_document instead: - if "<html" in html[:1024]: - tidy_f = tidylib.tidy_document - doc_mode = True - else: - tidy_f = tidylib.tidy_fragment - doc_mode = False - - html, messages = tidy_f( - html.strip(), - { - "char-encoding": "utf8", - "clean": False, - "drop-empty-paras": False, - "drop-font-tags": True, - "drop-proprietary-attributes": False, - "fix-backslash": True, - "indent": True, - "output-xhtml": True, - } - ) - - messages = filter(None, (l.strip() for l in messages.split("\n") if l)) - - # postprocess warnings to avoid HTML fragments being reported as lacking - # doctype and title: - errors = list() - warnings = list() - - for msg in messages: - if not doc_mode and "Warning: missing <!DOCTYPE> declaration" in msg: - continue - if not doc_mode and "Warning: inserting missing 'title' element" in msg: - continue - if not doc_mode and "Warning: inserting implicit <body>" in msg: - continue - - if "Error:" in msg: - errors.append(msg) - else: - warnings.append(msg) - - return html, errors, warnings diff --git a/feincms/utils/managers.py b/feincms/utils/managers.py new file mode 100644 index 000000000..ffa7d6375 --- /dev/null +++ b/feincms/utils/managers.py @@ -0,0 +1,53 @@ +# ------------------------------------------------------------------------ +class ActiveAwareContentManagerMixin: + """ + Implement what's necessary to add some kind of "active" state for content + objects. The notion of active is defined by a number of filter rules that + must all match (AND) for the object to be active. + + A Manager for a content class using the "datepublisher" extension + should either adopt this mixin or implement a similar interface. + """ + + # A dict of filters which are used to determine whether a page is active or + # not. Extended for example in the datepublisher extension (date-based + # publishing and un-publishing of pages). This will be set in + # add_to_active_filters() below, so we won't share the same dict for + # derived managers, do not replace with {} here! + active_filters = None + + @classmethod + def apply_active_filters(cls, queryset): + """ + Apply all filters defined to the queryset passed and return the result. + """ + if cls.active_filters is not None: + for filt in cls.active_filters.values() or (): + if callable(filt): + queryset = filt(queryset) + else: + queryset = queryset.filter(filt) + + return queryset + + @classmethod + def add_to_active_filters(cls, filter, key=None): + """ + Add a new clause to the active filters. A filter may be either + a Q object to be applied to the content class or a callable taking + a queryset and spitting out a new one. + + If a filter with the given `key` already exists, the new filter + replaces the old. + """ + if cls.active_filters is None: + cls.active_filters = {} + if key is None: + key = filter + cls.active_filters[key] = filter + + def active(self): + """ + Return only currently active objects. + """ + return self.apply_active_filters(self) diff --git a/feincms/utils/queryset_transform.py b/feincms/utils/queryset_transform.py new file mode 100644 index 000000000..f2c3e60c4 --- /dev/null +++ b/feincms/utils/queryset_transform.py @@ -0,0 +1,120 @@ +# Straight import from https://github.com/simonw/django-queryset-transform + +""" +django_queryset_transform +========================= + +Allows you to register a transforming map function with a Django QuerySet +that will be executed only when the QuerySet itself has been evaluated. + +This allows you to build optimisations like "fetch all tags for these 10 rows" +while still benefiting from Django's lazy QuerySet evaluation. + +For example:: + + def lookup_tags(item_qs): + item_pks = [item.pk for item in item_qs] + m2mfield = Item._meta.get_field('tags')[0] + tags_for_item = Tag.objects.filter( + item__in = item_pks + ).extra(select = { + 'item_id': '%s.%s' % ( + m2mfield.m2m_db_table(), m2mfield.m2m_column_name() + ) + }) + tag_dict = {} + for tag in tags_for_item: + tag_dict.setdefault(tag.item_id, []).append(tag) + for item in item_qs: + item.fetched_tags = tag_dict.get(item.pk, []) + + qs = Item.objects.filter(name__contains = 'e').transform(lookup_tags) + + for item in qs: + print(item, item.fetched_tags) + +Prints:: + + Winter comes to Ogglesbrook [<sledging>, <snow>, <winter>, <skating>] + Summer now [<skating>, <sunny>] + +But only executes two SQL queries - one to fetch the items, and one to fetch +ALL of the tags for those items. + +Since the transformer function can transform an evaluated QuerySet, it +doesn't need to make extra database calls at all - it should work for things +like looking up additional data from a cache.multi_get() as well. + +Originally inspired by http://github.com/lilspikey/django-batch-select/ + + + +LICENSE +======= + +Copyright (c) 2010, Simon Willison. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from django.db import models + + +class TransformQuerySet(models.query.QuerySet): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._transform_fns = [] + self._orig_iterable_class = getattr(self, "_iterable_class", None) + + def _clone(self, *args, **kwargs): + c = super()._clone(*args, **kwargs) + c._transform_fns = self._transform_fns[:] + return c + + def transform(self, *fn): + c = self._clone() + c._transform_fns.extend(fn) + return c + + def _fetch_all(self): + super()._fetch_all() + if getattr(self, "_iterable_class", None) == self._orig_iterable_class: # noqa + for fn in self._transform_fns: + fn(self._result_cache) + + +if hasattr(models.Manager, "from_queryset"): + TransformManager = models.Manager.from_queryset(TransformQuerySet) + +else: + + class TransformManager(models.Manager): + def get_queryset(self): + return TransformQuerySet(self.model, using=self._db) + + def transform(self, *fn): + return self.get_queryset().transform(*fn) diff --git a/feincms/utils/templatetags.py b/feincms/utils/templatetags.py index 2e4a849f9..1fa11470a 100644 --- a/feincms/utils/templatetags.py +++ b/feincms/utils/templatetags.py @@ -1,4 +1,4 @@ -''' +""" I really hate repeating myself. These are helpers that avoid typing the whole thing over and over when implementing additional template tags @@ -7,16 +7,17 @@ {% tag as var_name %} {% tag of template_var as var_name %} {% tag of template_var as var_name arg1,arg2,kwarg3=4 %} -''' +""" from django import template + def _parse_args(argstr, context=None): try: args = {} - for token in argstr.split(','): - if '=' in token: - k, v = token.split('=', 1) + for token in argstr.split(","): + if "=" in token: + k, v = token.split("=", 1) if context: try: args[k] = template.Variable(v).resolve(context) @@ -30,111 +31,8 @@ def _parse_args(argstr, context=None): return args except TypeError: - raise template.TemplateSyntaxError('Malformed arguments') - -def do_simple_node_with_var_and_args_helper(cls): - def _func(parser, token): - try: - tag_name, of_, in_var_name, args = token.contents.split() - except ValueError: - try: - tag_name, of_, in_var_name = token.contents.split() - args = '' - except ValueError: - raise template.TemplateSyntaxError, 'Invalid syntax for %s node: %s' % (cls.__name__, token.contents) - - return cls(tag_name, in_var_name, args) - - return _func - -class SimpleNodeWithVarAndArgs(template.Node): - def __init__(self, tag_name, in_var_name, args): - self.tag_name = tag_name - self.in_var = template.Variable(in_var_name) - self.args = args - - def render(self, context): - self.render_context = context - try: - instance = self.in_var.resolve(context) - except template.VariableDoesNotExist: - return '' - - return self.what(instance, _parse_args(self.args, context)) - -def do_simple_node_with_var_helper(cls): - def _func(parser, token): - try: - tag_name, of_, in_var_name = token.contents.split() - except ValueError: - raise template.TemplateSyntaxError, 'Invalid syntax for %s node: %s' % (cls.__name__, token.contents) - - return cls(tag_name, in_var_name) - - return _func + raise template.TemplateSyntaxError("Malformed arguments") -class SimpleNodeWithVar(template.Node): - def __init__(self, tag_name, in_var_name): - self.tag_name = tag_name - self.in_var = template.Variable(in_var_name) - - def render(self, context): - self.render_context = context - try: - instance = self.in_var.resolve(context) - except template.VariableDoesNotExist: - return '' - - return self.what(instance) - -def do_simple_assignment_node_helper(cls): - def _func(parser, token): - try: - tag_name, as_, var_name = token.contents.split() - except ValueError: - raise template.TemplateSyntaxError, 'Invalid syntax for %s node: %s' % (cls.__name__, token.contents) - - return cls(tag_name, var_name) - - return _func - -class SimpleAssignmentNode(template.Node): - def __init__(self, tag_name, var_name): - self.tag_name = tag_name - self.var_name = var_name - - def render(self, context): - self.render_context = context - context[self.var_name] = self.what() - return '' - -def do_simple_assignment_node_with_var_helper(cls): - def _func(parser, token): - try: - tag_name, of_, in_var_name, as_, var_name = token.contents.split() - except ValueError: - raise template.TemplateSyntaxError, 'Invalid syntax for %s node: %s' % (cls.__name__, token.contents) - - return cls(tag_name, in_var_name, var_name) - - return _func - -class SimpleAssignmentNodeWithVar(template.Node): - def __init__(self, tag_name, in_var_name, var_name): - self.tag_name = tag_name - self.in_var = template.Variable(in_var_name) - self.var_name = var_name - - def render(self, context): - self.render_context = context - try: - instance = self.in_var.resolve(context) - except template.VariableDoesNotExist: - context[self.var_name] = [] - return '' - - context[self.var_name] = self.what(instance) - return '' def do_simple_assignment_node_with_var_and_args_helper(cls): def _func(parser, token): @@ -143,14 +41,17 @@ def _func(parser, token): except ValueError: try: tag_name, of_, in_var_name, as_, var_name = token.contents.split() - args = '' + args = "" except ValueError: - raise template.TemplateSyntaxError, 'Invalid syntax for %s node: %s' % (cls.__name__, token.contents) + raise template.TemplateSyntaxError( + f"Invalid syntax for {cls.__name__} node: {token.contents}" + ) return cls(tag_name, in_var_name, var_name, args) return _func + class SimpleAssignmentNodeWithVarAndArgs(template.Node): def __init__(self, tag_name, in_var_name, var_name, args): self.tag_name = tag_name @@ -164,9 +65,8 @@ def render(self, context): instance = self.in_var.resolve(context) except template.VariableDoesNotExist: context[self.var_name] = [] - return '' + return "" context[self.var_name] = self.what(instance, _parse_args(self.args, context)) - return '' - + return "" diff --git a/feincms/utils/tuple.py b/feincms/utils/tuple.py new file mode 100644 index 000000000..4d53231ad --- /dev/null +++ b/feincms/utils/tuple.py @@ -0,0 +1,20 @@ +""" +Content types in FeinCMS 1.x returned a HTML fragment from ``render()`` +methods. + +FeinCMS 22.x changed them to return a ``(template_name, context)`` tuple. This +allows rendering content type templates while also having access to all +template variables set in views and context processors without having to +explicitly pass them on or something. + +Some old projects still rely on ``render()`` returning a HTML string. This +tuple subclass combines the advantages of both approaches: Newer projects see a +tuple, old projects still get a string. +""" + +from django.template.loader import render_to_string + + +class AutoRenderTuple(tuple): + def __str__(self): + return render_to_string(self[0], self[1]) diff --git a/feincms/views/__init__.py b/feincms/views/__init__.py index e69de29bb..26c35eca7 100644 --- a/feincms/views/__init__.py +++ b/feincms/views/__init__.py @@ -0,0 +1,69 @@ +import logging + +from django.apps import apps +from django.http import Http404 +from django.utils.functional import cached_property + +from feincms import settings +from feincms.module.mixins import ContentView + + +logger = logging.getLogger(__name__) + + +class Handler(ContentView): + page_model_path = None + context_object_name = "feincms_page" + + @cached_property + def page_model(self): + model = self.page_model_path or settings.FEINCMS_DEFAULT_PAGE_MODEL + return apps.get_model(*model.split(".")) + + def get_object(self): + path = None + if "path" in self.kwargs: + path = self.kwargs["path"] + elif self.args: + path = self.args[0] + return self.page_model._default_manager.for_request( + self.request, raise404=True, best_match=True, path=path + ) + + def dispatch(self, request, *args, **kwargs): + try: + return super().dispatch(request, *args, **kwargs) + except Http404 as e: + if settings.FEINCMS_CMS_404_PAGE is not None: + logger.info( + "Http404 raised for '%s', attempting redirect to" + " FEINCMS_CMS_404_PAGE", + args[0], + ) + try: + # Fudge environment so that we end up resolving the right + # page. + # Note: request.path is used by the page redirect processor + # to determine if the redirect can be taken, must be == to + # page url. + # Also clear out the _feincms_page attribute which caches + # page lookups (and would just re-raise a 404). + request.path = request.path_info = settings.FEINCMS_CMS_404_PAGE + if hasattr(request, "_feincms_page"): + delattr(request, "_feincms_page") + response = super().dispatch( + request, settings.FEINCMS_CMS_404_PAGE, **kwargs + ) + # Only set status if we actually have a page. If we get for + # example a redirect, overwriting would yield a blank page + if response.status_code == 200: + response.status_code = 404 + return response + except Http404: + logger.error( + "Http404 raised while resolving FEINCMS_CMS_404_PAGE=%s", + settings.FEINCMS_CMS_404_PAGE, + ) + raise e + else: + raise diff --git a/feincms/views/base.py b/feincms/views/base.py deleted file mode 100644 index c7975543a..000000000 --- a/feincms/views/base.py +++ /dev/null @@ -1,109 +0,0 @@ -from django.http import Http404 -from django.shortcuts import render_to_response -from django.template import RequestContext -from django.utils.cache import add_never_cache_headers - -from feincms import settings -from feincms.module.page.models import Page - - -class Handler(object): - """ - This is the default handler for feincms page content. - - It isn't a class-based-view like those in Django's generic view framework. - State should not be stored on the ``Handler`` class, because of thread-safety - and cross polination issues. - """ - - def __call__(self, request, path=None): - return self.build_response(request, - Page.objects.best_match_for_path(path or request.path, raise404=True)) - - def build_response(self, request, page): - """ - Calls `prepare`, `render` and `finalize`, in this order. - """ - - response = self.prepare(request, page) - if response: - return response - - response = self.render(request, page) - return self.finalize(request, response, page) - - def prepare(self, request, page): - """ - Prepare / pre-process content types. If this method returns anything, - it is treated as a ``HttpResponse`` and handed back to the visitor. - """ - - response = page.setup_request(request) - if response: - return response - - http404 = None # store eventual Http404 exceptions for re-raising, - # if no content type wants to handle the current request - successful = False # did any content type successfully end processing? - - for content in page.content.all_of_type(tuple(page._feincms_content_types_with_process)): - try: - r = content.process(request) - if r in (True, False): - successful = r - elif r: - return r - except Http404, e: - http404 = e - - if not successful: - if http404: - # re-raise stored Http404 exception - raise http404 - - if not settings.FEINCMS_ALLOW_EXTRA_PATH and \ - request._feincms_extra_context['extra_path'] != '/': - raise Http404 - - def render(self, request, page): - """ - The render step. Must return a HttpResponse. - """ - - # This facility can be used by request processors to add values - # to the context. - context = request._feincms_extra_context - context['feincms_page'] = page - - return render_to_response(page.template.path, - context_instance=RequestContext(request, context)) - - def finalize(self, request, response, page): - """ - Runs finalize() on content types having such a method, adds headers and - returns the final response. - """ - - for content in page.content.all_of_type(tuple(page._feincms_content_types_with_finalize)): - r = content.finalize(request, response) - if r: - return r - - page.finalize_response(request, response) - - # Add never cache headers in case frontend editing is active - if hasattr(request, "session") and request.session.get('frontend_editing', False): - add_never_cache_headers(response) - - return response - - @property - def __name__(self): - """ - Dummy property to make this handler behave like a normal function. - This property is used by django-debug-toolbar - """ - return self.__class__.__name__ - -#: Default handler -handler = Handler() diff --git a/feincms/views/cbv/__init__.py b/feincms/views/cbv/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/feincms/views/cbv/urls.py b/feincms/views/cbv/urls.py deleted file mode 100644 index cf7ff95c1..000000000 --- a/feincms/views/cbv/urls.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.conf.urls.defaults import * - -from feincms.views.cbv.views import Handler -handler = Handler.as_view() - -urlpatterns = patterns('', - url(r'^$', handler, name='feincms_home'), - url(r'^(.*)/$', handler, name='feincms_handler'), -) diff --git a/feincms/views/cbv/views.py b/feincms/views/cbv/views.py deleted file mode 100644 index b6dcbb98c..000000000 --- a/feincms/views/cbv/views.py +++ /dev/null @@ -1,96 +0,0 @@ -from django.http import Http404 -from django.utils.cache import add_never_cache_headers -from django.views.generic import TemplateView - -from feincms import settings -from feincms.module.page.models import Page - - -class Handler(TemplateView): - """ - Class-based handler for FeinCMS page content - """ - - def get(self, request, *args, **kwargs): - return self.handler(request, *args, **kwargs) - def post(self, request, *args, **kwargs): - return self.handler(request, *args, **kwargs) - - def handler(self, request, path=None, *args, **kwargs): - self.page = Page.objects.for_request(request, raise404=True, best_match=True, setup=False) - response = self.prepare() - if response: - return response - - response = self.render_to_response(self.get_context_data()) - return self.finalize(response) - - def get_template_names(self): - if self.template_name is None: - return [self.page.template.path] - return [self.template_name] - - def get_context_data(self, **kwargs): - context = self.request._feincms_extra_context - context['feincms_page'] = self.page - return context - - def prepare(self): - """ - Prepare / pre-process content types. If this method returns anything, - it is treated as a ``HttpResponse`` and handed back to the visitor. - """ - - response = self.page.setup_request(self.request) - if response: - return response - - http404 = None # store eventual Http404 exceptions for re-raising, - # if no content type wants to handle the current self.request - successful = False # did any content type successfully end processing? - - for content in self.page.content.all_of_type(tuple(self.page._feincms_content_types_with_process)): - try: - r = content.process(self.request, view=self) - if r in (True, False): - successful = r - elif r: - return r - except Http404, e: - http404 = e - - if not successful: - if http404: - # re-raise stored Http404 exception - raise http404 - - if not settings.FEINCMS_ALLOW_EXTRA_PATH and \ - self.request._feincms_extra_context['extra_path'] != '/': - raise Http404 - - def finalize(self, response): - """ - Runs finalize() on content types having such a method, adds headers and - returns the final response. - """ - - for content in self.page.content.all_of_type(tuple(self.page._feincms_content_types_with_finalize)): - r = content.finalize(self.request, response) - if r: - return r - - self.page.finalize_response(self.request, response) - - # Add never cache headers in case frontend editing is active - if hasattr(self.request, "session") and self.request.session.get('frontend_editing', False): - add_never_cache_headers(response) - - return response - - @property - def __name__(self): - """ - Dummy property to make this handler behave like a normal function. - This property is used by django-debug-toolbar - """ - return self.__class__.__name__ diff --git a/feincms/views/decorators.py b/feincms/views/decorators.py index 02490891a..900c78f29 100644 --- a/feincms/views/decorators.py +++ b/feincms/views/decorators.py @@ -1,37 +1,10 @@ -from django.http import HttpResponse -try: - from functools import wraps -except ImportError: - from django.utils.functional import wraps +# flake8: noqa -from feincms.module.page.models import Page +import warnings -def add_page_to_extra_context(view_func): - """ - Adds the best-match page to the extra_context keyword argument. Mainly used - to provide generic views which integrate into the page module. - """ - - def inner(request, *args, **kwargs): - kwargs.setdefault('extra_context', {}) - kwargs['extra_context']['feincms_page'] = Page.objects.for_request( - request, best_match=True) - - return view_func(request, *args, **kwargs) - return wraps(view_func)(inner) - - -def standalone(view_func): - """ - Marks the view method as standalone view; this means that - ``HttpResponse`` objects returned from ``ApplicationContent`` - are returned directly, without further processing. - """ - - def inner(request, *args, **kwargs): - response = view_func(request, *args, **kwargs) - if isinstance(response, HttpResponse): - response.standalone = True - return response - return wraps(view_func)(inner) +warnings.warn( + "Import ApplicationContent and friends from feincms.content.application.models", + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/views/generic/__init__.py b/feincms/views/generic/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/feincms/views/generic/create_update.py b/feincms/views/generic/create_update.py deleted file mode 100644 index 8ffe4c75f..000000000 --- a/feincms/views/generic/create_update.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.views.generic import create_update -from feincms.views.decorators import add_page_to_extra_context - - -create_object = add_page_to_extra_context(create_update.create_object) -update_object = add_page_to_extra_context(create_update.update_object) -delete_object = add_page_to_extra_context(create_update.delete_object) - diff --git a/feincms/views/generic/date_based.py b/feincms/views/generic/date_based.py deleted file mode 100644 index 1b0474ad8..000000000 --- a/feincms/views/generic/date_based.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.views.generic import date_based -from feincms.views.decorators import add_page_to_extra_context - - -archive_index = add_page_to_extra_context(date_based.archive_index) -archive_year = add_page_to_extra_context(date_based.archive_year) -archive_month = add_page_to_extra_context(date_based.archive_month) -archive_week = add_page_to_extra_context(date_based.archive_week) -archive_day = add_page_to_extra_context(date_based.archive_day) -archive_today = add_page_to_extra_context(date_based.archive_today) -object_detail = add_page_to_extra_context(date_based.object_detail) - diff --git a/feincms/views/generic/list_detail.py b/feincms/views/generic/list_detail.py deleted file mode 100644 index 6684e5abe..000000000 --- a/feincms/views/generic/list_detail.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.views.generic import list_detail -from feincms.views.decorators import add_page_to_extra_context - - -object_list = add_page_to_extra_context(list_detail.object_list) -object_detail = add_page_to_extra_context(list_detail.object_detail) - diff --git a/feincms/views/generic/simple.py b/feincms/views/generic/simple.py deleted file mode 100644 index 22f1b7eaa..000000000 --- a/feincms/views/generic/simple.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.views.generic import simple -from feincms.views.decorators import add_page_to_extra_context - - -direct_to_template = add_page_to_extra_context(simple.direct_to_template) - diff --git a/quickstart.sh b/quickstart.sh deleted file mode 100755 index 72a928521..000000000 --- a/quickstart.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# -# This script downloads all the software needed to run FeinCMS -# - -echo "Downloading Django and django-mptt via git... (this will take some time)" - -git clone git://github.com/django/django.git django -git clone git://github.com/django-mptt/django-mptt.git mptt - -cat <<EOD -Everything should be ready now. Type the following commands into the shell -to start the test server: - -cd example -python manage.py runserver - -Navigate to http://127.0.0.1:8000/admin/ to see the admin interface. The -username is 'admin', the password 'password'. You are probably most -interested in the page module. -EOD diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..bdee4d2e8 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,57 @@ +[metadata] +name = FeinCMS +version = attr: feincms.__version__ +description = Django-based Page CMS and CMS building toolkit. +long_description = file: README.rst +long_description_content_type = text/x-rst +url = http://github.com/feincms/feincms/ +author = Matthias Kestenholz +author_email = mk@feinheit.ch +license = BSD-3-Clause +license_file = LICENSE +platforms = OS Independent +classifiers = + Development Status :: 5 - Production/Stable + Environment :: Web Environment + Framework :: Django + Intended Audience :: Developers + License :: OSI Approved :: BSD License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 + Programming Language :: Python :: Implementation :: CPython + Topic :: Internet :: WWW/HTTP :: Dynamic Content + Topic :: Software Development + Topic :: Software Development :: Libraries :: Application Frameworks + +[options] +packages = find: +install_requires = + Django>=3.2 + Pillow>=2.0.0 + django-mptt>=0.7.1 +python_requires = >=3.6 +include_package_data = True +zip_safe = False + +[options.packages.find] +exclude = tests + +[options.extras_require] +tests = + coverage + +[coverage:run] +branch = True +include = + *feincms* +omit = + *migrations* + *migrate* + *tests* + *.tox* diff --git a/setup.py b/setup.py index 9c92055a6..2edc152fb 100755 --- a/setup.py +++ b/setup.py @@ -1,94 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +from setuptools import setup -import os -from setuptools import setup, find_packages -from setuptools.dist import Distribution -import pkg_resources - -add_django_dependency = True -# See issues #50, #57 and #58 for why this is necessary -try: - pkg_resources.get_distribution('Django') - add_django_dependency = False -except pkg_resources.DistributionNotFound: - try: - import django - if django.VERSION[0] >= 1 and django.VERSION[1] >= 2 and django.VERSION[2] >= 0: - add_django_dependency = False - except ImportError: - pass - -Distribution({ - "setup_requires": add_django_dependency and ['Django >=1.2.0'] or [] -}) - -import feincms - -setup(name='FeinCMS', - version=feincms.__version__, - description='Django-based Page CMS and CMS building toolkit.', - long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst')).read(), - author='Matthias Kestenholz', - author_email='mk@feinheit.ch', - url='http://github.com/feincms/feincms/', - license='BSD License', - platforms=['OS Independent'], - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Web Environment', - 'Framework :: Django', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', - 'Topic :: Software Development', - 'Topic :: Software Development :: Libraries :: Application Frameworks', - ], - install_requires=[ - #'Django >=1.2.0' # See http://github.com/feincms/feincms/issues/closed#issue/50 - ], - requires=[ - #'lxml', # only needed for rich text cleansing - #'tagging (>0.2.1)', # please use SVN trunk - 'django_mptt (>0.4.0)', - ], - packages=[ - 'feincms', - 'feincms.admin', - 'feincms.content', - 'feincms.content.application', - 'feincms.content.comments', - 'feincms.content.contactform', - 'feincms.content.file', - 'feincms.content.image', - 'feincms.content.medialibrary', - 'feincms.content.raw', - 'feincms.content.richtext', - 'feincms.content.rss', - 'feincms.content.section', - 'feincms.content.table', - 'feincms.content.template', - 'feincms.content.video', - 'feincms.contrib', - 'feincms.management', - 'feincms.management.commands', - 'feincms.module', - 'feincms.module.blog', - 'feincms.module.blog.extensions', - 'feincms.module.medialibrary', - 'feincms.module.extensions', - 'feincms.module.page', - 'feincms.module.page.extensions', - 'feincms.module.page.templatetags', - 'feincms.templatetags', - 'feincms.utils', - 'feincms.utils.html', - 'feincms.views', - 'feincms.views.cbv', - 'feincms.views.generic', - ], - include_package_data=True, - zip_safe=False, -) +setup() diff --git a/tests/manage.py b/tests/manage.py new file mode 100755 index 000000000..80a089b77 --- /dev/null +++ b/tests/manage.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +import os +import sys + + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testapp.settings") + + sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/feincms/content/comments/__init__.py b/tests/testapp/__init__.py similarity index 100% rename from feincms/content/comments/__init__.py rename to tests/testapp/__init__.py diff --git a/tests/testapp/applicationcontent_urls.py b/tests/testapp/applicationcontent_urls.py new file mode 100644 index 000000000..604589314 --- /dev/null +++ b/tests/testapp/applicationcontent_urls.py @@ -0,0 +1,64 @@ +""" +This is a dummy module used to test the ApplicationContent +""" + +from django.http import HttpResponse, HttpResponseRedirect +from django.template.loader import render_to_string +from django.template.response import TemplateResponse +from django.urls import path, re_path + +from feincms.content.application.models import standalone, unpack + + +def module_root(request): + return HttpResponse("module_root") + + +def args_test(request, kwarg1, kwarg2): + return HttpResponse(f"{kwarg1}-{kwarg2}") + + +def full_reverse_test(request): + return render_to_string("full_reverse_test.html", {}) + + +def alias_reverse_test(request): + return render_to_string("alias_reverse_test.html", {}) + + +def fragment(request): + return render_to_string("fragment.html", {"request": request}) + + +def redirect(request): + return HttpResponseRedirect(request.build_absolute_uri("../")) + + +def response(request): + return HttpResponse("Anything") + + +def inheritance20(request): + return "inheritance20.html", {"from_appcontent": 42} + + +@unpack +def inheritance20_unpack(request): + response = TemplateResponse(request, "inheritance20.html", {"from_appcontent": 43}) + response["Cache-Control"] = "yabba dabba" + return response + + +urlpatterns = [ + path("", module_root, name="ac_module_root"), + re_path(r"^args_test/([^/]+)/([^/]+)/$", args_test, name="ac_args_test"), + path("kwargs_test/<str:kwarg2>/<str:kwarg1>/", args_test), + path("full_reverse_test/", full_reverse_test), + path("alias_reverse_test/", alias_reverse_test), + path("fragment/", fragment), + path("redirect/", redirect), + path("response/", response), + path("response_decorated/", standalone(response)), + path("inheritance20/", inheritance20), + path("inheritance20_unpack/", inheritance20_unpack), +] diff --git a/tests/testapp/content.py b/tests/testapp/content.py new file mode 100644 index 000000000..24f79b6e2 --- /dev/null +++ b/tests/testapp/content.py @@ -0,0 +1,6 @@ +from django.db import models + + +class CustomContentType(models.Model): + class Meta: + abstract = True diff --git a/tests/testapp/context_processors.py b/tests/testapp/context_processors.py new file mode 100644 index 000000000..c477b6489 --- /dev/null +++ b/tests/testapp/context_processors.py @@ -0,0 +1,2 @@ +def test_context(request): + return {"THE_ANSWER": 42} diff --git a/example/media/.gitignore b/tests/testapp/media/.gitignore similarity index 100% rename from example/media/.gitignore rename to tests/testapp/media/.gitignore diff --git a/tests/testapp/media/somefile.jpg b/tests/testapp/media/somefile.jpg new file mode 100644 index 000000000..47ef1a78e Binary files /dev/null and b/tests/testapp/media/somefile.jpg differ diff --git a/feincms/content/rss/__init__.py b/tests/testapp/migrate/__init__.py similarity index 100% rename from feincms/content/rss/__init__.py rename to tests/testapp/migrate/__init__.py diff --git a/tests/testapp/migrate/medialibrary/0001_initial.py b/tests/testapp/migrate/medialibrary/0001_initial.py new file mode 100644 index 000000000..d01111132 --- /dev/null +++ b/tests/testapp/migrate/medialibrary/0001_initial.py @@ -0,0 +1,168 @@ +# Generated by Django 3.0.2 on 2020-01-21 15:21 + +import django.db.models.deletion +import django.utils.timezone +from django.db import migrations, models + +import feincms.extensions.base +import feincms.translations + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Category", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=200, verbose_name="title")), + ("slug", models.SlugField(max_length=150, verbose_name="slug")), + ( + "parent", + models.ForeignKey( + blank=True, + limit_choices_to={"parent__isnull": True}, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="children", + to="medialibrary.Category", + verbose_name="parent", + ), + ), + ], + options={ + "verbose_name": "category", + "verbose_name_plural": "categories", + "ordering": ["parent__title", "title"], + }, + ), + migrations.CreateModel( + name="MediaFile", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "file", + models.FileField( + max_length=255, + upload_to="medialibrary/%Y/%m/", + verbose_name="file", + ), + ), + ( + "type", + models.CharField( + choices=[ + ("image", "Image"), + ("video", "Video"), + ("audio", "Audio"), + ("pdf", "PDF document"), + ("swf", "Flash"), + ("txt", "Text"), + ("rtf", "Rich Text"), + ("zip", "Zip archive"), + ("doc", "Microsoft Word"), + ("xls", "Microsoft Excel"), + ("ppt", "Microsoft PowerPoint"), + ("other", "Binary"), + ], + editable=False, + max_length=12, + verbose_name="file type", + ), + ), + ( + "created", + models.DateTimeField( + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "copyright", + models.CharField( + blank=True, max_length=200, verbose_name="copyright" + ), + ), + ( + "file_size", + models.IntegerField( + blank=True, editable=False, null=True, verbose_name="file size" + ), + ), + ( + "categories", + models.ManyToManyField( + blank=True, + to="medialibrary.Category", + verbose_name="categories", + ), + ), + ], + bases=( + models.Model, + feincms.extensions.base.ExtensionsMixin, + feincms.translations.TranslatedObjectMixin, + ), + ), + migrations.CreateModel( + name="MediaFileTranslation", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "language_code", + models.CharField( + choices=[("en", "English"), ("de", "German")], + default="en", + max_length=10, + verbose_name="language", + ), + ), + ("caption", models.CharField(max_length=1000, verbose_name="caption")), + ( + "description", + models.TextField(blank=True, verbose_name="description"), + ), + ( + "parent", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="translations", + to="medialibrary.MediaFile", + ), + ), + ], + options={ + "verbose_name": "media file translation", + "verbose_name_plural": "media file translations", + "unique_together": {("parent", "language_code")}, + }, + ), + ] diff --git a/feincms/content/table/__init__.py b/tests/testapp/migrate/medialibrary/__init__.py similarity index 100% rename from feincms/content/table/__init__.py rename to tests/testapp/migrate/medialibrary/__init__.py diff --git a/tests/testapp/migrate/page/0001_initial.py b/tests/testapp/migrate/page/0001_initial.py new file mode 100644 index 000000000..64148bc38 --- /dev/null +++ b/tests/testapp/migrate/page/0001_initial.py @@ -0,0 +1,422 @@ +# Generated by Django 3.0.2 on 2020-01-21 15:21 + +import django.db.models.deletion +from django.db import migrations, models + +import feincms.contrib.fields +import feincms.extensions.base +import feincms.extensions.datepublisher +import feincms.module.medialibrary.fields +import feincms.module.mixins + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("sites", "0001_initial"), + ("medialibrary", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Page", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("active", models.BooleanField(default=True, verbose_name="active")), + ( + "title", + models.CharField( + help_text="This title is also used for navigation menu items.", + max_length=200, + verbose_name="title", + ), + ), + ( + "slug", + models.SlugField( + help_text="This is used to build the URL for this page", + max_length=150, + verbose_name="slug", + ), + ), + ( + "in_navigation", + models.BooleanField(default=False, verbose_name="in navigation"), + ), + ( + "override_url", + models.CharField( + blank=True, + help_text="Override the target URL. Be sure to include slashes at the beginning and at the end if it is a local URL. This affects both the navigation and subpages' URLs.", + max_length=255, + verbose_name="override URL", + ), + ), + ( + "redirect_to", + models.CharField( + blank=True, + help_text="Target URL for automatic redirects or the primary key of a page.", + max_length=255, + verbose_name="redirect to", + ), + ), + ( + "_cached_url", + models.CharField( + blank=True, + db_index=True, + default="", + editable=False, + max_length=255, + verbose_name="Cached URL", + ), + ), + ("lft", models.PositiveIntegerField(editable=False)), + ("rght", models.PositiveIntegerField(editable=False)), + ("tree_id", models.PositiveIntegerField(db_index=True, editable=False)), + ("level", models.PositiveIntegerField(editable=False)), + ( + "template_key", + models.CharField( + choices=[("base", "Base Template")], + default="base", + max_length=255, + verbose_name="template", + ), + ), + ( + "navigation_extension", + models.CharField( + blank=True, + choices=[], + help_text="Select the module providing subpages for this page if you need to customize the navigation.", + max_length=200, + null=True, + verbose_name="navigation extension", + ), + ), + ( + "language", + models.CharField( + choices=[("en", "English"), ("de", "German")], + default="en", + max_length=10, + verbose_name="language", + ), + ), + ( + "publication_date", + models.DateTimeField( + default=feincms.extensions.datepublisher.granular_now, + verbose_name="publication date", + ), + ), + ( + "publication_end_date", + models.DateTimeField( + blank=True, + help_text="Leave empty if the entry should stay active forever.", + null=True, + verbose_name="publication end date", + ), + ), + ( + "_ct_inventory", + feincms.contrib.fields.JSONField( + blank=True, + editable=False, + null=True, + verbose_name="content types", + ), + ), + ( + "meta_keywords", + models.TextField( + blank=True, + help_text="Keywords are ignored by most search engines.", + verbose_name="meta keywords", + ), + ), + ( + "meta_description", + models.TextField( + blank=True, + help_text="This text is displayed on the search results page. It is however not used for the SEO ranking. Text longer than 140 characters is truncated.", + verbose_name="meta description", + ), + ), + ( + "creation_date", + models.DateTimeField( + editable=False, null=True, verbose_name="creation date" + ), + ), + ( + "modification_date", + models.DateTimeField( + editable=False, null=True, verbose_name="modification date" + ), + ), + ( + "_content_title", + models.TextField( + blank=True, + help_text="The first line is the main title, the following lines are subtitles.", + verbose_name="content title", + ), + ), + ( + "_page_title", + models.CharField( + blank=True, + help_text="Page title for browser window. Same as title by default. Must be 69 characters or fewer.", + max_length=69, + verbose_name="page title", + ), + ), + ( + "navigation_group", + models.CharField( + blank=True, + choices=[("default", "Default"), ("footer", "Footer")], + db_index=True, + default="default", + max_length=20, + verbose_name="navigation group", + ), + ), + ( + "parent", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="children", + to="page.Page", + verbose_name="Parent", + ), + ), + ( + "site", + models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to="sites.Site", + verbose_name="Site", + ), + ), + ( + "symlinked_page", + models.ForeignKey( + blank=True, + help_text="All content is inherited from this page if given.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="page_page_symlinks", + to="page.Page", + verbose_name="symlinked page", + ), + ), + ( + "translation_of", + models.ForeignKey( + blank=True, + help_text="Leave this empty for entries in the primary language.", + limit_choices_to={"language": "en"}, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="translations", + to="page.Page", + verbose_name="translation of", + ), + ), + ], + options={ + "verbose_name": "page", + "verbose_name_plural": "pages", + "ordering": ["tree_id", "lft"], + }, + bases=( + models.Model, + feincms.extensions.base.ExtensionsMixin, + feincms.module.mixins.ContentModelMixin, + ), + ), + migrations.CreateModel( + name="TemplateContent", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("region", models.CharField(max_length=255)), + ("ordering", models.IntegerField(default=0, verbose_name="ordering")), + ( + "template", + models.CharField( + choices=[("templatecontent_1.html", "template 1")], + max_length=100, + verbose_name="template", + ), + ), + ( + "parent", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="templatecontent_set", + to="page.Page", + ), + ), + ], + options={ + "verbose_name": "template content", + "verbose_name_plural": "template contents", + "db_table": "page_page_templatecontent", + "ordering": ["ordering"], + "permissions": [], + "abstract": False, + }, + ), + migrations.CreateModel( + name="RawContent", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("text", models.TextField(blank=True, verbose_name="content")), + ("region", models.CharField(max_length=255)), + ("ordering", models.IntegerField(default=0, verbose_name="ordering")), + ( + "parent", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="rawcontent_set", + to="page.Page", + ), + ), + ], + options={ + "verbose_name": "raw content", + "verbose_name_plural": "raw contents", + "db_table": "page_page_rawcontent", + "ordering": ["ordering"], + "permissions": [], + "abstract": False, + }, + ), + migrations.CreateModel( + name="MediaFileContent", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("region", models.CharField(max_length=255)), + ("ordering", models.IntegerField(default=0, verbose_name="ordering")), + ( + "type", + models.CharField( + choices=[("default", "Default position")], + default="default", + max_length=20, + verbose_name="type", + ), + ), + ( + "mediafile", + feincms.module.medialibrary.fields.MediaFileForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="medialibrary.MediaFile", + verbose_name="media file", + ), + ), + ( + "parent", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="mediafilecontent_set", + to="page.Page", + ), + ), + ], + options={ + "verbose_name": "media file", + "verbose_name_plural": "media files", + "db_table": "page_page_mediafilecontent", + "ordering": ["ordering"], + "permissions": [], + "abstract": False, + }, + ), + migrations.CreateModel( + name="ApplicationContent", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "parameters", + feincms.contrib.fields.JSONField(editable=False, null=True), + ), + ("region", models.CharField(max_length=255)), + ("ordering", models.IntegerField(default=0, verbose_name="ordering")), + ( + "urlconf_path", + models.CharField( + choices=[("whatever", "Test Urls")], + max_length=100, + verbose_name="application", + ), + ), + ( + "parent", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="applicationcontent_set", + to="page.Page", + ), + ), + ], + options={ + "verbose_name": "application content", + "verbose_name_plural": "application contents", + "db_table": "page_page_applicationcontent", + "ordering": ["ordering"], + "permissions": [], + "abstract": False, + }, + ), + ] diff --git a/feincms/module/blog/__init__.py b/tests/testapp/migrate/page/__init__.py similarity index 100% rename from feincms/module/blog/__init__.py rename to tests/testapp/migrate/page/__init__.py diff --git a/tests/testapp/migrations/0001_initial.py b/tests/testapp/migrations/0001_initial.py new file mode 100644 index 000000000..7a4377a0d --- /dev/null +++ b/tests/testapp/migrations/0001_initial.py @@ -0,0 +1,131 @@ +# Generated by Django 2.1.4 on 2018-12-21 09:29 + +import django.db.models.deletion +from django.db import migrations, models + +import feincms.extensions.base + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Category", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=20)), + ("slug", models.SlugField()), + ("lft", models.PositiveIntegerField(db_index=True, editable=False)), + ("rght", models.PositiveIntegerField(db_index=True, editable=False)), + ("tree_id", models.PositiveIntegerField(db_index=True, editable=False)), + ("level", models.PositiveIntegerField(db_index=True, editable=False)), + ( + "parent", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="children", + to="testapp.Category", + ), + ), + ], + options={ + "verbose_name": "category", + "verbose_name_plural": "categories", + "ordering": ["tree_id", "lft"], + }, + ), + migrations.CreateModel( + name="CustomContentType", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("region", models.CharField(max_length=255)), + ("ordering", models.IntegerField(default=0, verbose_name="ordering")), + ], + options={ + "verbose_name": "custom content type", + "verbose_name_plural": "custom content types", + "db_table": "testapp_mymodel_customcontenttype", + "ordering": ["ordering"], + "permissions": [], + "abstract": False, + }, + ), + migrations.CreateModel( + name="ExampleCMSBase", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ) + ], + options={"abstract": False}, + bases=(models.Model, feincms.extensions.base.ExtensionsMixin), + ), + migrations.CreateModel( + name="ExampleCMSBase2", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ) + ], + options={"abstract": False}, + bases=(models.Model, feincms.extensions.base.ExtensionsMixin), + ), + migrations.CreateModel( + name="MyModel", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ) + ], + options={"abstract": False}, + bases=(models.Model, feincms.extensions.base.ExtensionsMixin), + ), + migrations.AddField( + model_name="customcontenttype", + name="parent", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="customcontenttype_set", + to="testapp.MyModel", + ), + ), + ] diff --git a/feincms/module/blog/extensions/__init__.py b/tests/testapp/migrations/__init__.py similarity index 100% rename from feincms/module/blog/extensions/__init__.py rename to tests/testapp/migrations/__init__.py diff --git a/tests/testapp/models.py b/tests/testapp/models.py new file mode 100644 index 000000000..150f8abb8 --- /dev/null +++ b/tests/testapp/models.py @@ -0,0 +1,126 @@ +from django import forms +from django.db import models +from django.utils.text import capfirst +from django.utils.translation import gettext_lazy as _ +from mptt.models import MPTTModel + +from feincms.content.application.models import ApplicationContent +from feincms.contents import RawContent, TemplateContent +from feincms.models import Base, create_base_model +from feincms.module.medialibrary.contents import MediaFileContent +from feincms.module.page import processors +from feincms.module.page.models import Page + +from .content import CustomContentType + + +Page.register_templates( + { + "key": "base", + "title": "Base Template", + "path": "base.html", + "regions": (("main", "Main region"), ("sidebar", "Sidebar", "inherited")), + } +) +Page.create_content_type(RawContent) +Page.create_content_type( + MediaFileContent, TYPE_CHOICES=(("default", "Default position"),) +) +Page.create_content_type( + TemplateContent, TEMPLATES=[("templatecontent_1.html", "template 1")] +) +Page.register_request_processor(processors.etag_request_processor) +Page.register_response_processor(processors.etag_response_processor) +Page.register_response_processor(processors.debug_sql_queries_response_processor()) + + +def get_admin_fields(form, *args, **kwargs): + return { + "exclusive_subpages": forms.BooleanField( + label=capfirst(_("exclusive subpages")), + required=False, + initial=form.instance.parameters.get("exclusive_subpages", False), + help_text=_( + "Exclude everything other than the application's content" + " when rendering subpages." + ), + ), + "custom_field": forms.CharField(), + } + + +Page.create_content_type( + ApplicationContent, + APPLICATIONS=( + ( + "whatever", + "Test Urls", + { + "admin_fields": get_admin_fields, + "urls": "testapp.applicationcontent_urls", + }, + ), + ), +) + +Page.register_extensions( + "feincms.module.page.extensions.navigation", + "feincms.module.page.extensions.sites", + "feincms.extensions.translations", + "feincms.extensions.datepublisher", + "feincms.extensions.translations", + "feincms.extensions.ct_tracker", + "feincms.extensions.seo", + "feincms.extensions.changedate", + "feincms.extensions.seo", # duplicate + "feincms.module.page.extensions.navigation", + "feincms.module.page.extensions.symlinks", + "feincms.module.page.extensions.titles", + "feincms.module.page.extensions.navigationgroups", +) + + +class Category(MPTTModel): + name = models.CharField(max_length=20) + slug = models.SlugField() + parent = models.ForeignKey( + "self", blank=True, null=True, related_name="children", on_delete=models.CASCADE + ) + + class Meta: + ordering = ["tree_id", "lft"] + verbose_name = "category" + verbose_name_plural = "categories" + + def __str__(self): + return self.name + + +class ExampleCMSBase(Base): + pass + + +ExampleCMSBase.register_regions( + ("region", "region title"), ("region2", "region2 title") +) + + +class ExampleCMSBase2(Base): + pass + + +ExampleCMSBase2.register_regions( + ("region", "region title"), ("region2", "region2 title") +) + + +class MyModel(create_base_model()): + pass + + +MyModel.register_regions(("main", "Main region")) + + +unchanged = CustomContentType +MyModel.create_content_type(CustomContentType) +assert CustomContentType is unchanged diff --git a/tests/testapp/navigation_extensions.py b/tests/testapp/navigation_extensions.py new file mode 100644 index 000000000..bdf89bcc3 --- /dev/null +++ b/tests/testapp/navigation_extensions.py @@ -0,0 +1,16 @@ +from feincms.module.page.extensions.navigation import NavigationExtension, PagePretender + + +class PassthroughExtension(NavigationExtension): + # See PagesTestCase.test_23_navigation_extension + name = "passthrough extension" + + def children(self, page, **kwargs): + yield from page.children.in_navigation() + + +class PretenderExtension(NavigationExtension): + name = "pretender extension" + + def children(self, page, **kwargs): + return [PagePretender(title="blabla", url="/asdsa/")] diff --git a/tests/testapp/settings.py b/tests/testapp/settings.py new file mode 100644 index 000000000..c15796d96 --- /dev/null +++ b/tests/testapp/settings.py @@ -0,0 +1,75 @@ +import os + + +SITE_ID = 1 + +DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}} +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" + +INSTALLED_APPS = [ + "django.contrib.auth", + "django.contrib.admin", + "django.contrib.contenttypes", + "django.contrib.messages", + "django.contrib.sessions", + "django.contrib.sitemaps", + "django.contrib.sites", + "django.contrib.staticfiles", + "feincms", + "feincms.module.medialibrary", + "feincms.module.page", + "mptt", + "testapp", +] + +MEDIA_URL = "/media/" +STATIC_URL = "/static/" +BASEDIR = os.path.dirname(__file__) +MEDIA_ROOT = os.path.join(BASEDIR, "media/") +STATIC_ROOT = os.path.join(BASEDIR, "static/") +SECRET_KEY = "supersikret" +USE_TZ = True +TIME_ZONE = "Europe/Zurich" + +ROOT_URLCONF = "testapp.urls" +LANGUAGES = (("en", "English"), ("de", "German")) +TEMPLATE_CONTEXT_PROCESSORS = ( + "django.contrib.auth.context_processors.auth", + "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + "django.core.context_processors.static", + # request context processor is needed + "django.core.context_processors.request", + "testapp.context_processors.test_context", +) + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "testapp.context_processors.test_context", + ] + }, + } +] +MIDDLEWARE = ( + "django.middleware.common.CommonMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.locale.LocaleMiddleware", +) + +MIGRATION_MODULES = { + "page": "testapp.migrate.page", + "medialibrary": "testapp.migrate.medialibrary", +} diff --git a/example/templates/404.html b/tests/testapp/templates/404.html similarity index 100% rename from example/templates/404.html rename to tests/testapp/templates/404.html diff --git a/tests/testapp/templates/alias_reverse_test.html b/tests/testapp/templates/alias_reverse_test.html new file mode 100644 index 000000000..0f6222033 --- /dev/null +++ b/tests/testapp/templates/alias_reverse_test.html @@ -0,0 +1 @@ +{% load applicationcontent_tags %}home:{% app_reverse "ac_module_root" "whatever" %} args:{% app_reverse "ac_args_test" "whatever" "xy" "zzy" %} base:{% url "feincms_handler" "test" %} diff --git a/tests/testapp/templates/base.html b/tests/testapp/templates/base.html new file mode 100644 index 000000000..b26a00a98 --- /dev/null +++ b/tests/testapp/templates/base.html @@ -0,0 +1,37 @@ +{% load applicationcontent_tags feincms_tags feincms_page_tags %} +<html> +<head> + <title>{{ feincms_page.title }} + + +

    {{ feincms_page.title }}

    + + + +
    +

    Main content

    + {% block content %}{% feincms_render_region feincms_page "main" request %}{% endblock %} +
    + + + + {% get_fragment request "something" %} + + diff --git a/tests/testapp/templates/feincms_base.html b/tests/testapp/templates/feincms_base.html new file mode 100644 index 000000000..78f055f6b --- /dev/null +++ b/tests/testapp/templates/feincms_base.html @@ -0,0 +1,2 @@ +{% extends "base.html" %} +{# only has to exist #} diff --git a/tests/testapp/templates/fragment.html b/tests/testapp/templates/fragment.html new file mode 100644 index 000000000..6280945b8 --- /dev/null +++ b/tests/testapp/templates/fragment.html @@ -0,0 +1 @@ +{% load applicationcontent_tags %}{% fragment request "something" %}some things{% endfragment %} diff --git a/tests/testapp/templates/full_reverse_test.html b/tests/testapp/templates/full_reverse_test.html new file mode 100644 index 000000000..8120fb5e9 --- /dev/null +++ b/tests/testapp/templates/full_reverse_test.html @@ -0,0 +1 @@ +{% load applicationcontent_tags %}home:{% app_reverse "ac_module_root" "testapp.applicationcontent_urls" %} args:{% app_reverse "ac_args_test" "testapp.applicationcontent_urls" "xy" "zzy" %} base:{% url "feincms_handler" "test" %} homeas:{% app_reverse "ac_module_root" "testapp.applicationcontent_urls" as reversed %}{{ reversed }} diff --git a/tests/testapp/templates/inheritance20.html b/tests/testapp/templates/inheritance20.html new file mode 100644 index 000000000..00e2b8705 --- /dev/null +++ b/tests/testapp/templates/inheritance20.html @@ -0,0 +1,4 @@ +{% extends "base.html" %} +some content outside +{% block content %}a content {{ from_appcontent }}{% endblock %} +{% block sidebar %}b content {{ block.super }}{% block bla %}{% endblock %}{% endblock %} diff --git a/tests/testapp/templates/templatecontent_1.html b/tests/testapp/templates/templatecontent_1.html new file mode 100644 index 000000000..b2986f579 --- /dev/null +++ b/tests/testapp/templates/templatecontent_1.html @@ -0,0 +1,2 @@ +TemplateContent_1 +#{{ THE_ANSWER }}# diff --git a/tests/testapp/tests/.gitattributes b/tests/testapp/tests/.gitattributes new file mode 100644 index 000000000..9b33e0525 --- /dev/null +++ b/tests/testapp/tests/.gitattributes @@ -0,0 +1 @@ +yahoo.rss binary -diff diff --git a/tests/testapp/tests/__init__.py b/tests/testapp/tests/__init__.py new file mode 100644 index 000000000..b059a2853 --- /dev/null +++ b/tests/testapp/tests/__init__.py @@ -0,0 +1,7 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + +# flake8: noqa + + +# ------------------------------------------------------------------------ diff --git a/tests/testapp/tests/test_cms.py b/tests/testapp/tests/test_cms.py new file mode 100644 index 000000000..90c3df88a --- /dev/null +++ b/tests/testapp/tests/test_cms.py @@ -0,0 +1,90 @@ +from django.core.exceptions import ImproperlyConfigured +from django.db import models +from django.test import TestCase +from testapp.models import ExampleCMSBase, ExampleCMSBase2 + +from feincms.contents import RawContent, RichTextContent +from feincms.module.medialibrary.contents import MediaFileContent + +from .test_stuff import Empty + + +# ------------------------------------------------------------------------ +class SubRawContent(RawContent): + title = models.CharField("title", max_length=100, blank=True) + + class Meta: + abstract = True + + +class CMSBaseTest(TestCase): + def test_01_simple_content_type_creation(self): + self.assertEqual(ExampleCMSBase.content_type_for(RawContent), None) + + ExampleCMSBase.create_content_type(RawContent, regions=("main2",)) + ExampleCMSBase.create_content_type(RichTextContent) + + # content_type_for should return None if it does not have a subclass + # registered + self.assertEqual(ExampleCMSBase.content_type_for(Empty), None) + + self.assertTrue( + "rawcontent" + not in dict(ExampleCMSBase.template.regions[0].content_types).keys() + ) + + def test_04_mediafilecontent_creation(self): + # the medialibrary needs to be enabled, otherwise this test fails + + # no TYPE_CHOICES, should raise + self.assertRaises( + ImproperlyConfigured, + lambda: ExampleCMSBase.create_content_type(MediaFileContent), + ) + + def test_05_non_abstract_content_type(self): + # Should not be able to create a content type from a non-abstract base + # type + class TestContentType(models.Model): + pass + + self.assertRaises( + ImproperlyConfigured, + lambda: ExampleCMSBase.create_content_type(TestContentType), + ) + + def test_07_default_render_method(self): + class SomethingElse(models.Model): + class Meta: + abstract = True + + def render_region(self): + return "hello" + + type = ExampleCMSBase.create_content_type(SomethingElse) + obj = type() + self.assertRaises(NotImplementedError, lambda: obj.render()) + + obj.region = "region" + self.assertEqual(obj.render(), "hello") + + def test_08_creating_two_content_types_in_same_application(self): + ExampleCMSBase.create_content_type(RawContent) + ct = ExampleCMSBase.content_type_for(RawContent) + self.assertEqual(ct._meta.db_table, "testapp_examplecmsbase_rawcontent") + + ExampleCMSBase2.create_content_type(RawContent, class_name="RawContent2") + ct2 = ExampleCMSBase2.content_type_for(RawContent) + self.assertEqual(ct2._meta.db_table, "testapp_examplecmsbase2_rawcontent2") + + def test_10_content_type_subclasses(self): + """ + See: + https://github.com/feincms/feincms/issues/339 + """ + ExampleCMSBase.create_content_type(SubRawContent) + ExampleCMSBase.create_content_type(RawContent) + + ct = ExampleCMSBase.content_type_for(RawContent) + ct2 = ExampleCMSBase.content_type_for(SubRawContent) + self.assertNotEqual(ct, ct2) diff --git a/tests/testapp/tests/test_extensions.py b/tests/testapp/tests/test_extensions.py new file mode 100644 index 000000000..cf8433ab6 --- /dev/null +++ b/tests/testapp/tests/test_extensions.py @@ -0,0 +1,116 @@ +from django.conf import settings as django_settings +from django.contrib.sites.models import Site +from django.template.defaultfilters import slugify +from django.test import RequestFactory, TestCase +from django.utils import translation + +from feincms.extensions.translations import ( + translation_set_language, + user_has_language_set, +) +from feincms.module.page.models import Page + + +class TranslationTestCase(TestCase): + def setUp(self): + Page.register_templates( + { + "key": "base", + "title": "Standard template", + "path": "feincms_base.html", + "regions": ( + ("main", "Main content area"), + ("sidebar", "Sidebar", "inherited"), + ), + } + ) + self.site_1 = Site.objects.all()[0] + + # create a bunch of pages + en = self.create_default_page_set(language="en") + de = self.create_default_page_set(language="de", title="Testseite") + de.translation_of = en + de.save() + de.parent.translation_of = en.parent + de.parent.save() + self.page_de = de.parent + self.page_en = en.parent + + if hasattr(translation, "LANGUAGE_SESSION_KEY"): + self.language_session_key = translation.LANGUAGE_SESSION_KEY + else: + # Django 1.6 + self.language_session_key = django_settings.LANGUAGE_COOKIE_NAME + + def create_page(self, title="Test page", parent=None, **kwargs): + defaults = { + "template_key": "base", + "site": self.site_1, + "in_navigation": False, + "active": False, + } + defaults.update(kwargs) + return Page.objects.create( + title=title, + slug=kwargs.get("slug", slugify(title)), + parent=parent, + **defaults, + ) + + def create_default_page_set(self, **kwargs): + return self.create_page("Test child page", parent=self.create_page(**kwargs)) + + def testPage(self): + page = Page() + self.assertTrue(hasattr(page, "language")) + self.assertTrue(hasattr(page, "translation_of")) + self.assertEqual(self.page_de.translation_of, self.page_en) + self.assertEqual(self.page_de.original_translation, self.page_en) + + # TODO: add request tests + # with translation.override('de'): + + def test_user_has_language_set_with_session(self): + factory = RequestFactory() + request = factory.get(self.page_en.get_navigation_url()) + setattr(request, "session", dict()) + request.session[self.language_session_key] = "en" + self.assertEqual(user_has_language_set(request), True) + + def test_user_has_language_set_with_cookie(self): + factory = RequestFactory() + request = factory.get(self.page_en.get_navigation_url()) + request.COOKIES[django_settings.LANGUAGE_COOKIE_NAME] = "en" + + self.assertEqual(user_has_language_set(request), True) + + def test_translation_set_language_to_session(self): + factory = RequestFactory() + request = factory.get(self.page_de.get_navigation_url()) + setattr(request, "session", dict()) + translation_set_language(request, "de") + + self.assertEqual(request.LANGUAGE_CODE, "de") + self.assertEqual(request.session[self.language_session_key], "de") + + def test_translation_set_language_to_session_primary(self): + factory = RequestFactory() + request = factory.get(self.page_en.get_navigation_url()) + setattr(request, "session", dict()) + translation_set_language(request, "en") + + self.assertEqual(request.LANGUAGE_CODE, "en") + # We avoid setting the translation language to the primary language, so should not be set + self.assertEqual( + request.session.get(self.language_session_key, "unset"), "unset" + ) + + def test_translation_set_language_to_cookie(self): + factory = RequestFactory() + request = factory.get(self.page_en.get_navigation_url()) + response = translation_set_language(request, "en") + + self.assertEqual(request.LANGUAGE_CODE, "en") + + c_key = django_settings.LANGUAGE_COOKIE_NAME + self.assertEqual(response.cookies[c_key].value, "en") diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py new file mode 100644 index 000000000..88cc77d5b --- /dev/null +++ b/tests/testapp/tests/test_page.py @@ -0,0 +1,1661 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + +import os +from datetime import datetime, timedelta + +from django import forms, template +from django.conf import settings +from django.contrib.auth.models import AnonymousUser, User +from django.contrib.contenttypes.models import ContentType +from django.contrib.sites.models import Site +from django.db import models +from django.http import Http404, HttpResponseBadRequest +from django.template import TemplateDoesNotExist +from django.template.defaultfilters import slugify +from django.test import TestCase +from django.urls import reverse +from django.utils import timezone +from django.utils.encoding import force_str +from mptt.exceptions import InvalidMove + +from feincms import settings as feincms_settings +from feincms.content.application.models import app_reverse +from feincms.contents import RawContent +from feincms.context_processors import add_page_if_missing +from feincms.models import ContentProxy +from feincms.module.medialibrary.models import Category, MediaFile +from feincms.module.page.extensions.navigation import PagePretender +from feincms.module.page.models import Page +from feincms.templatetags import feincms_page_tags +from feincms.translations import short_language_code + +from .test_stuff import Empty + + +# ------------------------------------------------------------------------ +class PagesTestCase(TestCase): + def setUp(self): + u = User(username="test", is_active=True, is_staff=True, is_superuser=True) + u.set_password("test") + u.save() + + self.site_1 = Site.objects.all()[0] + + Page.register_templates( + { + "key": "base", + "title": "Standard template", + "path": "feincms_base.html", + "regions": ( + ("main", "Main content area"), + ("sidebar", "Sidebar", "inherited"), + ), + }, + { + "key": "theother", + "title": "This actually exists", + "path": "base.html", + "regions": ( + ("main", "Main content area"), + ("sidebar", "Sidebar", "inherited"), + ), + }, + ) + + def login(self): + self.assertTrue(self.client.login(username="test", password="test")) + + def create_page_through_admin(self, title="Test page", parent="", **kwargs): + dic = { + "title": title, + "slug": kwargs.get("slug", slugify(title)), + "parent": parent, + "template_key": "base", + "publication_date_0": "2009-01-01", + "publication_date_1": "00:00:00", + "initial-publication_date_0": "2009-01-01", + "initial-publication_date_1": "00:00:00", + "language": "en", + "navigation_group": "default", + "site": self.site_1.id, + "rawcontent_set-TOTAL_FORMS": 0, + "rawcontent_set-INITIAL_FORMS": 0, + "rawcontent_set-MAX_NUM_FORMS": 10, + "mediafilecontent_set-TOTAL_FORMS": 0, + "mediafilecontent_set-INITIAL_FORMS": 0, + "mediafilecontent_set-MAX_NUM_FORMS": 10, + "imagecontent_set-TOTAL_FORMS": 0, + "imagecontent_set-INITIAL_FORMS": 0, + "imagecontent_set-MAX_NUM_FORMS": 10, + "contactformcontent_set-TOTAL_FORMS": 0, + "contactformcontent_set-INITIAL_FORMS": 0, + "contactformcontent_set-MAX_NUM_FORMS": 10, + "filecontent_set-TOTAL_FORMS": 0, + "filecontent_set-INITIAL_FORMS": 0, + "filecontent_set-MAX_NUM_FORMS": 10, + "templatecontent_set-TOTAL_FORMS": 0, + "templatecontent_set-INITIAL_FORMS": 0, + "templatecontent_set-MAX_NUM_FORMS": 10, + "applicationcontent_set-TOTAL_FORMS": 0, + "applicationcontent_set-INITIAL_FORMS": 0, + "applicationcontent_set-MAX_NUM_FORMS": 10, + } + dic.update(kwargs) + return self.client.post("/admin/page/page/add/", dic) + + def create_default_page_set_through_admin(self): + self.login() + self.create_page_through_admin() + return self.create_page_through_admin("Test child page", 1) + + def create_page(self, title="Test page", parent=None, **kwargs): + defaults = { + "template_key": "base", + "site": self.site_1, + "in_navigation": False, + "active": False, + "navigation_group": "default", + "publication_date": timezone.now() - timedelta(days=1), + } + defaults.update(kwargs) + return Page.objects.create( + title=title, + slug=kwargs.get("slug", slugify(title)), + parent=parent, + **defaults, + ) + + def create_default_page_set(self): + self.create_page("Test child page", parent=self.create_page()) + + def is_published(self, url, should_be=True): + try: + self.client.get(url) + except TemplateDoesNotExist as e: + if should_be: + if e.args != ("feincms_base.html",): + raise + else: + if e.args != ("404.html",): + raise + + def test_01_tree_editor(self): + self.login() + self.assertEqual(self.client.get("/admin/page/page/").status_code, 200) + + self.assertRedirects( + self.client.get("/admin/page/page/?anything=anything"), + "/admin/page/page/?e=1", + ) + + def test_02_add_page(self): + self.login() + self.assertRedirects( + self.create_page_through_admin(title="Test page " * 10, slug="test-page"), + "/admin/page/page/", + ) + self.assertEqual(Page.objects.count(), 1) + self.assertContains(self.client.get("/admin/page/page/"), "…") + + def test_03_item_editor(self): + self.login() + self.assertRedirects( + self.create_page_through_admin(_continue=1), + reverse("admin:page_page_change", args=(1,)), + ) + self.assertEqual( + self.client.get(reverse("admin:page_page_change", args=(1,))).status_code, + 200, + ) + self.is_published( + reverse("admin:page_page_change", args=(42,)), should_be=False + ) + + def test_03_add_another(self): + self.login() + self.assertRedirects( + self.create_page_through_admin(_addanother=1), "/admin/page/page/add/" + ) + + def test_04_add_child(self): + response = self.create_default_page_set_through_admin() + self.assertRedirects(response, "/admin/page/page/") + self.assertEqual(Page.objects.count(), 2) + + page = Page.objects.get(pk=2) + self.assertEqual(page.get_absolute_url(), "/test-page/test-child-page/") + + page.active = True + page.in_navigation = True + page.save() + + # page2 inherited the inactive flag from the toplevel page + self.assertContains(self.client.get("/admin/page/page/"), "inherited") + + page1 = Page.objects.get(pk=1) + page1.active = True + page1.save() + + content = self.client.get("/admin/page/page/").content.decode("utf-8") + self.assertEqual(len(content.split('checked="checked"')), 4) + + def test_05_override_url(self): + self.create_default_page_set() + + page = Page.objects.get(pk=1) + page.override_url = "/something/" + page.save() + + page2 = Page.objects.get(pk=2) + self.assertEqual(page2.get_absolute_url(), "/something/test-child-page/") + + page.override_url = "/" + page.save() + page2 = Page.objects.get(pk=2) + self.assertEqual(page2.get_absolute_url(), "/test-child-page/") + + self.is_published("/", False) + page.active = True + page.template_key = "theother" + page.save() + self.is_published("/", True) + + def test_06_tree_editor_save(self): + self.create_default_page_set() + + page1 = Page.objects.get(pk=1) + page2 = Page.objects.get(pk=2) + + page3 = Page.objects.create(title="page3", slug="page3", parent=page2) + page4 = Page.objects.create(title="page4", slug="page4", parent=page1) + page5 = Page.objects.create(title="page5", slug="page5", parent=None) + + self.assertEqual(page3.get_absolute_url(), "/test-page/test-child-page/page3/") + self.assertEqual(page4.get_absolute_url(), "/test-page/page4/") + self.assertEqual(page5.get_absolute_url(), "/page5/") + + self.login() + self.client.post( + "/admin/page/page/", + { + "__cmd": "move_node", + "position": "last-child", + "cut_item": "1", + "pasted_on": "5", + }, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ) + + self.assertEqual(Page.objects.get(pk=1).get_absolute_url(), "/page5/test-page/") + self.assertEqual(Page.objects.get(pk=5).get_absolute_url(), "/page5/") + self.assertEqual( + Page.objects.get(pk=3).get_absolute_url(), + "/page5/test-page/test-child-page/page3/", + ) + + def test_07_tree_editor_toggle_boolean(self): + self.create_default_page_set() + + self.assertEqual(Page.objects.get(pk=1).in_navigation, False) + + self.login() + self.assertContains( + self.client.post( + "/admin/page/page/", + {"__cmd": "toggle_boolean", "item_id": 1, "attr": "in_navigation"}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ), + r"checked=\"checked\"", + ) + self.assertEqual(Page.objects.get(pk=1).in_navigation, True) + self.assertNotContains( + self.client.post( + "/admin/page/page/", + {"__cmd": "toggle_boolean", "item_id": 1, "attr": "in_navigation"}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ), + 'checked="checked"', + ) + self.assertEqual(Page.objects.get(pk=1).in_navigation, False) + + self.assertTrue( + isinstance( + self.client.post( + "/admin/page/page/", + {"__cmd": "toggle_boolean", "item_id": 1, "attr": "notexists"}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ), + HttpResponseBadRequest, + ) + ) + + def test_07_tree_editor_invalid_ajax(self): + self.login() + self.assertContains( + self.client.post( + "/admin/page/page/", + {"__cmd": "notexists"}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ), + "Oops. AJAX request not understood.", + status_code=400, + ) + + def test_08_publishing(self): + self.create_default_page_set() + + page = Page.objects.get(pk=1) + page2 = Page.objects.get(pk=2) + self.is_published(page.get_absolute_url(), should_be=False) + self.is_published(page2.get_absolute_url(), should_be=False) + + page.active = True + page.save() + page2.active = True + page2.save() + self.is_published(page.get_absolute_url(), should_be=True) + self.is_published(page2.get_absolute_url(), should_be=True) + + old_publication = page.publication_date + page.publication_date = timezone.now() + timedelta(days=1) + page.save() + self.is_published(page.get_absolute_url(), should_be=False) + + # Should be not accessible because of its parent's inactivity + self.is_published(page2.get_absolute_url(), should_be=False) + + page.publication_date = old_publication + page.publication_end_date = timezone.now() - timedelta(days=1) + page.save() + self.is_published(page.get_absolute_url(), should_be=False) + + # Should be not accessible because of its parent's inactivity + self.is_published(page2.get_absolute_url(), should_be=False) + + page.publication_end_date = timezone.now() + timedelta(days=1) + page.save() + self.is_published(page.get_absolute_url(), should_be=True) + self.is_published(page2.get_absolute_url(), should_be=True) + + def create_page_through_admincontent(self, page, **kwargs): + data = { + "title": page.title, + "slug": page.slug, + # 'parent': page.parent_id, # this field is excluded from the form + "template_key": page.template_key, + "publication_date_0": "2009-01-01", + "publication_date_1": "00:00:00", + "initial-publication_date_0": "2009-01-01", + "initial-publication_date_1": "00:00:00", + "language": "en", + "navigation_group": "default", + "site": self.site_1.id, + "rawcontent_set-TOTAL_FORMS": 1, + "rawcontent_set-INITIAL_FORMS": 0, + "rawcontent_set-MAX_NUM_FORMS": 10, + "rawcontent_set-0-parent": 1, + "rawcontent_set-0-region": "main", + "rawcontent_set-0-ordering": 0, + "rawcontent_set-0-text": "This is some example content", + "mediafilecontent_set-TOTAL_FORMS": 1, + "mediafilecontent_set-INITIAL_FORMS": 0, + "mediafilecontent_set-MAX_NUM_FORMS": 10, + "mediafilecontent_set-0-parent": 1, + "mediafilecontent_set-0-type": "default", + "templatecontent_set-TOTAL_FORMS": 1, + "templatecontent_set-INITIAL_FORMS": 0, + "templatecontent_set-MAX_NUM_FORMS": 10, + "applicationcontent_set-TOTAL_FORMS": 1, + "applicationcontent_set-INITIAL_FORMS": 0, + "applicationcontent_set-MAX_NUM_FORMS": 10, + } + data.update(kwargs) + + return self.client.post( + reverse("admin:page_page_change", args=(page.pk,)), data + ) + + def test_09_pagecontent(self): + self.create_default_page_set() + + page = Page.objects.get(pk=1) + self.login() + response = self.create_page_through_admincontent(page) + self.assertRedirects(response, "/admin/page/page/") + self.assertEqual(page.content.main[0].__class__.__name__, "RawContent") + + page2 = Page.objects.get(pk=2) + page2.symlinked_page = page + + # Test that all_of_type works correctly even before accessing + # other content methods + self.assertEqual(len(page2.content.all_of_type(RawContent)), 1) + + self.assertEqual(page2.content.main[0].__class__.__name__, "RawContent") + self.assertEqual( + force_str(page2.content.main[0]), + "RawContent, region=main, ordering=0>", + ) + + self.assertEqual(len(page2.content.main), 1) + self.assertEqual(len(page2.content.sidebar), 0) + self.assertEqual(len(page2.content.nonexistant_region), 0) + + self.assertTrue(isinstance(page2.content.media, forms.Media)) + + self.assertEqual(len(page2.content.all_of_type(RawContent)), 1) + + def test_10_mediafile_and_imagecontent(self): + self.create_default_page_set() + self.login() + + page = Page.objects.get(pk=1) + self.create_page_through_admincontent(page) + + category = Category.objects.create(title="Category", parent=None) + category2 = Category.objects.create(title="Something", parent=category) + + self.assertEqual(force_str(category2), "Category - Something") + self.assertEqual(force_str(category), "Category") + + mediafile = MediaFile.objects.create(file="somefile.jpg") + mediafile.categories.set([category]) + page.mediafilecontent_set.create( + mediafile=mediafile, region="main", type="default", ordering=1 + ) + + self.assertEqual(force_str(mediafile), "somefile.jpg") + + mediafile.translations.create( + caption="something", language_code="%s-ha" % short_language_code() + ) + mediafile.purge_translation_cache() + + self.assertTrue("something" in force_str(mediafile)) + + mf = page.content.main[1].mediafile + + self.assertEqual(mf.translation.caption, "something") + self.assertEqual(mf.translation.short_language_code(), short_language_code()) + self.assertNotEqual(mf.get_absolute_url(), "") + self.assertEqual(force_str(mf), "something") + self.assertTrue(mf.type == "image") + + self.assertEqual(MediaFile.objects.only_language("de").count(), 0) + self.assertEqual(MediaFile.objects.only_language("en").count(), 0) + self.assertEqual( + MediaFile.objects.only_language( + lambda: "%s-ha" % short_language_code() + ).count(), + 1, + ) + + self.assertTrue("%s-ha" % short_language_code() in mf.available_translations) + + # this should not raise + self.client.get(reverse("admin:page_page_change", args=(1,))) + + page.mediafilecontent_set.update(mediafile=3) # WTF is this? + # this should not raise + self.client.get("/admin/page/page/1/") + page.mediafilecontent_set.update(mediafile=mf.id) # Revert changes + + field = MediaFile._meta.get_field("file") + old = (field.upload_to, field.storage, field.generate_filename) + from django.core.files.storage import FileSystemStorage + + MediaFile.reconfigure( + upload_to=lambda: "anywhere", + storage=FileSystemStorage(location="/wha/", base_url="/whe/"), + ) + mediafile = MediaFile.objects.get(pk=1) + self.assertEqual(mediafile.file.url, "/whe/somefile.jpg") + + # restore settings + (field.upload_to, field.storage, field.generate_filename) = old + + mediafile = MediaFile.objects.get(pk=1) + self.assertEqual(mediafile.file.url, "/media/somefile.jpg") + + def test_11_translations(self): + self.create_default_page_set() + + page1 = Page.objects.get(pk=1) + self.assertEqual(len(page1.available_translations()), 0) + + page1 = Page.objects.get(pk=1) + page2 = Page.objects.get(pk=2) + + page1.active = True + page1.save() + + page2.active = True + page2.language = "de" + page2.save() + + self.assertEqual(len(page2.available_translations()), 0) + + page2.translation_of = page1 + page2.save() + + self.assertEqual(len(page2.available_translations()), 1) + self.assertEqual(len(page1.available_translations()), 1) + + self.assertEqual(page1, page1.original_translation) + self.assertEqual(page1, page2.original_translation) + + def test_12_titles(self): + self.create_default_page_set() + + page = Page.objects.get(pk=1) + + self.assertEqual(page.page_title, page.title) + self.assertEqual(page.content_title, page.title) + + page._content_title = "Something\nawful" + page._page_title = "Hello world" + page.save() + + self.assertEqual(page.page_title, "Hello world") + self.assertEqual(page.content_title, "Something") + self.assertEqual(page.content_subtitle, "awful") + + page._content_title = "Only one line" + self.assertEqual(page.content_title, "Only one line") + self.assertEqual(page.content_subtitle, "") + + page._content_title = "" + self.assertEqual(page.content_title, page.title) + self.assertEqual(page.content_subtitle, "") + + def test_13_inheritance_and_ct_tracker(self): + self.create_default_page_set() + + page = Page.objects.get(pk=1) + page.rawcontent_set.create(region="sidebar", ordering=0, text="Something") + page.rawcontent_set.create(region="main", ordering=0, text="Anything") + + page2 = Page.objects.get(pk=2) + page2.rawcontent_set.create(region="main", ordering=0, text="Something else") + page2.rawcontent_set.create(region="main", ordering=1, text="Whatever") + + # Set default, non-caching content proxy + page2.content_proxy_class = ContentProxy + + if hasattr(self, "assertNumQueries"): + # 4 queries: Two to get the content types of page and page2, one to + # fetch all ancestor PKs of page2 and one to materialize the + # RawContent instances belonging to page's sidebar and page2's + # main. + self.assertNumQueries( + 4, lambda: [page2.content.main, page2.content.sidebar] + ) + self.assertNumQueries(0, lambda: page2.content.sidebar[0].render()) + + self.assertEqual( + "".join(c.render() for c in page2.content.main), "Something elseWhatever" + ) + self.assertEqual(page2.content.sidebar[0].render(), "Something") + + page2 = Page.objects.get(pk=2) + self.assertEqual(page2._ct_inventory, {}) + + # Prime Django content type cache + for ct in Page._feincms_content_types: + ContentType.objects.get_for_model(ct) + + if hasattr(self, "assertNumQueries"): + # 5 queries: Two to get the content types of page and page2, one to + # fetch all ancestor PKs of page2 and one to materialize the + # RawContent instances belonging to page's sidebar and page2's main + # and a few queries to update the pages _ct_inventory attributes: + # - one update to update page2 + # - one update to clobber the _ct_inventory attribute of all + # descendants of page2 + self.assertNumQueries( + 5, lambda: [page2.content.main, page2.content.sidebar] + ) + self.assertNumQueries(0, lambda: page2.content.sidebar[0].render()) + + self.assertEqual(page2.content.sidebar[0].render(), "Something") + + # Reload, again, to test ct_tracker extension + page2 = Page.objects.get(pk=2) + + if hasattr(self, "assertNumQueries"): + self.assertNumQueries( + 1, lambda: [page2.content.main, page2.content.sidebar] + ) + + self.assertNotEqual(page2._ct_inventory, {}) + + def test_17_page_template_tags(self): + self.create_default_page_set() + + page1 = Page.objects.get(pk=1) + page2 = Page.objects.get(pk=2) + + page2.language = "de" + page2.translation_of = page1 + page2.active = True + page2.in_navigation = True + page2.save() + + page3 = Page.objects.create( + parent=page2, + title="page3", + slug="page3", + language="en", + active=True, + in_navigation=True, + publication_date=datetime(2001, 1, 1), + ) + + # reload these two, their mptt attributes have changed + page1 = Page.objects.get(pk=1) + page2 = Page.objects.get(pk=2) + + context = template.Context({"feincms_page": page2, "page3": page3}) + + t = template.Template( + "{% load feincms_page_tags %}{% feincms_parentlink of feincms_page" + " level=1 %}" + ) + self.assertEqual(t.render(context), "/test-page/") + + t = template.Template( + "{% load feincms_page_tags %}{% feincms_languagelinks for" + " feincms_page as links %}{% for key, name, link in links %}" + "{{ key }}:{{ link }}{% if not forloop.last %},{% endif %}" + "{% endfor %}" + ) + self.assertEqual( + t.render(context), "en:/test-page/,de:/test-page/test-child-page/" + ) + + t = template.Template( + "{% load feincms_page_tags %}{% feincms_languagelinks for page3" + " as links %}{% for key, name, link in links %}{{ key }}:" + "{{ link }}{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual( + t.render(context), "en:/test-page/test-child-page/page3/,de:None" + ) + + t = template.Template( + "{% load feincms_page_tags %}{% feincms_languagelinks for page3" + " as links existing %}{% for key, name, link in links %}{{ key }}:" + "{{ link }}{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "en:/test-page/test-child-page/page3/") + + t = template.Template( + "{% load feincms_page_tags %}{% feincms_languagelinks for" + " feincms_page as links excludecurrent=1 %}" + "{% for key, name, link in links %}{{ key }}:{{ link }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "en:/test-page/") + + t = template.Template( + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=1 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "") + + # XXX should the other template tags not respect the in_navigation + # setting too? + page1.active = True + page1.in_navigation = True + page1.save() + + self.assertEqual(t.render(context), "/test-page/") + + t = template.Template( + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "/test-page/test-child-page/") + + t = template.Template( + "{% load feincms_page_tags %}" + "{% feincms_nav request level=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + + from django.http import HttpRequest + + request = HttpRequest() + request.path = "/test-page/" + self.assertEqual( + t.render(template.Context({"request": request})), + "/test-page/test-child-page/", + ) + + t = template.Template( + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=99 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "") + + t = template.Template( + "{% load feincms_page_tags %}{% feincms_breadcrumbs feincms_page %}" + ) + rendered = t.render(context) + self.assertTrue("Test child page" in rendered) + self.assertTrue( + 'href="/test-page/">Test page' in rendered, + msg="The parent page should be a breadcrumb link", + ) + self.assertTrue( + 'href="/test-page/test-child-page/"' not in rendered, + msg="The current page should not be a link in the breadcrumbs", + ) + + t = template.Template( + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=2 depth=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual( + t.render(context), + "/test-page/test-child-page/,/test-page/test-child-page/page3/", + ) + + t = template.Template( + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=1 depth=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "/test-page/,/test-page/test-child-page/") + + t = template.Template( + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=1 depth=3 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual( + t.render(context), + "/test-page/,/test-page/test-child-page/,/test-page/test-child-page/page3/", + ) + + t = template.Template( + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=3 depth=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "/test-page/test-child-page/page3/") + + t = template.Template( + "{% load feincms_page_tags %}" + "{% if feincms_page|is_parent_of:page3 %}yes{% endif %}|" + "{% if page3|is_parent_of:feincms_page %}yes{% endif %}" + ) + self.assertEqual(t.render(context), "yes|") + + t = template.Template( + "{% load feincms_page_tags %}" + "{% if feincms_page|is_equal_or_parent_of:page3 %}yes{% endif %}|" + "{% if page3|is_equal_or_parent_of:feincms_page %}yes{% endif %}" + ) + self.assertEqual(t.render(context), "yes|") + + t = template.Template( + "{% load feincms_page_tags %}" + "{% feincms_translatedpage for feincms_page as t1 language=de %}" + "{% feincms_translatedpage for feincms_page as t2 %}" + "{{ t1.id }}|{{ t2.id }}" + ) + self.assertEqual(t.render(context), "2|1") + + def test_17_feincms_nav(self): + """ + Test feincms_nav some more + """ + + self.login() + + self.create_page_through_admin("Page 1") # 1 + self.create_page_through_admin("Page 1.1", 1) + self.create_page_through_admin("Page 1.2", 1) # 3 + self.create_page_through_admin("Page 1.2.1", 3) + self.create_page_through_admin("Page 1.2.2", 3) + self.create_page_through_admin("Page 1.2.3", 3) + self.create_page_through_admin("Page 1.3", 1) + + self.create_page_through_admin("Page 2") # 8 + self.create_page_through_admin("Page 2.1", 8) + self.create_page_through_admin("Page 2.2", 8) + self.create_page_through_admin("Page 2.3", 8) + + self.create_page_through_admin("Page 3") # 12 + self.create_page_through_admin("Page 3.1", 12) + self.create_page_through_admin("Page 3.2", 12) + self.create_page_through_admin("Page 3.3", 12) # 15 + self.create_page_through_admin("Page 3.3.1", 15) # 16 + self.create_page_through_admin("Page 3.3.1.1", 16) + self.create_page_through_admin("Page 3.3.2", 15) + + self.create_page_through_admin("Page 4") # 19 + self.create_page_through_admin("Page 4.1", 19) + self.create_page_through_admin("Page 4.2", 19) + + """ + Creates the following structure: + + 1 (1) -+- 1.1 (2) + +- 1.2 (3) -+- 1.2.1 (4) + | +- 1.2.2 (5) + | +- 1.2.3 (6) + +- 1.3 (7) + + 2 (8) -+- 2.1 (9) + +- 2.2 (10) + +- 2.3 (11) + + 3 (12) -+- 3.1 (13) + +- 3.2 (14) + +- 3.3 (15) -+- 3.3.1 (16) --- 3.3.1.1 (17) + +- 3.3.2 (18) + 4 (19) -+- 4.1 (20) + +- 4.2 (21) + """ + + Page.objects.all().update(active=True, in_navigation=True) + Page.objects.filter(id__in=(5, 9, 19)).update(in_navigation=False) + + tests = [ + ( + {"feincms_page": Page.objects.get(pk=1)}, + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=1 depth=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}", + "/page-1/,/page-1/page-11/,/page-1/page-12/,/page-1/page-13/" + ",/page-2/,/page-2/page-22/,/page-2/page-23/,/page-3/,/page-" + "3/page-31/,/page-3/page-32/,/page-3/page-33/", + ), + ( + {"feincms_page": Page.objects.get(pk=14)}, + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=2 depth=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}", + "/page-3/page-31/,/page-3/page-32/,/page-3/page-33/,/page-3/" + "page-33/page-331/,/page-3/page-33/page-332/", + ), + ( + {"feincms_page": Page.objects.get(pk=14)}, + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=2 depth=3 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}", + "/page-3/page-31/,/page-3/page-32/,/page-3/page-33/,/page-3/" + "page-33/page-331/,/page-3/page-33/page-331/page-3311/,/page" + "-3/page-33/page-332/", + ), + ( + {"feincms_page": Page.objects.get(pk=19)}, + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=1 depth=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}", + "/page-1/,/page-1/page-11/,/page-1/page-12/,/page-1/page-13" + "/,/page-2/,/page-2/page-22/,/page-2/page-23/,/page-3/,/pag" + "e-3/page-31/,/page-3/page-32/,/page-3/page-33/", + ), + ( + {"feincms_page": Page.objects.get(pk=1)}, + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=3 depth=1 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}", + "", + ), + ] + + for c, t, r in tests: + self.assertEqual(template.Template(t).render(template.Context(c)), r) + + # Test that navigation entries do not exist several times, even with + # navigation extensions. Apply the PassthroughExtension to a page + # which does only have direct children, because it does not collect + # pages further down the tree. + page = Page.objects.get(pk=8) + page.navigation_extension = "testapp.navigation_extensions.PassthroughExtension" + page.save() + + for c, t, r in tests: + self.assertEqual(template.Template(t).render(template.Context(c)), r) + + # Now check that disabling a page also disables it in Navigation: + p = Page.objects.get(pk=15) + tmpl = """{% load feincms_page_tags %} +{% feincms_nav feincms_page level=1 depth=3 as nav %} +{% for p in nav %}{{ p.pk }}{% if not forloop.last %},{% endif %}{% endfor %} +""" + + data = ( + template.Template(tmpl) + .render(template.Context({"feincms_page": p})) + .strip(), + ) + self.assertEqual( + data, ("1,2,3,4,6,7,8,10,11,12,13,14,15,16,18",), "Original navigation" + ) + + p.active = False + p.save() + data = ( + template.Template(tmpl) + .render(template.Context({"feincms_page": p})) + .strip(), + ) + self.assertEqual( + data, + ("1,2,3,4,6,7,8,10,11,12,13,14",), + "Navigation after disabling intermediate page", + ) + + # Same test with feincms_nav + tmpl = """{% load feincms_page_tags %} +{% feincms_nav feincms_page level=1 depth=3 as nav %} +{% for p in nav %}{{ p.pk }}{% if not forloop.last %},{% endif %}{% endfor %} +""" + + data = ( + template.Template(tmpl) + .render(template.Context({"feincms_page": p})) + .strip(), + ) + self.assertEqual( + data, + ("1,2,3,4,6,7,8,10,11,12,13,14",), + "Navigation after disabling intermediate page", + ) + + p.active = True + p.save() + + data = ( + template.Template(tmpl) + .render(template.Context({"feincms_page": p})) + .strip(), + ) + self.assertEqual( + data, ("1,2,3,4,6,7,8,10,11,12,13,14,15,16,18",), "Original navigation" + ) + + def test_18_default_render_method(self): + """ + Test the default render() behavior of selecting render_ methods + to do the (not so) heavy lifting. + """ + + class Something(models.Model): + class Meta: + abstract = True + + def render_main(self): + return "Hello" + + # do not register this model in the internal FeinCMS bookkeeping + # structures + tmp = Page._feincms_content_types[:] + type = Page.create_content_type(Something, regions=("notexists",)) + Page._feincms_content_types = tmp + + s = type(region="main", ordering="1") + + self.assertEqual(s.render(), "Hello") + + def test_19_page_manager(self): + self.create_default_page_set() + + page = Page.objects.get(pk=2) + page.active = True + page.save() + + self.assertRaises( + Page.DoesNotExist, + lambda: Page.objects.page_for_path(page.get_absolute_url()), + ) + self.assertRaises( + Page.DoesNotExist, + lambda: Page.objects.best_match_for_path( + page.get_absolute_url() + "something/hello/" + ), + ) + + self.assertRaises( + Http404, + lambda: Page.objects.best_match_for_path("/blabla/blabla/", raise404=True), + ) + self.assertRaises( + Http404, lambda: Page.objects.page_for_path("/asdf/", raise404=True) + ) + self.assertRaises( + Page.DoesNotExist, + lambda: Page.objects.best_match_for_path("/blabla/blabla/"), + ) + self.assertRaises( + Page.DoesNotExist, lambda: Page.objects.page_for_path("/asdf/") + ) + + request = Empty() + request.path = request.path_info = page.get_absolute_url() + request.method = "GET" + request.get_full_path = lambda: "/xyz/" + request.GET = {} + request.META = {} + request.user = AnonymousUser() + + # tadaa + from django.utils import translation + + translation.activate(page.language) + + page.active = False + page.save() + + self.assertRaises( + Http404, lambda: Page.objects.for_request(request, raise404=True) + ) + + page.active = True + page.save() + + self.assertRaises( + Http404, lambda: Page.objects.for_request(request, raise404=True) + ) + + page.parent.active = True + page.parent.save() + self.assertEqual(page, Page.objects.for_request(request)) + + self.assertEqual(page, Page.objects.page_for_path(page.get_absolute_url())) + self.assertEqual( + page, + Page.objects.best_match_for_path( + page.get_absolute_url() + "something/hello/" + ), + ) + + old = feincms_settings.FEINCMS_ALLOW_EXTRA_PATH + request.path += "hello/" + + feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = False + self.assertEqual(self.client.get(request.path).status_code, 404) + + feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = True + self.assertEqual(self.client.get(request.path).status_code, 200) + self.assertEqual(page, Page.objects.for_request(request, best_match=True)) + + feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = old + + page_id = id(request._feincms_page) + p = Page.objects.for_request(request) + self.assertEqual(id(p), page_id) + + def test_20_redirects(self): + self.create_default_page_set() + page1 = Page.objects.get(pk=1) + page2 = Page.objects.get(pk=2) + + page2.active = True + page2.publication_date = timezone.now() - timedelta(days=1) + page2.override_url = "/blablabla/" + page2.redirect_to = page1.get_absolute_url() + page2.save() + + # regenerate cached URLs in the whole tree + page1.active = True + page1.save() + + page2 = Page.objects.get(pk=2) + + # page2 has been modified too, but its URL should not have changed + try: + self.assertRedirects( + self.client.get("/blablabla/"), page1.get_absolute_url() + ) + except TemplateDoesNotExist as e: + # catch the error from rendering page1 + if e.args != ("feincms_base.html",): + raise + + def test_21_copy_content(self): + self.create_default_page_set() + page = Page.objects.get(pk=1) + self.login() + self.create_page_through_admincontent(page) + + page2 = Page.objects.get(pk=2) + page2.copy_content_from(page) + self.assertEqual(len(page2.content.main), 1) + + def test_23_navigation_extension(self): + self.create_default_page_set() + + page = Page.objects.get(pk=1) + + self.assertEqual(len(page.extended_navigation()), 0) + + page.navigation_extension = "testapp.navigation_extensions.PassthroughExtension" + + page2 = Page.objects.get(pk=2) + page2.active = True + page2.in_navigation = True + page2.save() + + self.assertEqual(list(page.extended_navigation()), [page2]) + + page.navigation_extension = ( + "testapp.navigation_extensions.ThisExtensionDoesNotExist" + ) + + self.assertEqual(len(page.extended_navigation()), 1) + + page.navigation_extension = "testapp.navigation_extensions.PretenderExtension" + + self.assertEqual(page.extended_navigation()[0].get_absolute_url(), "/asdsa/") + + def test_24_admin_redirects(self): + self.create_default_page_set() + self.login() + page = Page.objects.get(pk=1) + + response = self.create_page_through_admincontent(page, _continue=1) + self.assertRedirects(response, reverse("admin:page_page_change", args=(1,))) + + response = self.create_page_through_admincontent(page, _addanother=1) + self.assertRedirects(response, "/admin/page/page/add/") + + response = self.create_page_through_admincontent(page) + self.assertRedirects(response, "/admin/page/page/") + + def test_25_applicationcontent(self): + self.create_default_page_set() + + page1 = Page.objects.get(pk=1) + page1.active = True + page1.save() + + page = Page.objects.get(pk=2) + page.active = True + page.template_key = "theother" + page.save() + + # Should not be published because the page has no application contents + # and should therefore not catch anything below it. + self.is_published(page1.get_absolute_url() + "anything/", False) + + page.applicationcontent_set.create( + region="main", ordering=0, urlconf_path="testapp.applicationcontent_urls" + ) + + self.assertContains(self.client.get(page.get_absolute_url()), "module_root") + self.assertContains( + self.client.get(page.get_absolute_url() + "args_test/abc/def/"), "abc-def" + ) + self.assertContains( + self.client.get(page.get_absolute_url() + "kwargs_test/abc/def/"), "def-abc" + ) + + response = self.client.get(page.get_absolute_url() + "full_reverse_test/") + self.assertContains(response, "home:/test-page/test-child-page/") + self.assertContains( + response, "args:/test-page/test-child-page/args_test/xy/zzy/" + ) + self.assertContains(response, "base:/test/") + self.assertContains(response, "homeas:/test-page/test-child-page/") + + self.assertEqual( + app_reverse("ac_module_root", "testapp.applicationcontent_urls"), + "/test-page/test-child-page/", + ) + + if hasattr(self, "assertNumQueries"): + self.assertNumQueries( + 0, + lambda: app_reverse( + "ac_module_root", "testapp.applicationcontent_urls" + ), + ) + + # This should not raise + self.assertEqual( + self.client.get(page.get_absolute_url() + "notexists/").status_code, 404 + ) + + self.assertContains( + self.client.get(page.get_absolute_url() + "fragment/"), + 'some things', + ) + + self.assertRedirects( + self.client.get(page.get_absolute_url() + "redirect/"), + "http://testserver" + page.get_absolute_url(), + ) + + self.assertEqual( + app_reverse("ac_module_root", "testapp.applicationcontent_urls"), + page.get_absolute_url(), + ) + + response = self.client.get(page.get_absolute_url() + "response/") + self.assertContains(response, "Anything") + # Ensure response has been wrapped + self.assertContains(response, "

    Main content

    ") + + # Test standalone behavior + self.assertEqual( + self.client.get( + page.get_absolute_url() + "response/", + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ).content, + self.client.get(page.get_absolute_url() + "response_decorated/").content, + ) + + page1.applicationcontent_set.create( + region="main", ordering=0, urlconf_path="whatever" + ) + + response = self.client.get(page.get_absolute_url() + "alias_reverse_test/") + self.assertContains(response, "home:/test-page/") + self.assertContains(response, "args:/test-page/args_test/xy/zzy/") + self.assertContains(response, "base:/test/") + + self.assertEqual( + app_reverse("ac_module_root", "testapp.applicationcontent_urls"), + "/test-page/test-child-page/", + ) + self.assertEqual(app_reverse("ac_module_root", "whatever"), "/test-page/") + + page.applicationcontent_set.get( + urlconf_path="testapp.applicationcontent_urls" + ).delete() + + self.assertEqual(app_reverse("ac_module_root", "whatever"), "/test-page/") + + # Ensure ApplicationContent's admin_fields support works properly + self.login() + response = self.client.get(reverse("admin:page_page_change", args=(page1.id,))) + + self.assertContains(response, "exclusive_subpages") + self.assertContains(response, "custom_field") + + # Check if admin_fields get populated correctly + app_ct = page1.applicationcontent_set.all()[0] + app_ct.parameters = '{"custom_field":"val42", "exclusive_subpages": false}' + app_ct.save() + response = self.client.get(reverse("admin:page_page_change", args=(page1.id,))) + self.assertContains(response, "val42") + + def test_26_page_form_initial(self): + self.create_default_page_set() + self.login() + + self.assertEqual( + self.client.get( + "/admin/page/page/add/?translation_of=1&lang=de" + ).status_code, + 200, + ) + self.assertEqual( + self.client.get("/admin/page/page/add/?parent=1").status_code, 200 + ) + self.assertEqual( + self.client.get("/admin/page/page/add/?parent=2").status_code, 200 + ) + + def test_27_cached_url_clash(self): + self.create_default_page_set() + + page1 = Page.objects.get(pk=1) + page2 = Page.objects.get(pk=2) + + page1.override_url = "/" + page1.active = True + page1.save() + + self.login() + self.assertContains( + self.create_page_through_admincontent(page2, active=True, override_url="/"), + "already taken by", + ) + + def test_28_applicationcontent_reverse(self): + self.create_default_page_set() + page1 = Page.objects.get(pk=1) + page1.active = True + page1.save() + + page = Page.objects.get(pk=2) + page.active = True + page.template_key = "theother" + page.save() + page.applicationcontent_set.create( + region="main", ordering=0, urlconf_path="testapp.applicationcontent_urls" + ) + + # test app_reverse + self.assertEqual( + app_reverse("ac_module_root", "testapp.applicationcontent_urls"), + page.get_absolute_url(), + ) + + # when specific applicationcontent exists more then once reverse should + # return the URL of the first (ordered by primary key) page. + self.login() + self.create_page_through_admin(title="Home DE", language="de", active=True) + page_de = Page.objects.get(title="Home DE") + self.create_page_through_admin( + title="Child 1 DE", language="de", parent=page_de.id, active=True + ) + page_de_1 = Page.objects.get(title="Child 1 DE") + page_de_1.applicationcontent_set.create( + region="main", ordering=0, urlconf_path="testapp.applicationcontent_urls" + ) + + page.active = False + page.save() + + settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), "templates"),) + self.client.get(page_de_1.get_absolute_url()) + self.assertEqual( + app_reverse("ac_module_root", "testapp.applicationcontent_urls"), + page_de_1.get_absolute_url(), + ) + + page.active = True + page.save() + + self.client.get(page1.get_absolute_url()) + self.assertEqual( + app_reverse("ac_module_root", "testapp.applicationcontent_urls"), + page.get_absolute_url(), + ) + + def test_29_medialibrary_admin(self): + self.create_default_page_set() + self.login() + + page = Page.objects.get(pk=1) + + mediafile = MediaFile.objects.create(file="somefile.jpg") + page.mediafilecontent_set.create( + mediafile=mediafile, region="main", type="default", ordering=1 + ) + + self.assertContains( + self.client.get("/admin/medialibrary/mediafile/"), "somefile.jpg" + ) + + import zipfile + + zf = zipfile.ZipFile("test.zip", "w") + for i in range(10): + zf.writestr("test%d.txt" % i, "test%d" % i) + zf.close() + + with open("test.zip", "rb") as handle: + response = self.client.post( + "/admin/medialibrary/mediafile/mediafile-bulk-upload/", {"data": handle} + ) + self.assertRedirects(response, "/admin/medialibrary/mediafile/") + + self.assertEqual( + MediaFile.objects.count(), + 11, + "Upload of media files with ZIP does not work", + ) + + dn = os.path.dirname + path = os.path.join( + dn(dn(dn(dn(__file__)))), "docs", "images", "tree_editor.png" + ) + + with open(path, "rb") as handle: + response = self.client.post( + "/admin/medialibrary/mediafile/add/", + { + "file": handle, + "translations-TOTAL_FORMS": 0, + "translations-INITIAL_FORMS": 0, + "translations-MAX_NUM_FORMS": 10, + }, + ) + self.assertRedirects(response, "/admin/medialibrary/mediafile/") + + self.assertContains( + self.client.get("/admin/medialibrary/mediafile/"), "100x100" + ) + + stats = list(MediaFile.objects.values_list("type", flat=True)) + self.assertEqual(len(stats), 12) + self.assertEqual(stats.count("image"), 2) + self.assertEqual(stats.count("txt"), 10) + + def test_30_context_processors(self): + self.create_default_page_set() + Page.objects.update(active=True, in_navigation=True) + + request = Empty() + request.GET = {} + request.META = {} + request.method = "GET" + request.path = request.path_info = "/test-page/test-child-page/abcdef/" + request.get_full_path = lambda: "/test-page/test-child-page/abcdef/" + + ctx = add_page_if_missing(request) + self.assertEqual(ctx["feincms_page"], request._feincms_page) + + def test_31_sites_framework_associating_with_single_site(self): + self.login() + site_2 = Site.objects.create(name="site 2", domain="2.example.com") + self.create_page_through_admin("site 1 homepage", override_url="/", active=True) + self.create_page_through_admin( + "site 2 homepage", override_url="/", site=site_2.id, active=True + ) + self.assertEqual(Page.objects.count(), 2) + self.assertEqual(Page.objects.active().count(), 1) + + def test_32_applicationcontent_inheritance20(self): + self.create_default_page_set() + + page1 = Page.objects.get(pk=1) + page1.active = True + page1.save() + + page = Page.objects.get(pk=2) + page.active = True + page.template_key = "theother" + page.save() + + # Should not be published because the page has no application contents + # and should therefore not catch anything below it. + self.is_published(page1.get_absolute_url() + "anything/", False) + + page.applicationcontent_set.create( + region="main", ordering=0, urlconf_path="testapp.applicationcontent_urls" + ) + page.rawcontent_set.create( + region="main", ordering=1, text="some_main_region_text" + ) + page.rawcontent_set.create( + region="sidebar", ordering=0, text="some_sidebar_region_text" + ) + + self.assertContains(self.client.get(page.get_absolute_url()), "module_root") + + response = self.client.get(page.get_absolute_url() + "inheritance20/") + self.assertContains(response, "a content 42") + self.assertContains(response, "b content") + self.assertNotContains(response, "some_main_region_text") + self.assertContains(response, "some_sidebar_region_text") + self.assertNotContains(response, "some content outside") + + response = self.client.get(page.get_absolute_url() + "inheritance20_unpack/") + self.assertContains(response, "a content 43") + self.assertIn("yabba dabba", response["cache-control"]) + + def test_33_preview(self): + self.create_default_page_set() + page = Page.objects.get(pk=1) + page.template_key = "theother" + page.save() + page.rawcontent_set.create(region="main", ordering=0, text="Example content") + + self.login() + self.assertEqual(self.client.get(page.get_absolute_url()).status_code, 404) + self.assertContains( + self.client.get(f"{page.get_absolute_url()}_preview/{page.pk}/"), + "Example content", + ) + + def test_34_access(self): + self.create_default_page_set() + + page = Page.objects.get(pk=1) + page.override_url = "/something/" + page.save() + + Page.objects.update(active=True) + + self.login() + self.create_page_through_admin( + title="redirect page", + override_url="/", + redirect_to=page.get_absolute_url(), + active=True, + ) + + # / -> redirect to /something/ + r = self.client.get("/") + self.assertRedirects(r, page.get_absolute_url()) + # /something/ should work + r = self.client.get(page.override_url) + self.assertEqual(r.status_code, 200) + # /foo not existant -> 404 + r = self.client.get("/foo/") + self.assertEqual(r.status_code, 404) + + def test_35_access_with_extra_path(self): + self.login() + self.create_page( + title="redirect again", + override_url="/", + redirect_to="/somewhere/", + active=True, + ) + self.create_page(title="somewhere", active=True) + + r = self.client.get("/") + self.assertRedirects(r, "/somewhere/") + r = self.client.get("/dingdong/") + self.assertEqual(r.status_code, 404) + + old = feincms_settings.FEINCMS_ALLOW_EXTRA_PATH + feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = True + + r = self.client.get("/") + self.assertRedirects(r, "/somewhere/") + r = self.client.get("/dingdong/") + self.assertEqual(r.status_code, 404) + + feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = old + + def test_36_sitemaps(self): + response = self.client.get("/sitemap.xml") + self.assertContains(response, "", status_code=200) + + page = Page.objects.get() + page.active = True + page.in_navigation = True + page.save() + response = self.client.get("/sitemap.xml") + self.assertContains(response, "", status_code=200) + + def test_37_invalid_parent(self): + self.create_default_page_set() + + page1, page2 = list(Page.objects.order_by("id")) + + page1.parent = page1 + self.assertRaises(InvalidMove, page1.save) + + self.create_page("Page 3", parent=page2) + page1, page2, page3 = list(Page.objects.order_by("id")) + + page1.parent = page3 + self.assertRaises(InvalidMove, page1.save) + + def test_38_invalid_template(self): + page = Page() + page.template_key = "test" + self.assertEqual(page.template.key, "base") + + def test_39_navigationgroups(self): + self.create_default_page_set() + + page1, page2 = list(Page.objects.order_by("id")) + + page1.active = True + page1.in_navigation = True + page1.save() + + page2.active = True + page2.in_navigation = True + page2.navigation_group = "footer" + page2.save() + + t = template.Template( + """ +{% load feincms_page_tags %} +{% feincms_nav feincms_page level=1 depth=10 group='default' as nav %} +{% for p in nav %}{{ p.get_absolute_url }},{% endfor %} + """ + ) + str = t.render(template.Context({"feincms_page": page1})) + + self.assertEqual(str.strip(), "/test-page/,") + + t = template.Template( + """ +{% load feincms_page_tags %} +{% feincms_nav feincms_page level=1 depth=10 group='footer' as nav %} +{% for p in nav %}{{ p.get_absolute_url }},{% endfor %} + """ + ) + str = t.render(template.Context({"feincms_page": page1})) + + self.assertEqual(str.strip(), "/test-page/test-child-page/,") + + def test_40_page_is_active(self): + self.create_default_page_set() + + page1, page2 = list(Page.objects.order_by("id")) + + self.assertTrue( + feincms_page_tags.page_is_active({"feincms_page": page1}, page1) + ) + self.assertTrue( + feincms_page_tags.page_is_active({"feincms_page": page2}, page1) + ) + self.assertFalse( + feincms_page_tags.page_is_active({"feincms_page": page1}, page2) + ) + + p = PagePretender(title="bla", slug="bla", url="/test-page/whatsup/") + + self.assertTrue( + feincms_page_tags.page_is_active({}, p, path="/test-page/whatsup/test/") + ) + self.assertFalse(feincms_page_tags.page_is_active({}, p, path="/test-page/")) + + self.assertTrue( + feincms_page_tags.page_is_active( + {"feincms_page": page1}, p, path="/test-page/whatsup/test/" + ) + ) + self.assertFalse( + feincms_page_tags.page_is_active( + {"feincms_page": page2}, p, path="/test-page/" + ) + ) + + def test_41_templatecontent(self): + page = self.create_page(active=True) + page.templatecontent_set.create( + region="main", ordering=10, template="templatecontent_1.html" + ) + + # The empty form contains the template option. + self.login() + self.assertContains( + self.client.get(reverse("admin:page_page_change", args=(page.id,))), + '', + ) + + response = self.client.get(page.get_absolute_url()) + self.assertContains(response, "TemplateContent_1") + self.assertContains(response, "#42#") diff --git a/tests/testapp/tests/test_stuff.py b/tests/testapp/tests/test_stuff.py new file mode 100644 index 000000000..2f61de441 --- /dev/null +++ b/tests/testapp/tests/test_stuff.py @@ -0,0 +1,102 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ + + +import doctest +from datetime import datetime + +from django.test import TestCase +from django.utils.encoding import force_str + +import feincms +from feincms.extensions.datepublisher import granular_now +from feincms.models import Region, Template +from feincms.utils import get_object, shorten_string +from feincms.utils.tuple import AutoRenderTuple + + +# ------------------------------------------------------------------------ +class Empty: + """ + Helper class to use as request substitute (or whatever) + """ + + +class DocTest(TestCase): + def test_translation_short_language_code(self): + doctest.testmod(feincms.translations) + + def test_medialibrary_doctests(self): + doctest.testmod(feincms.module.medialibrary.models) + + +class ModelsTest(TestCase): + def test_region(self): + # Creation should not fail + + r = Region("region", "region title") + t = Template( + "base template", + "base.html", + (("region", "region title"), Region("region2", "region2 title")), + ) + + # I'm not sure whether this test tests anything at all + self.assertEqual(r.key, t.regions[0].key) + self.assertEqual(force_str(r), "region title") + + +class UtilsTest(TestCase): + def test_get_object(self): + self.assertRaises(AttributeError, lambda: get_object("feincms.does_not_exist")) + self.assertRaises(ImportError, lambda: get_object("feincms.does_not_exist.fn")) + + self.assertEqual(get_object, get_object("feincms.utils.get_object")) + + def test_shorten_string(self): + string = shorten_string( + "Der Wolf und die Grossmutter assen im Wald zu mittag", 15, ellipsis="_" + ) + self.assertEqual(string, "Der Wolf und_ag") + self.assertEqual(len(string), 15) + + string = shorten_string( + "Haenschen-Klein, ging allein, in den tiefen Wald hinein", 15 + ) + self.assertEqual(string, "Haenschen \u2026 ein") + self.assertEqual(len(string), 15) + + string = shorten_string("Badgerbadgerbadgerbadgerbadger", 10, ellipsis="-") + self.assertEqual(string, "Badger-ger") + self.assertEqual(len(string), 10) + + def test_autotuple(self): + t = AutoRenderTuple( + ("content/richtext/default.html", {"content": {"text": "hello"}}) + ) + + self.assertTrue(isinstance(t, tuple)) + self.assertEqual(str(t).strip(), "hello") + + +# ------------------------------------------------------------------------ +class TimezoneTest(TestCase): + def test_granular_now_dst_transition(self): + try: + import pytz + except ModuleNotFoundError: + return + + # Should not raise an exception + d = datetime(2016, 10, 30, 2, 10) + tz = pytz.timezone("Europe/Copenhagen") + granular_now(d, default_tz=tz) + + def test_granular_now_rounding(self): + d = datetime(2016, 1, 3, 1, 13) + g = granular_now(d) + self.assertEqual(d.hour, g.hour) + self.assertEqual(10, g.minute) + + +# ------------------------------------------------------------------------ diff --git a/tests/testapp/tests/utils.py b/tests/testapp/tests/utils.py new file mode 100644 index 000000000..c221aedce --- /dev/null +++ b/tests/testapp/tests/utils.py @@ -0,0 +1,19 @@ +import datetime + + +def mock_datetime(): + class MockDatetime(datetime.datetime): + @classmethod + def now(cls): + return datetime.datetime(2012, 6, 1) + + return MockDatetime + + +def mock_date(): + class MockDate(datetime.date): + @classmethod + def today(cls): + return datetime.date(2012, 6, 1) + + return MockDate diff --git a/tests/testapp/urls.py b/tests/testapp/urls.py new file mode 100644 index 000000000..2e93e5843 --- /dev/null +++ b/tests/testapp/urls.py @@ -0,0 +1,28 @@ +import os + +from django.contrib import admin +from django.contrib.sitemaps.views import sitemap +from django.contrib.staticfiles.urls import staticfiles_urlpatterns +from django.urls import include, path, re_path +from django.views.static import serve + +from feincms.module.page.sitemap import PageSitemap + + +sitemaps = {"pages": PageSitemap} + +admin.autodiscover() + +urlpatterns = [ + re_path(r"^admin/", admin.site.urls), + re_path( + r"^media/(?P.*)$", + serve, + {"document_root": os.path.join(os.path.dirname(__file__), "media/")}, + ), + path("sitemap.xml", sitemap, {"sitemaps": sitemaps}), + path("", include("feincms.contrib.preview.urls")), + path("", include("feincms.urls")), +] + +urlpatterns += staticfiles_urlpatterns() diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..5bdea45cf --- /dev/null +++ b/tox.ini @@ -0,0 +1,20 @@ +[tox] +envlist = + py{38,39,310}-dj{32,41,42} + py{310,311}-dj{32,41,42,50,51,52} + py{312,313}-dj{42,50,51,52,main} + +[testenv] +usedevelop = true +extras = tests +commands = + python -Wd {envbindir}/coverage run tests/manage.py test -v2 --keepdb {posargs:testapp} + coverage report -m +deps = + dj32: Django>=3.2,<4.0 + dj41: Django>=4.1,<4.2 + dj42: Django>=4.2,<5.0 + dj50: Django>=5.0,<5.1 + dj51: Django>=5.1,<5.2 + dj52: Django>=5.2,<5.3 + djmain: https://github.com/django/django/archive/main.tar.gz