From acfe78932617aad7617b0fc2c282d26da13f4c4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Andrei?= Date: Thu, 24 Aug 2023 19:24:31 -0300 Subject: [PATCH] Initial commit --- .editorconfig | 54 + .flake8 | 22 + .gitattributes | 1 + .github/CODEOWNERS | 1 + .github/workflows/docs.yml | 67 + .github/workflows/meta.yml | 66 + .gitignore | 55 + .meta.toml | 44 + .pre-commit-config.yaml | 94 ++ CHANGES.md | 10 + CONTRIBUTORS.md | 3 + LICENSE.GPL | 339 ++++ MANIFEST.in | 13 + Makefile | 155 ++ README.md | 172 ++ constraints.txt | 1 + docker-compose.yml | 68 + docs/_static/images/icon.png | Bin 0 -> 126487 bytes docs/_static/styles/briefy.css | 1453 +++++++++++++++++ docs/_templates/layout.html | 7 + docs/changes.md | 2 + docs/conf.py | 247 +++ docs/contributing.md | 58 + docs/index.md | 15 + docs/intro.md | 94 ++ docs/robots.txt | 3 + docs/spelling_wordlist.txt | 449 +++++ instance.yaml | 8 + mx.ini | 15 + news/.changelog_template.jinja | 15 + news/.gitkeep | 0 news/0.feature | 1 + pyproject.toml | 166 ++ requirements-docs.txt | 10 + requirements.txt | 2 + setup.py | 81 + src/collective/__init__.py | 1 + src/collective/mastodon/__init__.py | 14 + src/collective/mastodon/actions/__init__.py | 1 + .../mastodon/actions/configure.zcml | 44 + src/collective/mastodon/actions/mastodon.pt | 34 + src/collective/mastodon/actions/mastodon.py | 206 +++ src/collective/mastodon/app.py | 94 ++ src/collective/mastodon/configure.zcml | 18 + src/collective/mastodon/interfaces.py | 53 + .../mastodon/interpolators/__init__.py | 0 .../mastodon/interpolators/adapters.py | 15 + .../mastodon/interpolators/configure.zcml | 11 + .../mastodon/locales/collective.mastodon.pot | 147 ++ .../de/LC_MESSAGES/collective.mastodon.po | 144 ++ .../en/LC_MESSAGES/collective.mastodon.po | 144 ++ .../es/LC_MESSAGES/collective.mastodon.po | 144 ++ .../pt_BR/LC_MESSAGES/collective.mastodon.po | 144 ++ src/collective/mastodon/locales/update.py | 78 + src/collective/mastodon/registry.py | 20 + src/collective/mastodon/settings.py | 5 + src/collective/mastodon/startup/__init__.py | 24 + src/collective/mastodon/testing.py | 30 + src/collective/mastodon/utils/__init__.py | 4 + src/collective/mastodon/utils/content.py | 63 + src/collective/mastodon/utils/tools.py | 9 + src/collective/mastodon/utils/username.py | 8 + .../mastodon/vocabularies/__init__.py | 0 src/collective/mastodon/vocabularies/apps.py | 20 + .../mastodon/vocabularies/configure.zcml | 12 + .../mastodon/vocabularies/visibility.py | 23 + .../cassettes/TestAction.test_call.yaml | 60 + .../cassettes/TestAction.test_execute.yaml | 60 + tests/actions/test_action_mastodon.py | 128 ++ ...cheduledStatuses.test_scheduled_posts.yaml | 121 ++ .../TestAppStatusPost.test_post.yaml | 122 ++ .../TestAppStatusPost.test_post_language.yaml | 122 ++ .../TestAppStatusPost.test_post_media.yaml | 192 +++ .../TestAppStatusPost.test_post_private.yaml | 122 ++ ...tAppStatusPost.test_post_scheduled_at.yaml | 121 ++ ...TestAppStatusPost.test_post_sensitive.yaml | 122 ++ ...tAppStatusPost.test_post_spoiler_text.yaml | 123 ++ tests/app/test_app.py | 117 ++ tests/conftest.py | 218 +++ tests/interpolators/test_tags.py | 26 + tests/registry/test_registry.py | 26 + tests/startup/conftest.py | 29 + tests/startup/test_register_apps.py | 16 + tests/utils/test_content.py | 106 ++ tests/utils/test_tools.py | 18 + tests/utils/test_username.py | 18 + tox.ini | 212 +++ 87 files changed, 7380 insertions(+) create mode 100644 .editorconfig create mode 100644 .flake8 create mode 100644 .gitattributes create mode 100644 .github/CODEOWNERS create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/meta.yml create mode 100644 .gitignore create mode 100644 .meta.toml create mode 100644 .pre-commit-config.yaml create mode 100644 CHANGES.md create mode 100644 CONTRIBUTORS.md create mode 100644 LICENSE.GPL create mode 100644 MANIFEST.in create mode 100644 Makefile create mode 100644 README.md create mode 100644 constraints.txt create mode 100644 docker-compose.yml create mode 100644 docs/_static/images/icon.png create mode 100644 docs/_static/styles/briefy.css create mode 100644 docs/_templates/layout.html create mode 100644 docs/changes.md create mode 100644 docs/conf.py create mode 100644 docs/contributing.md create mode 100644 docs/index.md create mode 100644 docs/intro.md create mode 100644 docs/robots.txt create mode 100644 docs/spelling_wordlist.txt create mode 100644 instance.yaml create mode 100644 mx.ini create mode 100644 news/.changelog_template.jinja create mode 100644 news/.gitkeep create mode 100644 news/0.feature create mode 100644 pyproject.toml create mode 100644 requirements-docs.txt create mode 100644 requirements.txt create mode 100644 setup.py create mode 100644 src/collective/__init__.py create mode 100644 src/collective/mastodon/__init__.py create mode 100644 src/collective/mastodon/actions/__init__.py create mode 100644 src/collective/mastodon/actions/configure.zcml create mode 100644 src/collective/mastodon/actions/mastodon.pt create mode 100644 src/collective/mastodon/actions/mastodon.py create mode 100644 src/collective/mastodon/app.py create mode 100644 src/collective/mastodon/configure.zcml create mode 100644 src/collective/mastodon/interfaces.py create mode 100644 src/collective/mastodon/interpolators/__init__.py create mode 100644 src/collective/mastodon/interpolators/adapters.py create mode 100644 src/collective/mastodon/interpolators/configure.zcml create mode 100644 src/collective/mastodon/locales/collective.mastodon.pot create mode 100644 src/collective/mastodon/locales/de/LC_MESSAGES/collective.mastodon.po create mode 100644 src/collective/mastodon/locales/en/LC_MESSAGES/collective.mastodon.po create mode 100644 src/collective/mastodon/locales/es/LC_MESSAGES/collective.mastodon.po create mode 100644 src/collective/mastodon/locales/pt_BR/LC_MESSAGES/collective.mastodon.po create mode 100644 src/collective/mastodon/locales/update.py create mode 100644 src/collective/mastodon/registry.py create mode 100644 src/collective/mastodon/settings.py create mode 100644 src/collective/mastodon/startup/__init__.py create mode 100644 src/collective/mastodon/testing.py create mode 100644 src/collective/mastodon/utils/__init__.py create mode 100644 src/collective/mastodon/utils/content.py create mode 100644 src/collective/mastodon/utils/tools.py create mode 100644 src/collective/mastodon/utils/username.py create mode 100644 src/collective/mastodon/vocabularies/__init__.py create mode 100644 src/collective/mastodon/vocabularies/apps.py create mode 100644 src/collective/mastodon/vocabularies/configure.zcml create mode 100644 src/collective/mastodon/vocabularies/visibility.py create mode 100644 tests/actions/cassettes/TestAction.test_call.yaml create mode 100644 tests/actions/cassettes/TestAction.test_execute.yaml create mode 100644 tests/actions/test_action_mastodon.py create mode 100644 tests/app/cassettes/TestAppScheduledStatuses.test_scheduled_posts.yaml create mode 100644 tests/app/cassettes/TestAppStatusPost.test_post.yaml create mode 100644 tests/app/cassettes/TestAppStatusPost.test_post_language.yaml create mode 100644 tests/app/cassettes/TestAppStatusPost.test_post_media.yaml create mode 100644 tests/app/cassettes/TestAppStatusPost.test_post_private.yaml create mode 100644 tests/app/cassettes/TestAppStatusPost.test_post_scheduled_at.yaml create mode 100644 tests/app/cassettes/TestAppStatusPost.test_post_sensitive.yaml create mode 100644 tests/app/cassettes/TestAppStatusPost.test_post_spoiler_text.yaml create mode 100644 tests/app/test_app.py create mode 100644 tests/conftest.py create mode 100644 tests/interpolators/test_tags.py create mode 100644 tests/registry/test_registry.py create mode 100644 tests/startup/conftest.py create mode 100644 tests/startup/test_register_apps.py create mode 100644 tests/utils/test_content.py create mode 100644 tests/utils/test_tools.py create mode 100644 tests/utils/test_username.py create mode 100644 tox.ini diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8ae05aa --- /dev/null +++ b/.editorconfig @@ -0,0 +1,54 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +# +# EditorConfig Configuration file, for more details see: +# http://EditorConfig.org +# EditorConfig is a convention description, that could be interpreted +# by multiple editors to enforce common coding conventions for specific +# file types + +# top-most EditorConfig file: +# Will ignore other EditorConfig files in Home directory or upper tree level. +root = true + + +[*] # For All Files +# Unix-style newlines with a newline ending every file +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +# Set default charset +charset = utf-8 +# Indent style default +indent_style = space +# Max Line Length - a hard line wrap, should be disabled +max_line_length = off + +[*.{py,cfg,ini}] +# 4 space indentation +indent_size = 4 + +[*.{yml,zpt,pt,dtml,zcml}] +# 2 space indentation +indent_size = 2 + +[*.{json,jsonl,js,jsx,ts,tsx,css,less,scss,html}] # Frontend development +# 2 space indentation +indent_size = 2 +max_line_length = 80 + +[{Makefile,.gitmodules}] +# Tab indentation (no size specified, but view as 4 spaces) +indent_style = tab +indent_size = unset +tab_width = unset + + +## +# Add extra configuration options in .meta.toml: +# [editorconfig] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..7ef4f64 --- /dev/null +++ b/.flake8 @@ -0,0 +1,22 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +[flake8] +doctests = 1 +ignore = + # black takes care of line length + E501, + # black takes care of where to break lines + W503, + # black takes care of spaces within slicing (list[:]) + E203, + # black takes care of spaces after commas + E231, + +## +# Add extra configuration options in .meta.toml: +# [flake8] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..af46901 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +CHANGES.rst merge=union \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..b9cb9b7 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @ericof diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..80f5c0d --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,67 @@ +name: Build docs +on: + push: + branches: + - "main" + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow one concurrent deployment +concurrency: + group: "pages" + cancel-in-progress: true + +env: + python-version: "3.11" + plone-version: "6.0-latest" + +jobs: + build: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + # git checkout + - uses: actions/checkout@v3 + + # Setup Deploy to GitHub Pages + - name: Setup Pages + uses: actions/configure-pages@v3 + + # Setup Plone and Python + - name: Setup Plone ${{ env.plone-version }} with Python ${{ env.python-version }} + id: setup + uses: plone/setup-plone@v2.0.0 + with: + python-version: ${{ env.python-version }} + plone-version: ${{ env.plone-version }} + + # Install plone.distribution and the requirements for documentation + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install -r requirements-docs.txt + + # Build the documentation + - name: Build Documentation + run: | + sphinx-build -b html docs docs/_build/html + + # Upload artifact to pages + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: 'docs/_build/html' + + # Deploy to GitHub pages + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/.github/workflows/meta.yml b/.github/workflows/meta.yml new file mode 100644 index 0000000..c9db54b --- /dev/null +++ b/.github/workflows/meta.yml @@ -0,0 +1,66 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +name: Meta +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + workflow_dispatch: + +## +# To set environment variables for all jobs, add in .meta.toml: +# [github] +# env = """ +# debug: 1 +# image-name: 'org/image' +# image-tag: 'latest' +# """ +## + +jobs: + qa: + uses: plone/meta/.github/workflows/qa.yml@1.0.0 + test: + uses: plone/meta/.github/workflows/test.yml@1.0.0 + coverage: + uses: plone/meta/.github/workflows/coverage.yml@1.0.0 + dependencies: + uses: plone/meta/.github/workflows/dependencies.yml@1.0.0 + release_ready: + uses: plone/meta/.github/workflows/release_ready.yml@1.0.0 + +## +# To modify the list of default jobs being created add in .meta.toml: +# [github] +# jobs = [ +# "qa", +# "test", +# "coverage", +# "dependencies", +# "release_ready", +# "circular", +# ] +## + +## +# To request that some OS level dependencies get installed +# when running tests/coverage jobs, add in .meta.toml: +# [github] +# os_dependencies = "git libxml2 libxslt" +## + + +## +# Specify additional jobs in .meta.toml: +# [github] +# extra_lines = """ +# another: +# uses: org/repo/.github/workflows/file.yml@main +# """ +## diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a47de2c --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +# python related +*.egg-info +*.pyc +*.pyo + +# translation related +*.mo + +# tools related +build/ +.coverage +coverage.xml +dist/ +docs/_build +__pycache__/ +.tox +.vscode/ +node_modules/ + +# venv / buildout related +bin/ +develop-eggs/ +eggs/ +.eggs/ +etc/ +.installed.cfg +include/ +lib/ +lib64 +.mr.developer.cfg +parts/ +pyvenv.cfg +var/ + +# mxdev +/instance/ +/.make-sentinels/ +/*-mxdev.txt +/reports/ +/sources/ +/venv/ +.installed.txt + +requirements-mxdev.txt + +## +# Add extra configuration options in .meta.toml: +# [gitignore] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/.meta.toml b/.meta.toml new file mode 100644 index 0000000..12fa9de --- /dev/null +++ b/.meta.toml @@ -0,0 +1,44 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +[meta] +template = "default" +commit-id = "21759c8c" + +[pyproject] +codespell_skip = "*.min.js,*.pot,*.po,*.yaml,*.json" +codespell_ignores = "vew" +dependencies_ignores = "['plone.volto', 'zestreleaser.towncrier', 'zest.releaser', 'pytest', 'pytest-cov', 'pytest-plone']" +dependencies_mappings = [ + "Plone = ['Products.CMFPlone', 'Products.CMFCore', 'Products.GenericSetup']", + ] +check_manifest_ignores = """ + "news/*", + "constraints-mxdev.txt", + "requirements-mxdev.txt", +""" +towncrier_issue_format = "[#{issue}](https://github.com/collective/collective.mastodon/issues/{issue})" +extra_lines = """ +[tool.coverage.run] +omit = ["*/locales/*"] +""" + +[gitignore] +extra_lines = """ +requirements-mxdev.txt +""" + +[tox] +test_runner = "pytest" +test_path = "/tests" +use_mxdev = true + +[github] +ref = "1.0.0" +jobs = [ + "qa", + "test", + "coverage", + "dependencies", + "release_ready", + ] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..d18d237 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,94 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +ci: + autofix_prs: false + autoupdate_schedule: monthly + +repos: +- repo: https://github.com/asottile/pyupgrade + rev: v3.10.1 + hooks: + - id: pyupgrade + args: [--py38-plus] +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort +- repo: https://github.com/psf/black + rev: 23.7.0 + hooks: + - id: black +- repo: https://github.com/collective/zpretty + rev: 3.1.0 + hooks: + - id: zpretty + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# zpretty_extra_lines = """ +# _your own configuration lines_ +# """ +## +- repo: https://github.com/PyCQA/flake8 + rev: 6.1.0 + hooks: + - id: flake8 + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# flake8_extra_lines = """ +# _your own configuration lines_ +# """ +## +- repo: https://github.com/codespell-project/codespell + rev: v2.2.5 + hooks: + - id: codespell + additional_dependencies: + - tomli + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# codespell_extra_lines = """ +# _your own configuration lines_ +# """ +## +- repo: https://github.com/mgedmin/check-manifest + rev: "0.49" + hooks: + - id: check-manifest +- repo: https://github.com/regebro/pyroma + rev: "4.2" + hooks: + - id: pyroma +- repo: https://github.com/mgedmin/check-python-versions + rev: "0.21.3" + hooks: + - id: check-python-versions + args: ['--only', 'setup.py,pyproject.toml'] +- repo: https://github.com/collective/i18ndude + rev: "6.1.0" + hooks: + - id: i18ndude + + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# i18ndude_extra_lines = """ +# _your own configuration lines_ +# """ +## + + +## +# Add extra configuration options in .meta.toml: +# [pre_commit] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..3020bae --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,10 @@ +# Changelog + + + + diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..8281e6f --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +# Contributors + +- Érico Andrei, @ericof diff --git a/LICENSE.GPL b/LICENSE.GPL new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE.GPL @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..7da4cf6 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,13 @@ +graft src/collective +graft tests +graft docs +prune docs/_build +include .editorconfig +include *.md +include *.GPL +include *.txt +include *.yaml +include *.yml +include Makefile +global-exclude *.pyc +global-exclude .DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1544c5e --- /dev/null +++ b/Makefile @@ -0,0 +1,155 @@ +### Defensive settings for make: +# https://tech.davis-hansson.com/p/make/ +SHELL:=bash +.ONESHELL: +.SHELLFLAGS:=-xeu -o pipefail -O inherit_errexit -c +.SILENT: +.DELETE_ON_ERROR: +MAKEFLAGS+=--warn-undefined-variables +MAKEFLAGS+=--no-builtin-rules + +# We like colors +# From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects +RED=`tput setaf 1` +GREEN=`tput setaf 2` +RESET=`tput sgr0` +YELLOW=`tput setaf 3` + +BACKEND_FOLDER=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +DOCS_DIR=${BACKEND_FOLDER}/docs + +# Python checks +PYTHON?=python3 + +# installed? +ifeq (, $(shell which $(PYTHON) )) + $(error "PYTHON=$(PYTHON) not found in $(PATH)") +endif + +# version ok? +PYTHON_VERSION_MIN=3.8 +PYTHON_VERSION_OK=$(shell $(PYTHON) -c "import sys; print((int(sys.version_info[0]), int(sys.version_info[1])) >= tuple(map(int, '$(PYTHON_VERSION_MIN)'.split('.'))))") +ifeq ($(PYTHON_VERSION_OK),0) + $(error "Need python $(PYTHON_VERSION) >= $(PYTHON_VERSION_MIN)") +endif + +all: build + +# Add the following 'help' target to your Makefile +# And add help text after each target name starting with '\#\#' +.PHONY: help +help: ## This help message + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: clean +clean: clean-build clean-pyc clean-test clean-venv clean-instance ## remove all build, test, coverage and Python artifacts + +.PHONY: clean-instance +clean-instance: ## remove existing instance + rm -fr instance etc inituser var + +.PHONY: clean-venv +clean-venv: ## remove virtual environment + rm -fr bin include lib lib64 env pyvenv.cfg .tox .pytest_cache requirements-mxdev.txt + +.PHONY: clean-build +clean-build: ## remove build artifacts + rm -fr build/ + rm -fr dist/ + rm -fr .eggs/ + find . -name '*.egg-info' -exec rm -fr {} + + find . -name '*.egg' -exec rm -rf {} + + +.PHONY: clean-pyc +clean-pyc: ## remove Python file artifacts + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +.PHONY: clean-test +clean-test: ## remove test and coverage artifacts + rm -f .coverage + rm -fr htmlcov/ + +bin/pip bin/tox bin/mxdev: + @echo "$(GREEN)==> Setup Virtual Env$(RESET)" + $(PYTHON) -m venv . + bin/pip install -U "pip" "wheel" "cookiecutter" "mxdev" "tox" + +.PHONY: config +config: bin/pip ## Create instance configuration + @echo "$(GREEN)==> Create instance configuration$(RESET)" + bin/cookiecutter -f --no-input --config-file instance.yaml gh:plone/cookiecutter-zope-instance + +.PHONY: install-plone-6.0 +install-plone-6.0: config ## pip install Plone packages + @echo "$(GREEN)==> Setup Build$(RESET)" + bin/mxdev -c mx.ini + bin/pip install -r requirements-mxdev.txt + +.PHONY: install +install: install-plone-6.0 ## Install Plone 6.0 + +.PHONY: start +start: ## Start a Plone instance on localhost:8080 + PYTHONWARNINGS=ignore ./bin/runwsgi instance/etc/zope.ini + +.PHONY: console +console: ## Console + PYTHONWARNINGS=ignore ./bin/zconsole debug etc/zope.ini + +.PHONY: format +format: bin/tox ## Format the codebase according to our standards + @echo "$(GREEN)==> Format codebase$(RESET)" + bin/tox -e format + +.PHONY: lint +lint: bin/tox ## check code style + bin/tox -e lint + +# i18n +bin/i18ndude bin/pocompile: bin/pip + @echo "$(GREEN)==> Install translation tools$(RESET)" + bin/pip install i18ndude zest.pocompile + +.PHONY: i18n +i18n: bin/i18ndude ## Update locales + @echo "$(GREEN)==> Updating locales$(RESET)" + bin/update_locale + bin/pocompile src/ + +# Tests +.PHONY: test +test: bin/tox ## run tests + bin/tox -e test + +.PHONY: test-coverage +test-coverage: bin/tox ## run tests + bin/tox -e coverage + +# Matomo instance +.PHONY: matomo-start +matomo-start: # Start matomo instance + @echo "$(GREEN)==> Start matomo$(RESET)" + docker compose -f $(BACKEND_FOLDER)/docker-compose.yml up -d + +.PHONY: matomo-stop +matomo-stop: # Stop matomo instance + @echo "$(GREEN)==> Start matomo$(RESET)" + docker compose -f $(BACKEND_FOLDER)/docker-compose.yml down + +# Docs +bin/sphinx-build: bin/pip + bin/pip install -r requirements-docs.txt + +.PHONY: build-docs +build-docs: bin/sphinx-build ## Build the documentation + ./bin/sphinx-build \ + -b html $(DOCS_DIR) "$(DOCS_DIR)/_build/html" + +.PHONY: livehtml +livehtml: bin/sphinx-build ## Rebuild Sphinx documentation on changes, with live-reload in the browser + ./bin/sphinx-autobuild \ + --ignore "*.swp" \ + -b html $(DOCS_DIR) "$(DOCS_DIR)/_build/html" diff --git a/README.md b/README.md new file mode 100644 index 0000000..903af55 --- /dev/null +++ b/README.md @@ -0,0 +1,172 @@ +
logo
+ +

collective.mastodon

+ +
+ +[![PyPI](https://img.shields.io/pypi/v/collective.mastodon)](https://pypi.org/project/collective.mastodon/) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/collective.mastodon)](https://pypi.org/project/collective.mastodon/) +[![PyPI - Wheel](https://img.shields.io/pypi/wheel/collective.mastodon)](https://pypi.org/project/collective.mastodon/) +[![PyPI - License](https://img.shields.io/pypi/l/collective.mastodon)](https://pypi.org/project/collective.mastodon/) +[![PyPI - Status](https://img.shields.io/pypi/status/collective.mastodon)](https://pypi.org/project/collective.mastodon/) + + +[![PyPI - Plone Versions](https://img.shields.io/pypi/frameworkversions/plone/collective.mastodon)](https://pypi.org/project/collective.mastodon/) + +[![Meta](https://github.com/collective/collective.mastodon/actions/workflows/meta.yml/badge.svg)](https://github.com/collective/collective.mastodon/actions/workflows/meta.yml) +![Code Style](https://img.shields.io/badge/Code%20Style-Black-000000) + +[![GitHub contributors](https://img.shields.io/github/contributors/collective/collective.mastodon)](https://github.com/collective/collective.mastodon) +[![GitHub Repo stars](https://img.shields.io/github/stars/collective/collective.mastodon?style=social)](https://github.com/collective/collective.mastodon) + +
+ +**collective.mastodon** is a package providing a [Plone](https://plone.org/) content rules action to post a status to a Mastodon instance. + + +# Installation + +This package supports Plone sites using Volto and ClassicUI. + +For proper Volto support, the requirements are: + +* plone.restapi >= 8.34.0 +* Volto >= 16.10.0 + +Add **collective.mastodon** to the Plone installation using `pip`: + +```bash +pip install collective.mastodon +``` + +or add it as a dependency on your package's `setup.py` + +```python + install_requires = [ + "collective.mastodon", + "Plone", + "plone.restapi", + "setuptools", + ], +``` + +## Configuration + +### Obtaining an Access Token +Before you can use this package, you have to register an application on Mastodon. +To do so, log in to your account, visit `settings/applications/new` and create the application. (Please select `read` and `write` scopes, and keep the default `Redirect URI`). +Go to the newly created application page and copy the value of `Your access token`. + +### Configuring Plone + +This package is configured via the `MASTODON_APPS` environment variable which should contain a valid JSON array with your Mastodon Application information. + +Each application registration requires the following information: + +| Key | Description | Example Value | +| -- | -- | -- | +| name | Identifier for the application | localhost-user | +| instance | URL of your instance, without the trailing slash | http://localhost | +| token | Access token of your Mastodon Application | jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M | +| user | User on the Mastodon instance. (Only used to generate a friendly name on Plone) | user | + +Using the information above, the environment variable would look like: + +```shell +MASTODON_APPS='[{"name": "localhost-user","instance":"http://localhost","token":"jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M","user":"user"}]' +``` + +### Starting Plone + +Now, you can start your local Plone installation with: + +```shell +MASTODON_APPS='[{"name": "localhost-user","instance":"http://localhost","token":"jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M","user":"user"}]' make start +``` + +or, if you are using a `docker compose` configuration, add the new environment variable under the `environment` key: + +```yaml + environment: + - MASTODON_APPS='[{"name": "localhost-user","instance":"http://localhost","token":"jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M","user":"user"}]' +``` + +After start-up visit the `Content Rules` Control Panel, and create a new content rule. + +No additional configuration is needed for Volto support. + +# Contributing + +If you want to help with the development (improvement, update, bug-fixing, ...) of `collective.mastodon` this is a great idea! + +- [Issue Tracker](https://github.com/collective/collective.mastodon/issues) +- [Source Code](https://github.com/collective/collective.mastodon/) +- [Documentation](https://collective.github.io/collective.mastodon) + +We appreciate any contribution and if a release is needed to be done on PyPI, please just contact one of us. + +## Local Development + +You need a working `python` environment (system, `virtualenv`, `pyenv`, etc) version 3.8 or superior. + +Then install the dependencies and a development instance using: + +```bash +make build +``` +### Update translations + +```bash +make i18n +``` + +### Format codebase + +```bash +make format +``` + +### Run tests + +Testing of this package is done with [`pytest`](https://docs.pytest.org/) and [`tox`](https://tox.wiki/). + +Run all tests with: + +```bash +make test +``` + +Run all tests but stop on the first error and open a `pdb` session: + +```bash +./bin/tox -e test -- -x --pdb +``` + +Run only tests that match `TestAppDiscovery`: + +```bash +./bin/tox -e test -- -k TestAppDiscovery +``` + +Run only tests that match `TestAppDiscovery`, but stop on the first error and open a `pdb` session: + +```bash +./bin/tox -e test -- -k TestAppDiscovery -x --pdb +``` + +## Translations + +This product has been translated into: + +- English (Érico Andrei) +- Português do Brasil (Érico Andrei) + +# License + +The project is licensed under GPLv2. + +# One Last Thing + +Originally Made in São Paulo, Brazil, with love, by your friends @ Simples Consultoria. + +Now maintained by the [Plone Collective](https://github.com/collective) diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 0000000..228b6cc --- /dev/null +++ b/constraints.txt @@ -0,0 +1 @@ +-c https://dist.plone.org/release/6.0-latest/constraints.txt diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..be28b06 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,68 @@ +# Copyright VMware, Inc. +# SPDX-License-Identifier: APACHE-2.0 + +version: '3' +services: + postgresql: + image: docker.io/bitnami/postgresql:15 + volumes: + - 'postgresql_data:/bitnami/postgresql' + environment: + - POSTGRESQL_DATABASE=bitnami_mastodon + - POSTGRESQL_USERNAME=bn_mastodon + - POSTGRESQL_PASSWORD=bitnami1 + redis: + image: docker.io/bitnami/redis:7.0 + volumes: + - 'redis_data:/bitnami/redis' + environment: + - ALLOW_EMPTY_PASSWORD=yes + elasticsearch: + image: docker.io/bitnami/elasticsearch:8 + volumes: + - 'elasticsearch_data:/bitnami/elasticsearch/data' + environment: + - ELASTICSEARCH_ENABLE_SECURITY=true + - ELASTICSEARCH_SKIP_TRANSPORT_TLS=true + - ELASTICSEARCH_ENABLE_REST_TLS=false + - ELASTICSEARCH_PASSWORD=bitnami123 + mastodon: + image: docker.io/bitnami/mastodon:4 + ports: + - 80:3000 + volumes: + - 'mastodon_data:/bitnami/mastodon' + environment: + - ALLOW_EMPTY_PASSWORD=yes + - MASTODON_MODE=web + - MASTODON_DATABASE_PASSWORD=bitnami1 + - MASTODON_ELASTICSEARCH_PASSWORD=bitnami123 + mastodon-streaming: + image: docker.io/bitnami/mastodon:4 + ports: + - 4000:4000 + environment: + - ALLOW_EMPTY_PASSWORD=yes + - MASTODON_MODE=streaming + - MASTODON_DATABASE_PASSWORD=bitnami1 + - MASTODON_ELASTICSEARCH_PASSWORD=bitnami123 + mastodon-sidekiq: + image: docker.io/bitnami/mastodon:4 + volumes: + - 'mastodon_data:/bitnami/mastodon' + environment: + - ALLOW_EMPTY_PASSWORD=yes + - MASTODON_MODE=sidekiq + - MASTODON_DATABASE_PASSWORD=bitnami1 + - MASTODON_ELASTICSEARCH_PASSWORD=bitnami123 +volumes: + postgresql_data: + driver: local + minio_data: + driver: local + redis_data: + driver: local + elasticsearch_data: + driver: local + mastodon_data: + driver: local diff --git a/docs/_static/images/icon.png b/docs/_static/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e405b4c1158ab7d7491cc53db0ab86e0adc5ff10 GIT binary patch literal 126487 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 a_kGY)sYdg2w(fNR>QI)~kgJff2>Bm0=09=( literal 0 HcmV?d00001 diff --git a/docs/_static/styles/briefy.css b/docs/_static/styles/briefy.css new file mode 100644 index 0000000..512c5cd --- /dev/null +++ b/docs/_static/styles/briefy.css @@ -0,0 +1,1453 @@ +/*! Generated by Live LESS Theme Customizer */ +@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700); +.label,sub,sup{vertical-align:baseline} +body,figure{margin:0} +.btn-group>.btn-group,.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.dropdown-menu{float:left} +.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.pre-scrollable{max-height:340px} +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-size:10px;-webkit-tap-highlight-color:transparent} +article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block} +audio,canvas,progress,video{display:inline-block;vertical-align:baseline} +audio:not([controls]){display:none;height:0} +[hidden],template{display:none} +a{background-color:transparent} +a:active,a:hover{outline:0} +b,optgroup,strong{font-weight:700} +dfn{font-style:italic} +h1{margin:.67em 0} +mark{background:#ff0;color:#000} +sub,sup{font-size:75%;line-height:0;position:relative} +sup{top:-.5em} +sub{bottom:-.25em} +img{border:0;vertical-align:middle} +svg:not(:root){overflow:hidden} +hr{box-sizing:content-box;height:0} +pre,textarea{overflow:auto} +code,kbd,pre,samp{font-size:1em} +button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0} +button{overflow:visible} +button,select{text-transform:none} +button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0} +input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto} +input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none} +table{border-collapse:collapse;border-spacing:0} +td,th{padding:0} +@media print{blockquote,img,pre,tr{page-break-inside:avoid} +*,:after,:before{background:0 0!important;color:#000!important;box-shadow:none!important;text-shadow:none!important} +a,a:visited{text-decoration:underline} +a[href]:after{content:" (" attr(href) ")"} +abbr[title]:after{content:" (" attr(title) ")"} +a[href^="javascript:"]:after,a[href^="#"]:after{content:""} +blockquote,pre{border:1px solid #999} +thead{display:table-header-group} +img{max-width:100%!important} +h2,h3,p{orphans:3;widows:3} +h2,h3{page-break-after:avoid} +.navbar{display:none} +.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important} +.label{border:1px solid #000} +.table{border-collapse:collapse!important} +.table td,.table th{background-color:#fff!important} +.table-bordered td,.table-bordered th{border:1px solid #ddd!important} +} +.img-thumbnail,body{background-color:#fff} +.btn,.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-warning.active,.btn-warning:active,.btn.active,.btn:active,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover,.form-control,.navbar-toggle,.open>.dropdown-toggle.btn-danger,.open>.dropdown-toggle.btn-default,.open>.dropdown-toggle.btn-info,.open>.dropdown-toggle.btn-primary,.open>.dropdown-toggle.btn-warning{background-image:none} +@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')} +.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} +.glyphicon-asterisk:before{content:"\002a"} +.glyphicon-plus:before{content:"\002b"} +.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"} +.glyphicon-minus:before{content:"\2212"} +.glyphicon-cloud:before{content:"\2601"} +.glyphicon-envelope:before{content:"\2709"} +.glyphicon-pencil:before{content:"\270f"} +.glyphicon-glass:before{content:"\e001"} +.glyphicon-music:before{content:"\e002"} +.glyphicon-search:before{content:"\e003"} +.glyphicon-heart:before{content:"\e005"} +.glyphicon-star:before{content:"\e006"} +.glyphicon-star-empty:before{content:"\e007"} +.glyphicon-user:before{content:"\e008"} +.glyphicon-film:before{content:"\e009"} +.glyphicon-th-large:before{content:"\e010"} +.glyphicon-th:before{content:"\e011"} +.glyphicon-th-list:before{content:"\e012"} +.glyphicon-ok:before{content:"\e013"} +.glyphicon-remove:before{content:"\e014"} +.glyphicon-zoom-in:before{content:"\e015"} +.glyphicon-zoom-out:before{content:"\e016"} +.glyphicon-off:before{content:"\e017"} +.glyphicon-signal:before{content:"\e018"} +.glyphicon-cog:before{content:"\e019"} +.glyphicon-trash:before{content:"\e020"} +.glyphicon-home:before{content:"\e021"} +.glyphicon-file:before{content:"\e022"} +.glyphicon-time:before{content:"\e023"} +.glyphicon-road:before{content:"\e024"} +.glyphicon-download-alt:before{content:"\e025"} +.glyphicon-download:before{content:"\e026"} +.glyphicon-upload:before{content:"\e027"} +.glyphicon-inbox:before{content:"\e028"} +.glyphicon-play-circle:before{content:"\e029"} +.glyphicon-repeat:before{content:"\e030"} +.glyphicon-refresh:before{content:"\e031"} +.glyphicon-list-alt:before{content:"\e032"} +.glyphicon-lock:before{content:"\e033"} +.glyphicon-flag:before{content:"\e034"} +.glyphicon-headphones:before{content:"\e035"} +.glyphicon-volume-off:before{content:"\e036"} +.glyphicon-volume-down:before{content:"\e037"} +.glyphicon-volume-up:before{content:"\e038"} +.glyphicon-qrcode:before{content:"\e039"} +.glyphicon-barcode:before{content:"\e040"} +.glyphicon-tag:before{content:"\e041"} +.glyphicon-tags:before{content:"\e042"} +.glyphicon-book:before{content:"\e043"} +.glyphicon-bookmark:before{content:"\e044"} +.glyphicon-print:before{content:"\e045"} +.glyphicon-camera:before{content:"\e046"} +.glyphicon-font:before{content:"\e047"} +.glyphicon-bold:before{content:"\e048"} +.glyphicon-italic:before{content:"\e049"} +.glyphicon-text-height:before{content:"\e050"} +.glyphicon-text-width:before{content:"\e051"} +.glyphicon-align-left:before{content:"\e052"} +.glyphicon-align-center:before{content:"\e053"} +.glyphicon-align-right:before{content:"\e054"} +.glyphicon-align-justify:before{content:"\e055"} +.glyphicon-list:before{content:"\e056"} +.glyphicon-indent-left:before{content:"\e057"} +.glyphicon-indent-right:before{content:"\e058"} +.glyphicon-facetime-video:before{content:"\e059"} +.glyphicon-picture:before{content:"\e060"} +.glyphicon-map-marker:before{content:"\e062"} +.glyphicon-adjust:before{content:"\e063"} +.glyphicon-tint:before{content:"\e064"} +.glyphicon-edit:before{content:"\e065"} +.glyphicon-share:before{content:"\e066"} +.glyphicon-check:before{content:"\e067"} +.glyphicon-move:before{content:"\e068"} +.glyphicon-step-backward:before{content:"\e069"} +.glyphicon-fast-backward:before{content:"\e070"} +.glyphicon-backward:before{content:"\e071"} +.glyphicon-play:before{content:"\e072"} +.glyphicon-pause:before{content:"\e073"} +.glyphicon-stop:before{content:"\e074"} +.glyphicon-forward:before{content:"\e075"} +.glyphicon-fast-forward:before{content:"\e076"} +.glyphicon-step-forward:before{content:"\e077"} +.glyphicon-eject:before{content:"\e078"} +.glyphicon-chevron-left:before{content:"\e079"} +.glyphicon-chevron-right:before{content:"\e080"} +.glyphicon-plus-sign:before{content:"\e081"} +.glyphicon-minus-sign:before{content:"\e082"} +.glyphicon-remove-sign:before{content:"\e083"} +.glyphicon-ok-sign:before{content:"\e084"} +.glyphicon-question-sign:before{content:"\e085"} +.glyphicon-info-sign:before{content:"\e086"} +.glyphicon-screenshot:before{content:"\e087"} +.glyphicon-remove-circle:before{content:"\e088"} +.glyphicon-ok-circle:before{content:"\e089"} +.glyphicon-ban-circle:before{content:"\e090"} +.glyphicon-arrow-left:before{content:"\e091"} +.glyphicon-arrow-right:before{content:"\e092"} +.glyphicon-arrow-up:before{content:"\e093"} +.glyphicon-arrow-down:before{content:"\e094"} +.glyphicon-share-alt:before{content:"\e095"} +.glyphicon-resize-full:before{content:"\e096"} +.glyphicon-resize-small:before{content:"\e097"} +.glyphicon-exclamation-sign:before{content:"\e101"} +.glyphicon-gift:before{content:"\e102"} +.glyphicon-leaf:before{content:"\e103"} +.glyphicon-fire:before{content:"\e104"} +.glyphicon-eye-open:before{content:"\e105"} +.glyphicon-eye-close:before{content:"\e106"} +.glyphicon-warning-sign:before{content:"\e107"} +.glyphicon-plane:before{content:"\e108"} +.glyphicon-calendar:before{content:"\e109"} +.glyphicon-random:before{content:"\e110"} +.glyphicon-comment:before{content:"\e111"} +.glyphicon-magnet:before{content:"\e112"} +.glyphicon-chevron-up:before{content:"\e113"} +.glyphicon-chevron-down:before{content:"\e114"} +.glyphicon-retweet:before{content:"\e115"} +.glyphicon-shopping-cart:before{content:"\e116"} +.glyphicon-folder-close:before{content:"\e117"} +.glyphicon-folder-open:before{content:"\e118"} +.glyphicon-resize-vertical:before{content:"\e119"} +.glyphicon-resize-horizontal:before{content:"\e120"} +.glyphicon-hdd:before{content:"\e121"} +.glyphicon-bullhorn:before{content:"\e122"} +.glyphicon-bell:before{content:"\e123"} +.glyphicon-certificate:before{content:"\e124"} +.glyphicon-thumbs-up:before{content:"\e125"} +.glyphicon-thumbs-down:before{content:"\e126"} +.glyphicon-hand-right:before{content:"\e127"} +.glyphicon-hand-left:before{content:"\e128"} +.glyphicon-hand-up:before{content:"\e129"} +.glyphicon-hand-down:before{content:"\e130"} +.glyphicon-circle-arrow-right:before{content:"\e131"} +.glyphicon-circle-arrow-left:before{content:"\e132"} +.glyphicon-circle-arrow-up:before{content:"\e133"} +.glyphicon-circle-arrow-down:before{content:"\e134"} +.glyphicon-globe:before{content:"\e135"} +.glyphicon-wrench:before{content:"\e136"} +.glyphicon-tasks:before{content:"\e137"} +.glyphicon-filter:before{content:"\e138"} +.glyphicon-briefcase:before{content:"\e139"} +.glyphicon-fullscreen:before{content:"\e140"} +.glyphicon-dashboard:before{content:"\e141"} +.glyphicon-paperclip:before{content:"\e142"} +.glyphicon-heart-empty:before{content:"\e143"} +.glyphicon-link:before{content:"\e144"} +.glyphicon-phone:before{content:"\e145"} +.glyphicon-pushpin:before{content:"\e146"} +.glyphicon-usd:before{content:"\e148"} +.glyphicon-gbp:before{content:"\e149"} +.glyphicon-sort:before{content:"\e150"} +.glyphicon-sort-by-alphabet:before{content:"\e151"} +.glyphicon-sort-by-alphabet-alt:before{content:"\e152"} +.glyphicon-sort-by-order:before{content:"\e153"} +.glyphicon-sort-by-order-alt:before{content:"\e154"} +.glyphicon-sort-by-attributes:before{content:"\e155"} +.glyphicon-sort-by-attributes-alt:before{content:"\e156"} +.glyphicon-unchecked:before{content:"\e157"} +.glyphicon-expand:before{content:"\e158"} +.glyphicon-collapse-down:before{content:"\e159"} +.glyphicon-collapse-up:before{content:"\e160"} +.glyphicon-log-in:before{content:"\e161"} +.glyphicon-flash:before{content:"\e162"} +.glyphicon-log-out:before{content:"\e163"} +.glyphicon-new-window:before{content:"\e164"} +.glyphicon-record:before{content:"\e165"} +.glyphicon-save:before{content:"\e166"} +.glyphicon-open:before{content:"\e167"} +.glyphicon-saved:before{content:"\e168"} +.glyphicon-import:before{content:"\e169"} +.glyphicon-export:before{content:"\e170"} +.glyphicon-send:before{content:"\e171"} +.glyphicon-floppy-disk:before{content:"\e172"} +.glyphicon-floppy-saved:before{content:"\e173"} +.glyphicon-floppy-remove:before{content:"\e174"} +.glyphicon-floppy-save:before{content:"\e175"} +.glyphicon-floppy-open:before{content:"\e176"} +.glyphicon-credit-card:before{content:"\e177"} +.glyphicon-transfer:before{content:"\e178"} +.glyphicon-cutlery:before{content:"\e179"} +.glyphicon-header:before{content:"\e180"} +.glyphicon-compressed:before{content:"\e181"} +.glyphicon-earphone:before{content:"\e182"} +.glyphicon-phone-alt:before{content:"\e183"} +.glyphicon-tower:before{content:"\e184"} +.glyphicon-stats:before{content:"\e185"} +.glyphicon-sd-video:before{content:"\e186"} +.glyphicon-hd-video:before{content:"\e187"} +.glyphicon-subtitles:before{content:"\e188"} +.glyphicon-sound-stereo:before{content:"\e189"} +.glyphicon-sound-dolby:before{content:"\e190"} +.glyphicon-sound-5-1:before{content:"\e191"} +.glyphicon-sound-6-1:before{content:"\e192"} +.glyphicon-sound-7-1:before{content:"\e193"} +.glyphicon-copyright-mark:before{content:"\e194"} +.glyphicon-registration-mark:before{content:"\e195"} +.glyphicon-cloud-download:before{content:"\e197"} +.glyphicon-cloud-upload:before{content:"\e198"} +.glyphicon-tree-conifer:before{content:"\e199"} +.glyphicon-tree-deciduous:before{content:"\e200"} +.glyphicon-cd:before{content:"\e201"} +.glyphicon-save-file:before{content:"\e202"} +.glyphicon-open-file:before{content:"\e203"} +.glyphicon-level-up:before{content:"\e204"} +.glyphicon-copy:before{content:"\e205"} +.glyphicon-paste:before{content:"\e206"} +.glyphicon-alert:before{content:"\e209"} +.glyphicon-equalizer:before{content:"\e210"} +.glyphicon-king:before{content:"\e211"} +.glyphicon-queen:before{content:"\e212"} +.glyphicon-pawn:before{content:"\e213"} +.glyphicon-bishop:before{content:"\e214"} +.glyphicon-knight:before{content:"\e215"} +.glyphicon-baby-formula:before{content:"\e216"} +.glyphicon-tent:before{content:"\26fa"} +.glyphicon-blackboard:before{content:"\e218"} +.glyphicon-bed:before{content:"\e219"} +.glyphicon-apple:before{content:"\f8ff"} +.glyphicon-erase:before{content:"\e221"} +.glyphicon-hourglass:before{content:"\231b"} +.glyphicon-lamp:before{content:"\e223"} +.glyphicon-duplicate:before{content:"\e224"} +.glyphicon-piggy-bank:before{content:"\e225"} +.glyphicon-scissors:before{content:"\e226"} +.glyphicon-bitcoin:before,.glyphicon-btc:before,.glyphicon-xbt:before{content:"\e227"} +.glyphicon-jpy:before,.glyphicon-yen:before{content:"\00a5"} +.glyphicon-rub:before,.glyphicon-ruble:before{content:"\20bd"} +.glyphicon-scale:before{content:"\e230"} +.glyphicon-ice-lolly:before{content:"\e231"} +.glyphicon-ice-lolly-tasted:before{content:"\e232"} +.glyphicon-education:before{content:"\e233"} +.glyphicon-option-horizontal:before{content:"\e234"} +.glyphicon-option-vertical:before{content:"\e235"} +.glyphicon-menu-hamburger:before{content:"\e236"} +.glyphicon-modal-window:before{content:"\e237"} +.glyphicon-oil:before{content:"\e238"} +.glyphicon-grain:before{content:"\e239"} +.glyphicon-sunglasses:before{content:"\e240"} +.glyphicon-text-size:before{content:"\e241"} +.glyphicon-text-color:before{content:"\e242"} +.glyphicon-text-background:before{content:"\e243"} +.glyphicon-object-align-top:before{content:"\e244"} +.glyphicon-object-align-bottom:before{content:"\e245"} +.glyphicon-object-align-horizontal:before{content:"\e246"} +.glyphicon-object-align-left:before{content:"\e247"} +.glyphicon-object-align-vertical:before{content:"\e248"} +.glyphicon-object-align-right:before{content:"\e249"} +.glyphicon-triangle-right:before{content:"\e250"} +.glyphicon-triangle-left:before{content:"\e251"} +.glyphicon-triangle-bottom:before{content:"\e252"} +.glyphicon-triangle-top:before{content:"\e253"} +.glyphicon-console:before{content:"\e254"} +.glyphicon-superscript:before{content:"\e255"} +.glyphicon-subscript:before{content:"\e256"} +.glyphicon-menu-left:before{content:"\e257"} +.glyphicon-menu-right:before{content:"\e258"} +.glyphicon-menu-down:before{content:"\e259"} +.glyphicon-menu-up:before{content:"\e260"} +*,:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} +body{font-family:"Source Sans Pro",Calibri,Candara,Arial,sans-serif;font-size:15px;line-height:1.42857143;color:#333} +button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit} +a{color:#34bdb7;text-decoration:none} +a:focus,a:hover{color:#23817d;text-decoration:underline} +a:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px} +.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto} +.img-rounded{border-radius:0} +.img-thumbnail{padding:4px;line-height:1.42857143;border:1px solid #ddd;border-radius:0;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto} +.img-circle{border-radius:50%} +hr{margin-top:21px;margin-bottom:21px;border:0;border-top:1px solid #e6e6e6} +.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0} +.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} +[role=button]{cursor:pointer} +.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:"Source Sans Pro",Calibri,Candara,Arial,sans-serif;font-weight:300;line-height:1.1;color:inherit} +.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#999} +.h1,.h2,.h3,h1,h2,h3{margin-top:21px;margin-bottom:10.5px} +.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%} +.h4,.h5,.h6,h4,h5,h6{margin-top:10.5px;margin-bottom:10.5px} +.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%} +.h1,h1{font-size:39px} +.h2,h2{font-size:32px} +.h3,h3{font-size:26px} +.h4,h4{font-size:19px} +.h5,h5{font-size:15px} +.h6,h6{font-size:13px} +p{margin:0 0 10.5px} +.lead{margin-bottom:21px;font-size:17px;font-weight:300;line-height:1.4} +address,blockquote .small,blockquote footer,blockquote small,dd,dt,pre{line-height:1.42857143} +dt,kbd kbd,label{font-weight:700} +@media (min-width:768px){.lead{font-size:22.5px} +} +.small,small{font-size:86%} +.mark,mark{background-color:#ff7518;padding:.2em} +.list-inline,.list-unstyled{list-style:none;padding-left:0} +.text-left{text-align:left} +.text-right{text-align:right} +.text-center{text-align:center} +.text-justify{text-align:justify} +.text-nowrap{white-space:nowrap} +.text-lowercase{text-transform:lowercase} +.text-uppercase{text-transform:uppercase} +.text-capitalize{text-transform:capitalize} +.text-muted{color:#999} +.text-primary{color:#34bdb7} +a.text-primary:focus,a.text-primary:hover{color:#299590} +a.text-danger:focus,a.text-danger:hover,a.text-info:focus,a.text-info:hover,a.text-success:focus,a.text-success:hover,a.text-warning:focus,a.text-warning:hover{color:#e6e6e6} +.bg-primary{color:#fff;background-color:#34bdb7} +a.bg-primary:focus,a.bg-primary:hover{background-color:#299590} +.bg-success{background-color:#3fb618} +a.bg-success:focus,a.bg-success:hover{background-color:#2f8912} +.bg-info{background-color:#9954bb} +a.bg-info:focus,a.bg-info:hover{background-color:#7e3f9d} +.bg-warning{background-color:#ff7518} +a.bg-warning:focus,a.bg-warning:hover{background-color:#e45c00} +.bg-danger{background-color:#ff0039} +a.bg-danger:focus,a.bg-danger:hover{background-color:#cc002e} +pre code,table{background-color:transparent} +.page-header{padding-bottom:9.5px;margin:42px 0 21px;border-bottom:1px solid #e6e6e6} +dl,ol,ul{margin-top:0} +ol,ul{margin-bottom:10.5px} +ol ol,ol ul,ul ol,ul ul{margin-bottom:0} +.list-inline{margin-left:-5px} +.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px} +dl{margin-bottom:21px} +dd{margin-left: 30px;} +@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.dl-horizontal dd{margin-left:180px} +} +abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #999} +.initialism{font-size:90%;text-transform:uppercase} +blockquote{padding:10.5px 21px;margin:0 0 21px;font-size:18.75px;border-left:5px solid #e6e6e6} +blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0} +blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;color:#999} +blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'} +.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #e6e6e6;border-left:0;text-align:right} +caption,th{text-align:left} +code,kbd{padding:2px 4px;font-size:90%;border-radius:0} +.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''} +.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'} +address{margin-bottom:21px;font-style:normal} +code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace} +code{color:#c7254e;background-color:#f9f2f4} +kbd{color:#fff;background-color:#333;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)} +kbd kbd{padding:0;font-size:100%;box-shadow:none} +pre{display:block;padding:10px;margin:0 0 10.5px;font-size:14px;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:0} +.container,.container-fluid{margin-right:auto;margin-left:auto} +pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;border-radius:0} +.container,.container-fluid{padding-left:15px;padding-right:15px} +.pre-scrollable{overflow-y:scroll} +@media (min-width:768px){.container{width:750px} +} +@media (min-width:992px){.container{width:970px} +} +@media (min-width:1200px){.container{width:1170px} +} +.row{margin-left:-15px;margin-right:-15px} +.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-left:15px;padding-right:15px} +.col-xs-12{width:100%} +.col-xs-11{width:91.66666667%} +.col-xs-10{width:83.33333333%} +.col-xs-9{width:75%} +.col-xs-8{width:66.66666667%} +.col-xs-7{width:58.33333333%} +.col-xs-6{width:50%} +.col-xs-5{width:41.66666667%} +.col-xs-4{width:33.33333333%} +.col-xs-3{width:25%} +.col-xs-2{width:16.66666667%} +.col-xs-1{width:8.33333333%} +.col-xs-pull-12{right:100%} +.col-xs-pull-11{right:91.66666667%} +.col-xs-pull-10{right:83.33333333%} +.col-xs-pull-9{right:75%} +.col-xs-pull-8{right:66.66666667%} +.col-xs-pull-7{right:58.33333333%} +.col-xs-pull-6{right:50%} +.col-xs-pull-5{right:41.66666667%} +.col-xs-pull-4{right:33.33333333%} +.col-xs-pull-3{right:25%} +.col-xs-pull-2{right:16.66666667%} +.col-xs-pull-1{right:8.33333333%} +.col-xs-pull-0{right:auto} +.col-xs-push-12{left:100%} +.col-xs-push-11{left:91.66666667%} +.col-xs-push-10{left:83.33333333%} +.col-xs-push-9{left:75%} +.col-xs-push-8{left:66.66666667%} +.col-xs-push-7{left:58.33333333%} +.col-xs-push-6{left:50%} +.col-xs-push-5{left:41.66666667%} +.col-xs-push-4{left:33.33333333%} +.col-xs-push-3{left:25%} +.col-xs-push-2{left:16.66666667%} +.col-xs-push-1{left:8.33333333%} +.col-xs-push-0{left:auto} +.col-xs-offset-12{margin-left:100%} +.col-xs-offset-11{margin-left:91.66666667%} +.col-xs-offset-10{margin-left:83.33333333%} +.col-xs-offset-9{margin-left:75%} +.col-xs-offset-8{margin-left:66.66666667%} +.col-xs-offset-7{margin-left:58.33333333%} +.col-xs-offset-6{margin-left:50%} +.col-xs-offset-5{margin-left:41.66666667%} +.col-xs-offset-4{margin-left:33.33333333%} +.col-xs-offset-3{margin-left:25%} +.col-xs-offset-2{margin-left:16.66666667%} +.col-xs-offset-1{margin-left:8.33333333%} +.col-xs-offset-0{margin-left:0} +@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left} +.col-sm-12{width:100%} +.col-sm-11{width:91.66666667%} +.col-sm-10{width:83.33333333%} +.col-sm-9{width:75%} +.col-sm-8{width:66.66666667%} +.col-sm-7{width:58.33333333%} +.col-sm-6{width:50%} +.col-sm-5{width:41.66666667%} +.col-sm-4{width:33.33333333%} +.col-sm-3{width:25%} +.col-sm-2{width:16.66666667%} +.col-sm-1{width:8.33333333%} +.col-sm-pull-12{right:100%} +.col-sm-pull-11{right:91.66666667%} +.col-sm-pull-10{right:83.33333333%} +.col-sm-pull-9{right:75%} +.col-sm-pull-8{right:66.66666667%} +.col-sm-pull-7{right:58.33333333%} +.col-sm-pull-6{right:50%} +.col-sm-pull-5{right:41.66666667%} +.col-sm-pull-4{right:33.33333333%} +.col-sm-pull-3{right:25%} +.col-sm-pull-2{right:16.66666667%} +.col-sm-pull-1{right:8.33333333%} +.col-sm-pull-0{right:auto} +.col-sm-push-12{left:100%} +.col-sm-push-11{left:91.66666667%} +.col-sm-push-10{left:83.33333333%} +.col-sm-push-9{left:75%} +.col-sm-push-8{left:66.66666667%} +.col-sm-push-7{left:58.33333333%} +.col-sm-push-6{left:50%} +.col-sm-push-5{left:41.66666667%} +.col-sm-push-4{left:33.33333333%} +.col-sm-push-3{left:25%} +.col-sm-push-2{left:16.66666667%} +.col-sm-push-1{left:8.33333333%} +.col-sm-push-0{left:auto} +.col-sm-offset-12{margin-left:100%} +.col-sm-offset-11{margin-left:91.66666667%} +.col-sm-offset-10{margin-left:83.33333333%} +.col-sm-offset-9{margin-left:75%} +.col-sm-offset-8{margin-left:66.66666667%} +.col-sm-offset-7{margin-left:58.33333333%} +.col-sm-offset-6{margin-left:50%} +.col-sm-offset-5{margin-left:41.66666667%} +.col-sm-offset-4{margin-left:33.33333333%} +.col-sm-offset-3{margin-left:25%} +.col-sm-offset-2{margin-left:16.66666667%} +.col-sm-offset-1{margin-left:8.33333333%} +.col-sm-offset-0{margin-left:0} +} +@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left} +.col-md-12{width:100%} +.col-md-11{width:91.66666667%} +.col-md-10{width:83.33333333%} +.col-md-9{width:75%} +.col-md-8{width:66.66666667%} +.col-md-7{width:58.33333333%} +.col-md-6{width:50%} +.col-md-5{width:41.66666667%} +.col-md-4{width:33.33333333%} +.col-md-3{width:25%} +.col-md-2{width:16.66666667%} +.col-md-1{width:8.33333333%} +.col-md-pull-12{right:100%} +.col-md-pull-11{right:91.66666667%} +.col-md-pull-10{right:83.33333333%} +.col-md-pull-9{right:75%} +.col-md-pull-8{right:66.66666667%} +.col-md-pull-7{right:58.33333333%} +.col-md-pull-6{right:50%} +.col-md-pull-5{right:41.66666667%} +.col-md-pull-4{right:33.33333333%} +.col-md-pull-3{right:25%} +.col-md-pull-2{right:16.66666667%} +.col-md-pull-1{right:8.33333333%} +.col-md-pull-0{right:auto} +.col-md-push-12{left:100%} +.col-md-push-11{left:91.66666667%} +.col-md-push-10{left:83.33333333%} +.col-md-push-9{left:75%} +.col-md-push-8{left:66.66666667%} +.col-md-push-7{left:58.33333333%} +.col-md-push-6{left:50%} +.col-md-push-5{left:41.66666667%} +.col-md-push-4{left:33.33333333%} +.col-md-push-3{left:25%} +.col-md-push-2{left:16.66666667%} +.col-md-push-1{left:8.33333333%} +.col-md-push-0{left:auto} +.col-md-offset-12{margin-left:100%} +.col-md-offset-11{margin-left:91.66666667%} +.col-md-offset-10{margin-left:83.33333333%} +.col-md-offset-9{margin-left:75%} +.col-md-offset-8{margin-left:66.66666667%} +.col-md-offset-7{margin-left:58.33333333%} +.col-md-offset-6{margin-left:50%} +.col-md-offset-5{margin-left:41.66666667%} +.col-md-offset-4{margin-left:33.33333333%} +.col-md-offset-3{margin-left:25%} +.col-md-offset-2{margin-left:16.66666667%} +.col-md-offset-1{margin-left:8.33333333%} +.col-md-offset-0{margin-left:0} +} +@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left} +.col-lg-12{width:100%} +.col-lg-11{width:91.66666667%} +.col-lg-10{width:83.33333333%} +.col-lg-9{width:75%} +.col-lg-8{width:66.66666667%} +.col-lg-7{width:58.33333333%} +.col-lg-6{width:50%} +.col-lg-5{width:41.66666667%} +.col-lg-4{width:33.33333333%} +.col-lg-3{width:25%} +.col-lg-2{width:16.66666667%} +.col-lg-1{width:8.33333333%} +.col-lg-pull-12{right:100%} +.col-lg-pull-11{right:91.66666667%} +.col-lg-pull-10{right:83.33333333%} +.col-lg-pull-9{right:75%} +.col-lg-pull-8{right:66.66666667%} +.col-lg-pull-7{right:58.33333333%} +.col-lg-pull-6{right:50%} +.col-lg-pull-5{right:41.66666667%} +.col-lg-pull-4{right:33.33333333%} +.col-lg-pull-3{right:25%} +.col-lg-pull-2{right:16.66666667%} +.col-lg-pull-1{right:8.33333333%} +.col-lg-pull-0{right:auto} +.col-lg-push-12{left:100%} +.col-lg-push-11{left:91.66666667%} +.col-lg-push-10{left:83.33333333%} +.col-lg-push-9{left:75%} +.col-lg-push-8{left:66.66666667%} +.col-lg-push-7{left:58.33333333%} +.col-lg-push-6{left:50%} +.col-lg-push-5{left:41.66666667%} +.col-lg-push-4{left:33.33333333%} +.col-lg-push-3{left:25%} +.col-lg-push-2{left:16.66666667%} +.col-lg-push-1{left:8.33333333%} +.col-lg-push-0{left:auto} +.col-lg-offset-12{margin-left:100%} +.col-lg-offset-11{margin-left:91.66666667%} +.col-lg-offset-10{margin-left:83.33333333%} +.col-lg-offset-9{margin-left:75%} +.col-lg-offset-8{margin-left:66.66666667%} +.col-lg-offset-7{margin-left:58.33333333%} +.col-lg-offset-6{margin-left:50%} +.col-lg-offset-5{margin-left:41.66666667%} +.col-lg-offset-4{margin-left:33.33333333%} +.col-lg-offset-3{margin-left:25%} +.col-lg-offset-2{margin-left:16.66666667%} +.col-lg-offset-1{margin-left:8.33333333%} +.col-lg-offset-0{margin-left:0} +} +caption{padding-top:8px;padding-bottom:8px;color:#999} +.table{width:100%;max-width:100%;margin-bottom:21px} +.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd} +.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd} +.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0} +.table>tbody+tbody{border-top:2px solid #ddd} +.table .table{background-color:#fff} +.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px} +.table-bordered,.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd} +.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px} +.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9} +.table-hover>tbody>tr:hover,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5} +table col[class*=col-]{position:static;float:none;display:table-column} +table td[class*=col-],table th[class*=col-]{position:static;float:none;display:table-cell} +.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8} +.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#3fb618} +.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#379f15} +.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#9954bb} +.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#8d46b0} +.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#ff7518} +.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#fe6600} +.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#ff0039} +.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#e60033} +.table-responsive{overflow-x:auto;min-height:.01%} +@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15.75px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd} +.table-responsive>.table{margin-bottom:0} +.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap} +.table-responsive>.table-bordered{border:0} +.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0} +.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0} +.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0} +} +fieldset,legend{padding:0;border:0} +fieldset{margin:0;min-width:0} +legend{display:block;width:100%;margin-bottom:21px;font-size:22.5px;line-height:inherit;color:#333;border-bottom:1px solid #e5e5e5} +label{display:inline-block;max-width:100%;margin-bottom:5px} +input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none} +input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal} +.form-control,output{display:block;font-size:15px;line-height:1.42857143;color:#333} +input[type=file]{display:block} +input[type=range]{display:block;width:100%} +select[multiple],select[size]{height:auto} +input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px} +output{padding-top:11px} +.form-control{width:100%;height:43px;padding:10px 18px;background-color:#fff;border:1px solid #ccc;border-radius:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s} +.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)} +.form-control::-moz-placeholder{color:#999;opacity:1} +.form-control:-ms-input-placeholder{color:#999} +.form-control::-webkit-input-placeholder{color:#999} +.form-control::-ms-expand{border:0;background-color:transparent} +.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#e6e6e6;opacity:1} +.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed} +textarea.form-control{height:auto} +@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:43px} +.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:31px} +.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:64px} +} +.form-group{margin-bottom:15px} +.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px} +.checkbox label,.radio label{min-height:21px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer} +.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-left:-20px;margin-top:4px\9} +.checkbox+.checkbox,.radio+.radio{margin-top:-5px} +.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer} +.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px} +.checkbox-inline.disabled,.checkbox.disabled label,.radio-inline.disabled,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio label,fieldset[disabled] .radio-inline,fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed} +.form-control-static{padding-top:11px;padding-bottom:11px;margin-bottom:0;min-height:36px} +.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0} +.form-group-sm .form-control,.input-sm{font-size:13px;padding:5px 10px;border-radius:0} +.input-sm{height:31px;line-height:1.5} +select.input-sm{height:31px;line-height:31px} +select[multiple].input-sm,textarea.input-sm{height:auto} +.form-group-sm .form-control{height:31px;line-height:1.5} +.form-group-sm select.form-control{height:31px;line-height:31px} +.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto} +.form-group-sm .form-control-static{height:31px;min-height:34px;padding:6px 10px;font-size:13px;line-height:1.5} +.form-group-lg .form-control,.input-lg{font-size:19px;padding:18px 30px;border-radius:0} +.input-lg{height:64px;line-height:1.3333333} +select.input-lg{height:64px;line-height:64px} +select[multiple].input-lg,textarea.input-lg{height:auto} +.form-group-lg .form-control{height:64px;line-height:1.3333333} +.form-group-lg select.form-control{height:64px;line-height:64px} +.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto} +.form-group-lg .form-control-static{height:64px;min-height:40px;padding:19px 30px;font-size:19px;line-height:1.3333333} +.has-feedback{position:relative} +.has-feedback .form-control{padding-right:53.75px} +.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:43px;height:43px;line-height:43px;text-align:center;pointer-events:none} +.collapsing,.dropdown,.dropup{position:relative} +.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:64px;height:64px;line-height:64px} +.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:31px;height:31px;line-height:31px} +.has-success .form-control{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} +.has-success .form-control:focus{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #fff} +.has-success .input-group-addon{color:#fff;background-color:#3fb618} +.has-warning .form-control{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} +.has-warning .form-control:focus{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #fff} +.has-warning .input-group-addon{color:#fff;background-color:#ff7518} +.has-error .form-control{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} +.has-error .form-control:focus{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #fff} +.has-error .input-group-addon{color:#fff;background-color:#ff0039} +.has-feedback label~.form-control-feedback{top:26px} +.has-feedback label.sr-only~.form-control-feedback{top:0} +.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373} +@media (min-width:768px){.form-inline .form-control-static,.form-inline .form-group{display:inline-block} +.form-inline .control-label,.form-inline .form-group{margin-bottom:0;vertical-align:middle} +.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle} +.form-inline .input-group{display:inline-table;vertical-align:middle} +.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto} +.form-inline .input-group>.form-control{width:100%} +.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle} +.form-inline .checkbox label,.form-inline .radio label{padding-left:0} +.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0} +.form-inline .has-feedback .form-control-feedback{top:0} +} +.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{margin-top:0;margin-bottom:0;padding-top:11px} +.form-horizontal .checkbox,.form-horizontal .radio{min-height:32px} +.form-horizontal .form-group{margin-left:-15px;margin-right:-15px} +.form-horizontal .has-feedback .form-control-feedback{right:15px} +@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:11px} +.form-horizontal .form-group-lg .control-label{padding-top:19px;font-size:19px} +.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:13px} +} +.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;border:1px solid transparent;white-space:nowrap;padding:10px 18px;font-size:15px;line-height:1.42857143;border-radius:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} +.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px} +.btn.focus,.btn:focus,.btn:hover{color:#fff;text-decoration:none} +.btn.active,.btn:active{outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)} +.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none} +a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none} +.btn-default{color:#fff;background-color:#222;border-color:#222} +.btn-default.focus,.btn-default:focus{color:#fff;background-color:#090909;border-color:#000} +.btn-default.active,.btn-default:active,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#fff;background-color:#090909;border-color:#040404} +.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#fff;background-color:#000;border-color:#000} +.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#222;border-color:#222} +.btn-default .badge{color:#222;background-color:#fff} +.btn-primary{color:#fff;background-color:#34bdb7;border-color:#34bdb7} +.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#299590;border-color:#185956} +.btn-primary.active,.btn-primary:active,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#299590;border-color:#278d89} +.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#217975;border-color:#185956} +.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#34bdb7;border-color:#34bdb7} +.btn-primary .badge{color:#34bdb7;background-color:#fff} +.btn-success{color:#fff;background-color:#3fb618;border-color:#3fb618} +.btn-success.focus,.btn-success:focus{color:#fff;background-color:#2f8912;border-color:#184509} +.btn-success.active,.btn-success:active,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#2f8912;border-color:#2c8011} +.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#24690e;border-color:#184509} +.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none} +.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#3fb618;border-color:#3fb618} +.btn-success .badge{color:#3fb618;background-color:#fff} +.btn-info{color:#fff;background-color:#9954bb;border-color:#9954bb} +.btn-info.focus,.btn-info:focus{color:#fff;background-color:#7e3f9d;border-color:#522967} +.btn-info.active,.btn-info:active,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#7e3f9d;border-color:#783c96} +.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#6a3484;border-color:#522967} +.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#9954bb;border-color:#9954bb} +.btn-info .badge{color:#9954bb;background-color:#fff} +.btn-warning{color:#fff;background-color:#ff7518;border-color:#ff7518} +.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#e45c00;border-color:#983d00} +.btn-warning.active,.btn-warning:active,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#e45c00;border-color:#da5800} +.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#c04d00;border-color:#983d00} +.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#ff7518;border-color:#ff7518} +.btn-warning .badge{color:#ff7518;background-color:#fff} +.btn-danger{color:#fff;background-color:#ff0039;border-color:#ff0039} +.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#cc002e;border-color:#80001c} +.btn-danger.active,.btn-danger:active,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#cc002e;border-color:#c2002b} +.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#a80026;border-color:#80001c} +.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#ff0039;border-color:#ff0039} +.btn-danger .badge{color:#ff0039;background-color:#fff} +.btn-link{color:#34bdb7;font-weight:400;border-radius:0} +.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none} +.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent} +.btn-link:focus,.btn-link:hover{color:#23817d;text-decoration:underline;background-color:transparent} +.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#999;text-decoration:none} +.btn-group-lg>.btn,.btn-lg{padding:18px 30px;font-size:19px;line-height:1.3333333;border-radius:0} +.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:13px;line-height:1.5;border-radius:0} +.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:13px;line-height:1.5;border-radius:0} +.btn-block{display:block;width:100%} +.btn-block+.btn-block{margin-top:5px} +input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%} +.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear} +.fade.in{opacity:1} +.collapse{display:none} +.collapse.in{display:block} +tr.collapse.in{display:table-row} +tbody.collapse.in{display:table-row-group} +.collapsing{height:0;overflow:hidden;-webkit-transition-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:.35s;transition-duration:.35s;-webkit-transition-timing-function:ease;transition-timing-function:ease} +.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent} +.dropdown-toggle:focus{outline:0} +.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:15px;text-align:left;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:0;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box} +.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child,.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0} +.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle,.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0} +.dropdown-header,.dropdown-menu>li>a{white-space:nowrap;padding:3px 20px;line-height:1.42857143} +.dropdown-menu-right,.dropdown-menu.pull-right{left:auto;right:0} +.dropdown-menu .divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:#e5e5e5} +.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group,.input-group-btn>.btn+.btn{margin-left:-1px} +.dropdown-menu>li>a{display:block;clear:both;font-weight:400;color:#333} +.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{text-decoration:none;color:#fff;background-color:#34bdb7} +.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;outline:0;background-color:#34bdb7} +.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#999} +.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed} +.open>.dropdown-menu{display:block} +.open>a{outline:0} +.dropdown-menu-left{left:0;right:auto} +.dropdown-header{display:block;font-size:13px;color:#999} +.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990} +.nav-justified>.dropdown .dropdown-menu,.nav-tabs.nav-justified>.dropdown .dropdown-menu{left:auto;top:auto} +.pull-right>.dropdown-menu{right:0;left:auto} +.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9;content:""} +.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px} +@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0} +.navbar-right .dropdown-menu-left{left:0;right:auto} +} +.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle} +.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left} +.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2} +.btn-toolbar{margin-left:-5px} +.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px} +.btn .caret,.btn-group>.btn:first-child{margin-left:0} +.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0} +.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0} +.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0} +.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px} +.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px} +.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)} +.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none} +.btn-lg .caret{border-width:5px 5px 0} +.dropup .btn-lg .caret{border-width:0 5px 5px} +.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%} +.btn-group-vertical>.btn-group>.btn{float:none} +.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0} +.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn,.btn-group-vertical>.btn:first-child:not(:last-child),.btn-group-vertical>.btn:last-child:not(:first-child),.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0} +.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0} +.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0} +.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate} +.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%} +.btn-group-justified>.btn-group .btn{width:100%} +.btn-group-justified>.btn-group .dropdown-menu{left:auto} +[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none} +.input-group{position:relative;display:table;border-collapse:separate} +.input-group[class*=col-]{float:none;padding-left:0;padding-right:0} +.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0} +.input-group .form-control:focus{z-index:3} +.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:64px;padding:18px 30px;font-size:19px;line-height:1.3333333;border-radius:0} +select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:64px;line-height:64px} +select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto} +.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:31px;padding:5px 10px;font-size:13px;line-height:1.5;border-radius:0} +select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:31px;line-height:31px} +select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto} +.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell} +.nav>li,.nav>li>a{position:relative;display:block} +.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0} +.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle} +.input-group-addon{padding:10px 18px;font-size:15px;font-weight:400;line-height:1;color:#333;text-align:center;background-color:#e6e6e6;border:1px solid #ccc;border-radius:0} +.badge,.label{text-align:center;font-weight:700;white-space:nowrap} +.input-group-addon.input-sm{padding:5px 10px;font-size:13px;border-radius:0} +.input-group-addon.input-lg{padding:18px 30px;font-size:19px;border-radius:0} +.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0} +.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0} +.input-group-addon:first-child{border-right:0} +.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-bottom-left-radius:0;border-top-left-radius:0} +.input-group-addon:last-child{border-left:0} +.input-group-btn{position:relative;font-size:0;white-space:nowrap} +.input-group-btn>.btn{position:relative} +.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2} +.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px} +.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px} +.nav{margin-bottom:0;padding-left:0;list-style:none} +.nav>li>a{padding:10px 15px} +.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#e6e6e6} +.nav>li.disabled>a{color:#999} +.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#999;text-decoration:none;background-color:transparent;cursor:not-allowed} +.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#e6e6e6;border-color:#34bdb7} +.nav .nav-divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:#e5e5e5} +.nav>li>a>img{max-width:none} +.nav-tabs{border-bottom:1px solid #ddd} +.nav-tabs>li{float:left;margin-bottom:-1px} +.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:0} +.nav-tabs>li>a:hover{border-color:#e6e6e6 #e6e6e6 #ddd} +.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default} +.nav-tabs.nav-justified{width:100%;border-bottom:0} +.nav-tabs.nav-justified>li{float:none} +.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px;margin-right:0;border-radius:0} +.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd} +@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%} +.nav-tabs.nav-justified>li>a{margin-bottom:0;border-bottom:1px solid #ddd;border-radius:0} +.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff} +} +.nav-pills>li{float:left} +.nav-justified>li,.nav-stacked>li{float:none} +.nav-pills>li+li{margin-left:2px} +.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#34bdb7} +.nav-stacked>li+li{margin-top:2px;margin-left:0} +.nav-justified{width:100%} +.nav-justified>li>a{text-align:center;margin-bottom:5px} +.nav-tabs-justified{border-bottom:0} +.nav-tabs-justified>li>a{margin-right:0;border-radius:0} +.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd} +@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%} +.nav-justified>li>a{margin-bottom:0} +.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:0} +.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff} +} +.tab-content>.tab-pane{display:none} +.tab-content>.active{display:block} +.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0} +.navbar{position:relative;min-height:50px;margin-bottom:21px;border:1px solid transparent} +.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch} +.navbar-collapse.in{overflow-y:auto} +@media (min-width:768px){.navbar{border-radius:0} +.navbar-header{float:left} +.navbar-collapse{width:auto;border-top:0;box-shadow:none} +.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important} +.navbar-collapse.in{overflow-y:visible} +.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-left:0;padding-right:0} +} +.carousel-inner,.embed-responsive,.modal,.modal-open,.progress{overflow:hidden} +@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px} +} +.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px} +.navbar-static-top{z-index:1000;border-width:0 0 1px} +.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030} +.navbar-fixed-top{top:0;border-width:0 0 1px} +.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0} +.navbar-brand{float:left;padding:14.5px 15px;font-size:19px;line-height:21px;height:50px} +.navbar-brand:focus,.navbar-brand:hover{text-decoration:none} +.navbar-brand>img{display:block} +@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0} +.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0} +.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px} +} +.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:0} +.navbar-toggle:focus{outline:0} +.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px} +.navbar-toggle .icon-bar+.icon-bar{margin-top:4px} +.navbar-nav{margin:7.25px -15px} +.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:21px} +@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none} +.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px} +.navbar-nav .open .dropdown-menu>li>a{line-height:21px} +.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none} +} +.progress-bar-striped,.progress-striped .progress-bar,.progress-striped .progress-bar-danger,.progress-striped .progress-bar-info,.progress-striped .progress-bar-success,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} +@media (min-width:768px){.navbar-toggle{display:none} +.navbar-nav{float:left;margin:0} +.navbar-nav>li{float:left} +.navbar-nav>li>a{padding-top:14.5px;padding-bottom:14.5px} +} +.navbar-form{padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin:3.5px -15px} +@media (min-width:768px){.navbar-form .form-control-static,.navbar-form .form-group{display:inline-block} +.navbar-form .control-label,.navbar-form .form-group{margin-bottom:0;vertical-align:middle} +.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle} +.navbar-form .input-group{display:inline-table;vertical-align:middle} +.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto} +.navbar-form .input-group>.form-control{width:100%} +.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle} +.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0} +.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0} +.navbar-form .has-feedback .form-control-feedback{top:0} +} +.btn .badge,.btn .label{position:relative;top:-1px} +.breadcrumb>li,.pagination{display:inline-block} +@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px} +.navbar-form .form-group:last-child{margin-bottom:0} +} +@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none} +} +.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0} +.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-radius:0} +.navbar-btn{margin-top:3.5px;margin-bottom:3.5px} +.navbar-btn.btn-sm{margin-top:9.5px;margin-bottom:9.5px} +.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px} +.navbar-text{margin-top:14.5px;margin-bottom:14.5px} +@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px} +.navbar-left{float:left!important} +.navbar-right{float:right!important;margin-right:-15px} +.navbar-right~.navbar-right{margin-right:0} +} +.navbar-default{background-color:#222;border-color:#121212} +.navbar-default .navbar-brand{color:#fff} +.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#fff;background-color:none} +.navbar-default .navbar-nav>li>a,.navbar-default .navbar-text{color:#fff} +.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#fff;background-color:#090909} +.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent} +.navbar-default .navbar-toggle{border-color:transparent} +.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#090909} +.navbar-default .navbar-toggle .icon-bar{background-color:#fff} +.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#121212} +.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{background-color:#090909;color:#fff} +@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#fff} +.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:#090909} +.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent} +} +.navbar-default .btn-link,.navbar-default .btn-link:focus,.navbar-default .btn-link:hover,.navbar-default .navbar-link,.navbar-default .navbar-link:hover{color:#fff} +.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc} +.navbar-inverse{background-color:#34bdb7;border-color:#299590} +.navbar-inverse .navbar-brand{color:#fff} +.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:none} +.navbar-inverse .navbar-nav>li>a,.navbar-inverse .navbar-text{color:#fff} +.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:#299590} +.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#fff;background-color:transparent} +.navbar-inverse .navbar-toggle{border-color:transparent} +.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#299590} +.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff} +.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#2ca19c} +.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{background-color:#299590;color:#fff} +@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#299590} +.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#299590} +.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#fff} +.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:#299590} +.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#fff;background-color:transparent} +} +.navbar-inverse .btn-link,.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,.navbar-inverse .navbar-link,.navbar-inverse .navbar-link:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#fff} +.breadcrumb{padding:8px 15px;margin-bottom:21px;list-style:none;background-color:#f5f5f5;border-radius:0} +.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc} +.breadcrumb>.active{color:#999} +.pagination{padding-left:0;margin:21px 0;border-radius:0} +.pager li,.pagination>li{display:inline} +.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:10px 18px;line-height:1.42857143;text-decoration:none;color:#34bdb7;background-color:#fff;border:1px solid #ddd;margin-left:-1px} +.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span,.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0} +.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span,.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span,.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0} +.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:0;border-top-left-radius:0} +.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23817d;background-color:#e6e6e6;border-color:#ddd} +.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#999;background-color:#f5f5f5;border-color:#ddd;cursor:default} +.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#999;background-color:#fff;border-color:#ddd;cursor:not-allowed} +.pagination-lg>li>a,.pagination-lg>li>span{padding:18px 30px;font-size:19px;line-height:1.3333333} +.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:13px;line-height:1.5} +.badge,.close,.label{line-height:1} +.pager{padding-left:0;margin:21px 0;list-style:none;text-align:center} +.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:0} +.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#e6e6e6} +.pager .next>a,.pager .next>span{float:right} +.pager .previous>a,.pager .previous>span{float:left} +.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#999;background-color:#fff;cursor:not-allowed} +.label{display:inline;padding:.2em .6em .3em;font-size:75%;color:#fff} +a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer} +.label:empty{display:none} +.label-default{background-color:#222} +.label-default[href]:focus,.label-default[href]:hover{background-color:#090909} +.label-primary{background-color:#34bdb7} +.label-primary[href]:focus,.label-primary[href]:hover{background-color:#299590} +.label-success{background-color:#3fb618} +.label-success[href]:focus,.label-success[href]:hover{background-color:#2f8912} +.label-info{background-color:#9954bb} +.label-info[href]:focus,.label-info[href]:hover{background-color:#7e3f9d} +.label-warning{background-color:#ff7518} +.label-warning[href]:focus,.label-warning[href]:hover{background-color:#e45c00} +.label-danger{background-color:#ff0039} +.label-danger[href]:focus,.label-danger[href]:hover{background-color:#cc002e} +.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:13px;color:#fff;vertical-align:middle;background-color:#34bdb7;border-radius:10px} +.badge:empty{display:none} +.media-object,.thumbnail{display:block} +.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px} +a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer} +.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#34bdb7;background-color:#fff} +.jumbotron,.jumbotron .h1,.jumbotron h1{color:inherit} +.list-group-item>.badge{float:right} +.list-group-item>.badge+.badge{margin-right:5px} +.nav-pills>li>a>.badge{margin-left:3px} +.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;background-color:#e6e6e6} +.jumbotron p{margin-bottom:15px;font-size:23px;font-weight:200} +.alert .alert-link,.close{font-weight:700} +.alert,.thumbnail{margin-bottom:21px} +.jumbotron>hr{border-top-color:#ccc} +.container .jumbotron,.container-fluid .jumbotron{border-radius:0;padding-left:15px;padding-right:15px} +.jumbotron .container{max-width:100%} +@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px} +.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px} +.jumbotron .h1,.jumbotron h1{font-size:68px} +} +.thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:0;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out} +.thumbnail a>img,.thumbnail>img{margin-left:auto;margin-right:auto} +a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#34bdb7} +.thumbnail .caption{padding:9px;color:#333} +.alert{padding:15px;border-radius:0} +.alert h4{margin-top:0;color:inherit} +.alert>p,.alert>ul{margin-bottom:0} +.alert>p+p{margin-top:5px} +.alert-dismissable,.alert-dismissible{padding-right:35px} +.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit} +.modal,.modal-backdrop{right:0;bottom:0;left:0} +.alert-success{background-color:#3fb618;border-color:#4e9f15;color:#fff} +.alert-success hr{border-top-color:#438912} +.alert-success .alert-link{color:#e6e6e6} +.alert-info{background-color:#9954bb;border-color:#7643a8;color:#fff} +.alert-info hr{border-top-color:#693c96} +.alert-info .alert-link{color:#e6e6e6} +.alert-warning{background-color:#ff7518;border-color:#ff4309;color:#fff} +.alert-warning hr{border-top-color:#ee3800} +.alert-warning .alert-link{color:#e6e6e6} +.alert-danger{background-color:#ff0039;border-color:#f0005e;color:#fff} +.alert-danger hr{border-top-color:#d60054} +.alert-danger .alert-link{color:#e6e6e6} +@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0} +to{background-position:0 0} +} +@keyframes progress-bar-stripes{from{background-position:40px 0} +to{background-position:0 0} +} +.progress{margin-bottom:21px;background-color:#ccc;border-radius:0} +.progress-bar{float:left;width:0;height:100%;font-size:13px;line-height:21px;color:#fff;text-align:center;background-color:#34bdb7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease} +.progress-bar-striped,.progress-striped .progress-bar{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:40px 40px} +.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite} +.progress-bar-success{background-color:#3fb618} +.progress-striped .progress-bar-success{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} +.progress-bar-info{background-color:#9954bb} +.progress-striped .progress-bar-info{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} +.progress-bar-warning{background-color:#ff7518} +.progress-striped .progress-bar-warning{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} +.progress-bar-danger{background-color:#ff0039} +.progress-striped .progress-bar-danger{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} +.media{margin-top:15px} +.media:first-child{margin-top:0} +.media,.media-body{zoom:1;overflow:hidden} +.media-body{width:10000px} +.media-object.img-thumbnail{max-width:none} +.media-right,.media>.pull-right{padding-left:10px} +.media-left,.media>.pull-left{padding-right:10px} +.media-body,.media-left,.media-right{display:table-cell;vertical-align:top} +.media-middle{vertical-align:middle} +.media-bottom{vertical-align:bottom} +.media-heading{margin-top:0;margin-bottom:5px} +.media-list{padding-left:0;list-style:none} +.list-group{margin-bottom:20px;padding-left:0} +.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd} +.list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0} +.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0} +a.list-group-item,button.list-group-item{color:#555} +a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333} +a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{text-decoration:none;color:#555;background-color:#f5f5f5} +button.list-group-item{width:100%;text-align:left} +.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{background-color:#e6e6e6;color:#999;cursor:not-allowed} +.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit} +.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#999} +.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#34bdb7;border-color:#ddd} +.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit} +.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#ccf1ef} +.list-group-item-success{color:#fff;background-color:#3fb618} +a.list-group-item-success,button.list-group-item-success{color:#fff} +a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit} +a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#fff;background-color:#379f15} +a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#fff;border-color:#fff} +.list-group-item-info{color:#fff;background-color:#9954bb} +a.list-group-item-info,button.list-group-item-info{color:#fff} +a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit} +a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#fff;background-color:#8d46b0} +a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#fff;border-color:#fff} +.list-group-item-warning{color:#fff;background-color:#ff7518} +a.list-group-item-warning,button.list-group-item-warning{color:#fff} +a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit} +a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#fff;background-color:#fe6600} +a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#fff;border-color:#fff} +.list-group-item-danger{color:#fff;background-color:#ff0039} +a.list-group-item-danger,button.list-group-item-danger{color:#fff} +a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit} +a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#fff;background-color:#e60033} +a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#fff;border-color:#fff} +.panel-heading>.dropdown .dropdown-toggle,.panel-title,.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit} +.list-group-item-heading{margin-top:0;margin-bottom:5px} +.list-group-item-text{margin-bottom:0;line-height:1.3} +.panel{margin-bottom:21px;background-color:#fff;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)} +.panel-title,.panel>.list-group,.panel>.panel-collapse>.list-group,.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0} +.panel-body{padding:15px} +.panel-heading{padding:10px 15px;border-bottom:1px solid transparent} +.panel-group .panel-heading,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0} +.panel-title{margin-top:0;font-size:17px} +.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:-1;border-bottom-left-radius:-1} +.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0} +.panel>.table-responsive:last-child>.table:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:-1;border-bottom-left-radius:-1} +.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:-1;border-top-left-radius:-1} +.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:-1;border-bottom-left-radius:-1} +.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0} +.panel>.table-responsive:first-child>.table:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-right-radius:-1;border-top-left-radius:-1} +.list-group+.panel-footer,.panel-heading+.list-group .list-group-item:first-child{border-top-width:0} +.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-left:15px;padding-right:15px} +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:-1} +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:-1} +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:-1} +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:-1} +.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd} +.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0} +.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0} +.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0} +.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0} +.panel>.table-responsive{border:0;margin-bottom:0} +.panel-group{margin-bottom:21px} +.panel-group .panel{margin-bottom:0;border-radius:0} +.panel-group .panel+.panel{margin-top:5px} +.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd} +.panel-group .panel-footer{border-top:0} +.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd} +.panel-default{border-color:#ddd} +.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd} +.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd} +.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333} +.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd} +.panel-primary{border-color:#34bdb7} +.panel-primary>.panel-heading{color:#fff;background-color:#34bdb7;border-color:#34bdb7} +.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#34bdb7} +.panel-primary>.panel-heading .badge{color:#34bdb7;background-color:#fff} +.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#34bdb7} +.panel-success{border-color:#4e9f15} +.panel-success>.panel-heading{color:#fff;background-color:#3fb618;border-color:#4e9f15} +.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#4e9f15} +.panel-success>.panel-heading .badge{color:#3fb618;background-color:#fff} +.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#4e9f15} +.panel-info{border-color:#7643a8} +.panel-info>.panel-heading{color:#fff;background-color:#9954bb;border-color:#7643a8} +.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#7643a8} +.panel-info>.panel-heading .badge{color:#9954bb;background-color:#fff} +.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#7643a8} +.panel-warning{border-color:#ff4309} +.panel-warning>.panel-heading{color:#fff;background-color:#ff7518;border-color:#ff4309} +.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ff4309} +.panel-warning>.panel-heading .badge{color:#ff7518;background-color:#fff} +.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ff4309} +.panel-danger{border-color:#f0005e} +.panel-danger>.panel-heading{color:#fff;background-color:#ff0039;border-color:#f0005e} +.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#f0005e} +.panel-danger>.panel-heading .badge{color:#ff0039;background-color:#fff} +.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#f0005e} +.embed-responsive{position:relative;display:block;height:0;padding:0} +.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0} +.embed-responsive-16by9{padding-bottom:56.25%} +.embed-responsive-4by3{padding-bottom:75%} +.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)} +.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)} +.well-lg{padding:24px;border-radius:0} +.well-sm{padding:9px;border-radius:0} +.close{float:right;font-size:22.5px;color:#fff;filter:alpha(opacity=20)} +.popover,.tooltip{text-decoration:none;font-family:"Source Sans Pro",Calibri,Candara,Arial,sans-serif;font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal} +.close:focus,.close:hover{color:#fff;text-decoration:none;cursor:pointer;filter:alpha(opacity=50)} +button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none} +.modal-content,.popover{background-clip:padding-box} +.modal{display:none;position:fixed;top:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0} +.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out} +.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)} +.modal-open .modal{overflow-x:hidden;overflow-y:auto} +.modal-dialog{position:relative;width:auto;margin:10px} +.modal-content{position:relative;background-color:#fff;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);outline:0} +.modal-backdrop{position:fixed;top:0;z-index:1040;background-color:#000} +.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)} +.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)} +.modal-header{padding:15px;border-bottom:1px solid #e5e5e5} +.tooltip.bottom .tooltip-arrow,.tooltip.bottom-left .tooltip-arrow,.tooltip.bottom-right .tooltip-arrow{top:0;border-width:0 5px 5px;border-bottom-color:#000} +.modal-header .close{margin-top:-2px} +.modal-title{margin:0;line-height:1.42857143} +.modal-body{position:relative;padding:20px} +.modal-footer{padding:20px;text-align:right;border-top:1px solid #e5e5e5} +.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0} +.modal-footer .btn-group .btn+.btn{margin-left:-1px} +.modal-footer .btn-block+.btn-block{margin-left:0} +.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll} +@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto} +.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)} +.modal-sm{width:300px} +} +.tooltip.top-left .tooltip-arrow,.tooltip.top-right .tooltip-arrow{bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000} +@media (min-width:992px){.modal-lg{width:900px} +} +.tooltip{position:absolute;z-index:1070;display:block;text-align:left;text-align:start;font-size:13px;opacity:0;filter:alpha(opacity=0)} +.tooltip.in{opacity:.9;filter:alpha(opacity=90)} +.tooltip.top{margin-top:-3px;padding:5px 0} +.tooltip.right{margin-left:3px;padding:0 5px} +.tooltip.bottom{margin-top:3px;padding:5px 0} +.tooltip.left{margin-left:-3px;padding:0 5px} +.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:0} +.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid} +.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000} +.tooltip.top-left .tooltip-arrow{right:5px} +.tooltip.top-right .tooltip-arrow{left:5px} +.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000} +.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000} +.tooltip.bottom .tooltip-arrow{left:50%;margin-left:-5px} +.tooltip.bottom-left .tooltip-arrow{right:5px;margin-top:-5px} +.tooltip.bottom-right .tooltip-arrow{left:5px;margin-top:-5px} +.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;text-align:left;text-align:start;font-size:15px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:0;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)} +.carousel-caption,.carousel-control{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.6)} +.popover.top{margin-top:-10px} +.popover.right{margin-left:10px} +.popover.bottom{margin-top:10px} +.popover.left{margin-left:-10px} +.popover-title{margin:0;padding:8px 14px;font-size:15px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:-1 -1 0 0} +.popover-content{padding:9px 14px} +.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid} +.carousel,.carousel-inner{position:relative} +.popover>.arrow{border-width:11px} +.popover>.arrow:after{border-width:10px;content:""} +.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,.25);bottom:-11px} +.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff} +.popover.left>.arrow:after,.popover.right>.arrow:after{content:" ";bottom:-10px} +.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,.25)} +.popover.right>.arrow:after{left:1px;border-left-width:0;border-right-color:#fff} +.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25);top:-11px} +.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff} +.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)} +.popover.left>.arrow:after{right:1px;border-right-width:0;border-left-color:#fff} +.carousel-inner{width:100%} +.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left} +.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1} +@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-moz-transition:-moz-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;-moz-perspective:1000px;perspective:1000px} +.carousel-inner>.item.active.right,.carousel-inner>.item.next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);left:0} +.carousel-inner>.item.active.left,.carousel-inner>.item.prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);left:0} +.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);left:0} +} +.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block} +.carousel-inner>.active{left:0} +.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%} +.carousel-inner>.next{left:100%} +.carousel-inner>.prev{left:-100%} +.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0} +.carousel-inner>.active.left{left:-100%} +.carousel-inner>.active.right{left:100%} +.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;text-align:center;background-color:transparent} +.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)} +.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)} +.carousel-control:focus,.carousel-control:hover{outline:0;color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)} +.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;margin-top:-10px;z-index:5;display:inline-block} +.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px} +.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px} +.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;line-height:1;font-family:serif} +.carousel-control .icon-prev:before{content:'\2039'} +.carousel-control .icon-next:before{content:'\203a'} +.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center} +.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000\9;background-color:transparent} +.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff} +.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;text-align:center} +.carousel-caption .btn,.close,.text-hide{text-shadow:none} +@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px} +.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px} +.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px} +.carousel-caption{left:20%;right:20%;padding-bottom:30px} +.carousel-indicators{bottom:20px} +} +.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{content:" ";display:table} +.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both} +.center-block{display:block;margin-left:auto;margin-right:auto} +.pull-right{float:right!important} +.pull-left{float:left!important} +.hide{display:none!important} +.show{display:block!important} +.hidden,.visible-lg,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important} +.invisible{visibility:hidden} +.text-hide{font:0/0 a;color:transparent;background-color:transparent;border:0} +.affix{position:fixed} +@-ms-viewport{width:device-width} +@media (max-width:767px){.visible-xs{display:block!important} +table.visible-xs{display:table!important} +tr.visible-xs{display:table-row!important} +td.visible-xs,th.visible-xs{display:table-cell!important} +.visible-xs-block{display:block!important} +.visible-xs-inline{display:inline!important} +.visible-xs-inline-block{display:inline-block!important} +} +@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important} +table.visible-sm{display:table!important} +tr.visible-sm{display:table-row!important} +td.visible-sm,th.visible-sm{display:table-cell!important} +.visible-sm-block{display:block!important} +.visible-sm-inline{display:inline!important} +.visible-sm-inline-block{display:inline-block!important} +} +@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important} +table.visible-md{display:table!important} +tr.visible-md{display:table-row!important} +td.visible-md,th.visible-md{display:table-cell!important} +.visible-md-block{display:block!important} +.visible-md-inline{display:inline!important} +.visible-md-inline-block{display:inline-block!important} +} +@media (min-width:1200px){.visible-lg{display:block!important} +table.visible-lg{display:table!important} +tr.visible-lg{display:table-row!important} +td.visible-lg,th.visible-lg{display:table-cell!important} +.visible-lg-block{display:block!important} +.visible-lg-inline{display:inline!important} +.visible-lg-inline-block{display:inline-block!important} +} +@media (max-width:767px){.hidden-xs{display:none!important} +} +@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important} +} +@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important} +} +@media (min-width:1200px){.hidden-lg{display:none!important} +} +.visible-print{display:none!important} +@media print{.visible-print{display:block!important} +table.visible-print{display:table!important} +tr.visible-print{display:table-row!important} +td.visible-print,th.visible-print{display:table-cell!important} +} +.visible-print-block{display:none!important} +@media print{.visible-print-block{display:block!important} +} +.visible-print-inline{display:none!important} +@media print{.visible-print-inline{display:inline!important} +} +.visible-print-inline-block{display:none!important} +@media print{.visible-print-inline-block{display:inline-block!important} +.hidden-print{display:none!important} +} +.navbar-inverse .badge{background-color:#fff;color:#34bdb7} +body{-webkit-font-smoothing:antialiased} +.text-primary,.text-primary:hover{color:#34bdb7} +.text-success,.text-success:hover{color:#3fb618} +.text-danger,.text-danger:hover{color:#ff0039} +.text-warning,.text-warning:hover{color:#ff7518} +.text-info,.text-info:hover{color:#9954bb} +.table a:not(.btn),table a:not(.btn){text-decoration:underline} +.close,.table .dropdown-menu a,table .dropdown-menu a{text-decoration:none} +.table .danger,.table .danger a,.table .info,.table .info a,.table .success,.table .success a,.table .warning,.table .warning a,table .danger,table .danger a,table .info,table .info a,table .success,table .success a,table .warning,table .warning a{color:#fff} +.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .form-control-feedback,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#ff7518} +.has-warning .form-control,.has-warning .form-control:focus,.has-warning .input-group-addon{border:1px solid #ff7518} +.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .form-control-feedback,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#ff0039} +.has-error .form-control,.has-error .form-control:focus,.has-error .input-group-addon{border:1px solid #ff0039} +.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .form-control-feedback,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3fb618} +.has-success .form-control,.has-success .form-control:focus,.has-success .input-group-addon{border:1px solid #3fb618} +.nav-pills>li>a{border-radius:0} +.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-image:none} +.close{opacity:.4} +.close:focus,.close:hover{opacity:1} +.alert{border:none} +.alert .alert-link{text-decoration:underline;color:#fff} +.modal .close,.panel-default .close,.popover{color:#333} +.label{border-radius:0} +.progress{height:8px;-webkit-box-shadow:none;box-shadow:none} +.progress .progress-bar{font-size:8px;line-height:8px} +.panel-footer,.panel-heading{border-top-right-radius:0;border-top-left-radius:0} +a.list-group-item-success.active{background-color:#3fb618} +a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{background-color:#379f15} +a.list-group-item-warning.active{background-color:#ff7518} +a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{background-color:#fe6600} +a.list-group-item-danger.active{background-color:#ff0039} +a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{background-color:#e60033} \ No newline at end of file diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 0000000..1d1ae4c --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,7 @@ +{# Import the theme's layout. #} +{% extends "!layout.html" %} +{% set bootswatch_css_custom = ['_static/styles/briefy.css'] %} + +{# Add some extra stuff before and use existing with 'super()' call. #} +{% block footer %} +{% endblock %} diff --git a/docs/changes.md b/docs/changes.md new file mode 100644 index 0000000..c6d99d6 --- /dev/null +++ b/docs/changes.md @@ -0,0 +1,2 @@ +```{include} ../CHANGES.md +``` diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..24d9b5b --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,247 @@ +# Configuration file for the Sphinx documentation builder. +# collective.mastodon documentation build configuration file + + +from datetime import datetime +from packaging.version import parse +from packaging.version import Version +from pathlib import Path + +# -- Path setup -------------------------------------------------------------- +import re + + +# 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. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath(".")) + + +# -- Project information ----------------------------------------------------- +year = datetime.now().year + +project = "collective.mastodon" +copyright = f"2023 - {year}, Simples Consultoria" +author = "Érico Andrei" +trademark_name = "Simples Consultoria" +now = datetime.now() +year = str(now.year) + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. + + +def extract_version() -> Version: + """Extract version from setup.py.""" + path = Path("../setup.py").resolve() + text = path.read_text() + pattern = r"""version="([^"]*)",""" + return parse(re.search(pattern, text).groups()[0]) + + +_version = extract_version() + +# The short X.Y version. +version = f"{_version.major}.{_version.minor}" +# The full version, including alpha/beta/rc tags. +release = f"{_version}" + +# -- General configuration ---------------------------------------------------- + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# Add any Sphinx extension module names here, as strings. +# They can be extensions coming with Sphinx (named "sphinx.ext.*") +# or your custom ones. +extensions = [ + "myst_parser", + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx_copybutton", +] + + +# If true, the Docutils Smart Quotes transform, originally based on SmartyPants +# (limited to English) and currently applying to many languages, will be used +# to convert quotes and dashes to typographically correct entities. +# Note to maintainers: setting this to `True` will cause contractions and +# hyphenated words to be marked as misspelled by spellchecker. +smartquotes = False + +# The name of the Pygments (syntax highlighting) style to use. +# pygments_style = "sphinx.pygments_styles.PyramidStyle" +pygments_style = "sphinx" + +# Options for the linkcheck builder +# Ignore localhost +linkcheck_ignore = [ + r"http://localhost", + r"http://0.0.0.0", + r"http://127.0.0.1", +] +linkcheck_anchors = True +linkcheck_timeout = 10 +linkcheck_retries = 2 + +# This is our wordlist with known words, like Github or Plone ... +spelling_word_list_filename = "spelling_wordlist.txt" +spelling_ignore_pypi_package_names = True + +# The suffix of source filenames. +source_suffix = { + ".md": "markdown", + ".rst": "restructuredtext", +} + +# The master toctree document. +master_doc = "index" + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [ + "spelling_wordlist.txt", + "robots.txt", + "requirements.txt", + "Dockerfile", + "_build/*", + "_static/*", + "bin/*", + "include/*", + "lib/*", + "lib64/*", +] + +html_extra_path = [ + "robots.txt", +] + +html_static_path = [ + "_static", +] + +# -- Options for myST markdown conversion to html ----------------------------- + +# For more information see: +# https://myst-parser.readthedocs.io/en/latest/syntax/optional.html +myst_enable_extensions = [ + "deflist", # You will be able to utilise definition lists + # https://myst-parser.readthedocs.io/en/latest/syntax/optional.html#definition-lists + "linkify", # Identify “bare” web URLs and add hyperlinks. + "colon_fence", # You can also use ::: delimiters to denote code fences,\ + # instead of ```. +] + +myst_substitutions = {} + +# -- Intersphinx configuration ---------------------------------- + +# This extension can generate automatic links to the documentation of objects +# in other projects. Usage is simple: whenever Sphinx encounters a +# cross-reference that has no matching target in the current documentation set, +# it looks for targets in the documentation sets configured in +# intersphinx_mapping. A reference like :py:class:`zipfile.ZipFile` can then +# linkto the Python documentation for the ZipFile class, without you having to +# specify where it is located exactly. +# +# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html +# +# Note that collective.mastodon documentation imports documentation from +# several remote repositories. +# These projects need to build their docs as part of their CI/CD and testing. +# We use Intersphinx to resolve targets when either the individual project's or +# the entire collective.mastodon documentation is built. +intersphinx_mapping = {} + + +# -- GraphViz configuration ---------------------------------- + +graphviz_output_format = "svg" + + +# -- OpenGraph configuration ---------------------------------- + +ogp_site_url = "https://collectivemastodon.readthedocs.io/" +ogp_description_length = 200 +ogp_image = "https://collectivemastodon.readthedocs.io/_static/images/icon.png" +ogp_site_name = "collective.mastodon documentation" +ogp_type = "website" +ogp_custom_meta_tags = [ + '', +] + + +# -- sphinx_copybutton ----------------------- +copybutton_prompt_text = r"^ {0,2}\d{1,3}" +copybutton_prompt_is_regexp = True + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_book_theme" + +html_logo = "_static/images/icon.png" +html_favicon = "_static/favicon.ico" + +html_css_files = ["custom.css", ("print.css", {"media": "print"})] + +# See http://sphinx-doc.org/ext/todo.html#confval-todo_include_todos +todo_include_todos = True + +# Announce that we have an opensearch plugin +# https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_use_opensearch +html_use_opensearch = "https://collectivemastodon.readthedocs.io/" + +html_theme_options = { + "path_to_docs": "docs", + "repository_url": "https://github.com/collective/collective.mastodon", + "repository_branch": "main", + "use_repository_button": True, + "use_issues_button": True, + "use_edit_page_button": True, + "extra_navbar": "", + "extra_footer": "", +} + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = f"{project} v{release}" + +# If false, no index is generated. +html_use_index = True + +# Used by sphinx_sitemap to generate a sitemap +html_baseurl = "https://collectivemastodon.readthedocs.io/" + +# -- Options for HTML help output ------------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = "ContentRulesMastodonDoc" + + +# -- Options for LaTeX output ------------------------------------------------- + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]) +latex_documents = [ + ( + "index", + "ContentRulesMastodonDoc.tex", + "collective.mastodon documentation", + "Pendect GmbH", + "manual", + ), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +latex_logo = "_static/images/icon.png" diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..3b0dec3 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,58 @@ +# Contributing + +If you want to help with the development (improvement, update, bug-fixing, ...) of `collective.mastodon` this is a great idea! + +- [Issue Tracker](https://github.com/collective/collective.mastodon/issues) +- [Source Code](https://github.com/collective/collective.mastodon/) +- [Documentation](https://collective.github.io/collective.mastodon) + +We appreciate any contribution and if a release is needed to be done on PyPI, please just contact one of us. + +## Local Development + +You need a working `python` environment (system, `virtualenv`, `pyenv`, etc) version 3.8 or superior. + +Then install the dependencies and a development instance using: + +```bash +make build +``` +### Update translations + +```bash +make i18n +``` + +### Format codebase + +```bash +make format +``` + +### Run tests + +Testing of this package is done with [`pytest`](https://docs.pytest.org/) and [`tox`](https://tox.wiki/). + +Run all tests with: + +```bash +make test +``` + +Run all tests but stop on the first error and open a `pdb` session: + +```bash +./bin/tox -e test -- -x --pdb +``` + +Run only tests that match `TestAppDiscovery`: + +```bash +./bin/tox -e test -- -k TestAppDiscovery +``` + +Run only tests that match `TestAppDiscovery`, but stop on the first error and open a `pdb` session: + +```bash +./bin/tox -e test -- -k TestAppDiscovery -x --pdb +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..eb696b2 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,15 @@ +# Mastodon Content Rules Action + +[collective.mastodon](https://github.com/collective/collective.mastodon) is a package providing a [Plone](https://plone.org) content rules action to post a status using a Mastodon account. + + +## Contents + +```{toctree} +:maxdepth: 3 +:numbered: 0 + +intro +contributing +changes +``` diff --git a/docs/intro.md b/docs/intro.md new file mode 100644 index 0000000..fe3bad5 --- /dev/null +++ b/docs/intro.md @@ -0,0 +1,94 @@ +# About + +[collective.mastodon](https://github.com/collective/collective.mastodon) is a package providing a [Plone](https://plone.org) content rules action to post a status using a Mastodon account. + +## Code Health +
+ +[![PyPI](https://img.shields.io/pypi/v/collective.mastodon)](https://pypi.org/project/collective.mastodon/) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/collective.mastodon)](https://pypi.org/project/collective.mastodon/) +[![PyPI - Wheel](https://img.shields.io/pypi/wheel/collective.mastodon)](https://pypi.org/project/collective.mastodon/) +[![PyPI - License](https://img.shields.io/pypi/l/collective.mastodon)](https://pypi.org/project/collective.mastodon/) +[![PyPI - Status](https://img.shields.io/pypi/status/collective.mastodon)](https://pypi.org/project/collective.mastodon/) + + +[![PyPI - Plone Versions](https://img.shields.io/pypi/frameworkversions/plone/collective.mastodon)](https://pypi.org/project/collective.mastodon/) + +[![Meta](https://github.com/collective/collective.mastodon/actions/workflows/meta.yml/badge.svg)](https://github.com/collective/collective.mastodon/actions/workflows/meta.yml) +![Code Style](https://img.shields.io/badge/Code%20Style-Black-000000) + +[![GitHub contributors](https://img.shields.io/github/contributors/collective/collective.mastodon)](https://github.com/collective/collective.mastodon) +[![GitHub Repo stars](https://img.shields.io/github/stars/collective/collective.mastodon?style=social)](https://github.com/collective/collective.mastodon) + +
+ +# Installation + +This package supports Plone sites using Volto and ClassicUI. + +For proper Volto support, the requirements are: + +* plone.restapi >= 8.34.0 +* Volto >= 16.10.0 + +Add **collective.mastodon** to the Plone installation using `pip`: + +```bash +pip install collective.mastodon +``` + +or add it as a dependency on your package's `setup.py` + +```python + install_requires = [ + "collective.mastodon", + "Plone", + "plone.restapi", + "setuptools", + ], +``` + +## Configuration + +### Obtaining an Access Token +Before you can use this package, you have to register an application on Mastodon. +To do so, log in to your account, visit `settings/applications/new` and create the application. (Please select `read` and `write` scopes, and keep the default `Redirect URI`). +Go to the newly created application page and copy the value of `Your access token`. + +### Configuring Plone + +This package is configured via the `MASTODON_APPS` environment variable which should contain a valid JSON array with your Mastodon Application information. + +Each application registration requires the following information: + +| Key | Description | Example Value | +| -- | -- | -- | +| name | Identifier for the application | localhost-user | +| instance | URL of your instance, without the trailing slash | http://localhost | +| token | Access token of your Mastodon Application | jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M | +| user | User on the Mastodon instance. (Only used to generate a friendly name on Plone) | user | + +Using the information above, the environment variable would look like: + +```shell +MASTODON_APPS='[{"name": "localhost-user","instance":"http://localhost","token":"jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M","user":"user"}]' +``` + +### Starting Plone + +Now, you can start your local Plone installation with: + +```shell +MASTODON_APPS='[{"name": "localhost-user","instance":"http://localhost","token":"jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M","user":"user"}]' make start +``` + +or, if you are using a `docker compose` configuration, add the new environment variable under the `environment` key: + +```yaml + environment: + - MASTODON_APPS='[{"name": "localhost-user","instance":"http://localhost","token":"jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M","user":"user"}]' +``` + +After start-up visit the `Content Rules` Control Panel, and create a new content rule. + +No additional configuration is needed for Volto support. diff --git a/docs/robots.txt b/docs/robots.txt new file mode 100644 index 0000000..e3edd07 --- /dev/null +++ b/docs/robots.txt @@ -0,0 +1,3 @@ +# Disallow all user agents from the following directories and files +User-agent: * +Disallow: diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt new file mode 100644 index 0000000..98c0397 --- /dev/null +++ b/docs/spelling_wordlist.txt @@ -0,0 +1,449 @@ +acl +add-on +add-ons +addon +addons +admin +admins +andrei +AnnotationStorage +Ansible +api +ary +Aspeli +ATContentTypes +autoform +backend +Backend +bakula +barceloneta +Barceloneta +Barebone +blobstorage +Bluebream +boolean +Bootstrap +browsable +browserlayer +Browserlayer +browserlayers +BrowserViews +BTree +btw +Bugzilla +buildout +buildouts +Buildouts +built-in +bzr +cano +Casali +cedricmessiant +cfg +changelog +Changelog +changelogs +checkboxes +cmf +CMFCore +CMFPlone +codebase +codeintel +Conectivo +conf +config +contentprovider +contenttype +contenttypes +Covantec +coveragerc +crockfort +cron +cronjobs +css +datamanager +Datamanager +de +debuggable +deliverance +demostorage +Demostorage +demoview +dev +Diazo +dict +diff +diffs +Dijk +DLR +dmesg +docsets +docstring +docstrings +doctests +Doctests +dropdown +Dropdown +dropdowns +Drupal +dtml +ecreall +eea +Einsteigerkurs +emacs +env +erico +eventhandlers +exe +extendable +extensibility +Facebook +facettednavigation +fallback +faq +fg +fieldset +fieldsets +firefox +Flickr +Flowplayer +folderish +FormActionAdapter +formlib +framebuffer +freenode +frontend +frontpage +Fulvio +georeference +Gerken +getLayout +getLink +gforcada +Gheem +gif +gil +gists +github +GitHub +gitlab +Glick +Gmail +gnupg +gomez +google +gpg +GPL +grep +Groundwire +gruntfile +Gunicorn +HAProxy +Herstory +hg +homebrew +hostname +hotfix +href +html +http +IBasic +iCal +IDE +iframe +img +ini +init +inline +intranet +Intranet +io +IPython +irc +ISocial +issuecomment +iterable +IVotable +IVoting +javascript +Javascript +JavaScript +javascripts +jbot +jenkins +Joomla +jpg +jquery +jQuery +js +jshint +json +jurymember +kitconcept +Kupu +lanyrd +Lanyrd +lasttagdiff +libxml +libxslt +Limi +Lindner +linebreaks +localhost +logfiles +longtest +lxml +macagua +macOS +Mailserver +Makefile +Maurits +memberID +metadata +Metadata +Mikko +mimetype +minimalpattern +mockup +Mockup +mr +mrbob +mtime +multiselect +MVC +mysite +MySQL +Nagios +namespace +namespaces +nametag +nametags +Nejc +nginx +Nginx +nocall +offline +Ohtamaa +omelette +online +Online +optilude +org +outlier +pageview +Pastanaga +paster +pastie +pattern +Patternslib +pbauer +pdb +PDF +Pellegrini +pep8 +PEP8 +Phantomjs +Photoshop +php +phpBB +pipbox +placeful +Placeful +playbook +plip +Plip +plips +Plips +Plomino +plone +Plone +ploneconf +plonectl +ploneCustom +PloneFormGen +PlonePAS +plonetruegallery +ploneview +Plonista +pluggability +plugin +plugins +png +po +portlet +Portlet +portlets +Portlets +POSIX +PostgreSQL +postrelease +pre +precompiler +prepending +prerelease +px +py +Pygments +pypi +pypirc +quickinstaller +Rapido +ReactJS +redmine +Redux +Rees +refactor +Reinout +release +Relstorage +renderers +repo +requirejs +reST +reStructuredText +reusable +richtext +roadmap +Roadmap +robotframework +Rockstar +rst +rsync +rtd +ruleset +runtime +Runyan +Salesforce +scp +screen +screenshot +screenshots +searchable +seo +SEO +setuphandler +setuphandlers +smcmahon +Solaris +Solr +spam +Spammish +Spettoli +sql +src +sshd +ssl +starzel +Starzel +styleguide +subclassing +subdirectory +Sunbird +svn +symlink +symlinks +systemd +Tahr +tal +Tal +talklist +talklistview +talkview +tarball +templating +Templating +templer +testrunner +testsuite +testsuites +textbox +thomasdesvenain +timo +tinymce +TinyMCE +tmp +toc +ToC +toctree +Todo +Todos +toLocalizedTime +tomgross +toolbar +Trac +trainings +transifex +Transmogrifier +travis +trello +ttw +txt +UI +uid +UML +underscorejs +uninstalling +url +urls +usenames +username +Username +utf +uuid +uWSGI +validator +vCal +viewlet +Viewlet +viewlets +Viewlets +vincentfretin +Virtualbox +VirtualBox +virtualenv +virtualhost +Volto +votable +voteable +Walkthrough +Webmail +webpack +webserver +Werkzeug +wf +wget +whitespaces +Wordpress +workflow +Workflow +workflows +Workflows +wsgi +WSGI +www +xdv +XDV +Xephyr +xml +XPath +xss +XSS +xvfb +xxx +yml +youtube +zc +ZCatalog +zcml +zeo +ZEO +zeoclients +zeoserver +zexp +ZODB +zope +zopectl +zopepy +zopeskel +zpt2 +Zupan diff --git a/instance.yaml b/instance.yaml new file mode 100644 index 0000000..ba73988 --- /dev/null +++ b/instance.yaml @@ -0,0 +1,8 @@ +default_context: + initial_user_name: 'admin' + initial_user_password: 'admin' + + load_zcml: + package_includes: ['collective.mastodon'] + + db_storage: direct diff --git a/mx.ini b/mx.ini new file mode 100644 index 0000000..6e7929c --- /dev/null +++ b/mx.ini @@ -0,0 +1,15 @@ +; This is a mxdev configuration file +; it can be used to override versions of packages already defined in the +; constraints files and to add new packages from VCS like git. +; to learn more about mxdev visit https://pypi.org/project/mxdev/ + +[settings] +version-overrides = + plone.restapi>=8.40.0 + +; example section to use packages from git +; [example.contenttype] +; url = https://github.com/collective/example.contenttype.git +; pushurl = git@github.com:collective/example.contenttype.git +; extras = test +; branch = feature-7 diff --git a/news/.changelog_template.jinja b/news/.changelog_template.jinja new file mode 100644 index 0000000..b35bff3 --- /dev/null +++ b/news/.changelog_template.jinja @@ -0,0 +1,15 @@ +{% if sections[""] %} +{% for category, val in definitions.items() if category in sections[""] %} + +### {{ definitions[category]['name'] }} + +{% for text, values in sections[""][category].items() %} +- {{ text }} {{ values|join(', ') }} +{% endfor %} + +{% endfor %} +{% else %} +No significant changes. + + +{% endif %} \ No newline at end of file diff --git a/news/.gitkeep b/news/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/news/0.feature b/news/0.feature new file mode 100644 index 0000000..b86542a --- /dev/null +++ b/news/0.feature @@ -0,0 +1 @@ +Initial implementation of collective.mastodon [@ericof] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6768823 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,166 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +[tool.towncrier] +directory = "news/" +filename = "CHANGES.md" +start_string = "\n" +title_format = "## {version} ({project_date})" +template = "news/.changelog_template.jinja" +underlines = ["", "", ""] +issue_format = "[#{issue}](https://github.com/collective/collective.mastodon/issues/{issue})" + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking changes:" +showcontent = true + +[[tool.towncrier.type]] +directory = "feature" +name = "New features:" +showcontent = true + +[[tool.towncrier.type]] +directory = "bugfix" +name = "Bug fixes:" +showcontent = true + +[[tool.towncrier.type]] +directory = "internal" +name = "Internal:" +showcontent = true + +[[tool.towncrier.type]] +directory = "documentation" +name = "Documentation:" +showcontent = true + +[[tool.towncrier.type]] +directory = "tests" +name = "Tests" +showcontent = true + +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# towncrier_extra_lines = """ +# extra_configuration +# """ +## + +[tool.isort] +profile = "plone" + +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# isort_extra_lines = """ +# extra_configuration +# """ +## + +[tool.black] +target-version = ["py38"] + +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# black_extra_lines = """ +# extra_configuration +# """ +## + +[tool.codespell] +ignore-words-list = "discreet,vew" +skip = "*.po,*.min.js,*.pot,*.po,*.yaml,*.json" +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# codespell_ignores = "foo,bar" +# codespell_skip = "*.po,*.map,package-lock.json" +## + +[tool.dependencychecker] +Zope = [ + # Zope own provided namespaces + 'App', 'OFS', 'Products.Five', 'Products.OFSP', 'Products.PageTemplates', + 'Products.SiteAccess', 'Shared', 'Testing', 'ZPublisher', 'ZTUtils', + 'Zope2', 'webdav', 'zmi', + # ExtensionClass own provided namespaces + 'ExtensionClass', 'ComputedAttribute', 'MethodObject', + # Zope dependencies + 'AccessControl', 'Acquisition', 'AuthEncoding', 'beautifulsoup4', 'BTrees', + 'cffi', 'Chameleon', 'DateTime', 'DocumentTemplate', + 'MultiMapping', 'multipart', 'PasteDeploy', 'Persistence', 'persistent', + 'pycparser', 'python-gettext', 'pytz', 'RestrictedPython', 'roman', + 'soupsieve', 'transaction', 'waitress', 'WebOb', 'WebTest', 'WSGIProxy2', + 'z3c.pt', 'zc.lockfile', 'ZConfig', 'zExceptions', 'ZODB', 'zodbpickle', + 'zope.annotation', 'zope.browser', 'zope.browsermenu', 'zope.browserpage', + 'zope.browserresource', 'zope.cachedescriptors', 'zope.component', + 'zope.configuration', 'zope.container', 'zope.contentprovider', + 'zope.contenttype', 'zope.datetime', 'zope.deferredimport', + 'zope.deprecation', 'zope.dottedname', 'zope.event', 'zope.exceptions', + 'zope.filerepresentation', 'zope.globalrequest', 'zope.hookable', + 'zope.i18n', 'zope.i18nmessageid', 'zope.interface', 'zope.lifecycleevent', + 'zope.location', 'zope.pagetemplate', 'zope.processlifetime', 'zope.proxy', + 'zope.ptresource', 'zope.publisher', 'zope.schema', 'zope.security', + 'zope.sequencesort', 'zope.site', 'zope.size', 'zope.structuredtext', + 'zope.tal', 'zope.tales', 'zope.testbrowser', 'zope.testing', + 'zope.traversing', 'zope.viewlet' +] +'Products.CMFCore' = [ + 'docutils', 'five.localsitemanager', 'Missing', 'Products.BTreeFolder2', + 'Products.GenericSetup', 'Products.MailHost', 'Products.PythonScripts', + 'Products.StandardCacheManagers', 'Products.ZCatalog', 'Record', + 'zope.sendmail', 'Zope' +] +'plone.base' = [ + 'plone.batching', 'plone.registry', 'plone.schema','plone.z3cform', + 'Products.CMFCore', 'Products.CMFDynamicViewFTI', +] +python-dateutil = ['dateutil'] +ignore-packages = ['plone.volto', 'zestreleaser.towncrier', 'zest.releaser', 'pytest', 'pytest-cov', 'pytest-plone'] +Plone = ['Products.CMFPlone', 'Products.CMFCore', 'Products.GenericSetup'] + +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# dependencies_ignores = "['zestreleaser.towncrier']" +# dependencies_mappings = [ +# "gitpython = ['git']", +# "pygithub = ['github']", +# ] +## + +[tool.check-manifest] +ignore = [ + ".editorconfig", + ".meta.toml", + ".pre-commit-config.yaml", + "tox.ini", + ".flake8", + "mx.ini", + "news/*", + "constraints-mxdev.txt", + "requirements-mxdev.txt", + +] +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# check_manifest_ignores = """ +# "*.map.js", +# "*.pyc", +# """ +## + +[tool.coverage.run] +omit = ["*/locales/*"] + +## +# Add extra configuration options in .meta.toml: +# [pyproject] +# extra_lines = """ +# _your own configuration lines_ +# """ +## diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..1e26f94 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,10 @@ +Sphinx +jsx-lexer # volto +lesscpy +linkify-it-py +myst-parser +sphinx-autobuild +sphinx-book-theme +sphinx-copybutton +sphinx-togglebutton +sphinxcontrib-spelling diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9dd6dd4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +-c constraints.txt +-e ".[test]" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e8298d7 --- /dev/null +++ b/setup.py @@ -0,0 +1,81 @@ +"""Installer for the collective.mastodon package.""" +from pathlib import Path +from setuptools import find_packages +from setuptools import setup + + +long_description = f""" +{Path("README.md").read_text()}\n +{Path("CONTRIBUTORS.md").read_text()}\n +{Path("CHANGES.md").read_text()}\n +""" + + +setup( + name="collective.mastodon", + version="1.0.0.dev0", + description="Mastodon integration for Plone.", + long_description=long_description, + long_description_content_type="text/markdown", + classifiers=[ + "Development Status :: 3 - Alpha", + "Environment :: Web Environment", + "Framework :: Plone", + "Framework :: Plone :: Addon", + "Framework :: Plone :: 6.0", + "Programming Language :: Python", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Operating System :: OS Independent", + "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", + ], + keywords="Python Plone Mastodon ContentRules SocialNetwork", + author="Érico Andrei", + author_email="ericof@plone.org", + url="https://pypi.python.org/pypi/collective.mastodon", + license="GPL version 2", + project_urls={ + "PyPI": "https://pypi.python.org/pypi/collective.mastodon", + "Source": "https://github.com/collective/collective.mastodon", + "Tracker": "https://github.com/collective/collective.mastodon/issues", + "Documentation": "https://collective.github.io/collective.mastodon", + }, + packages=find_packages("src", exclude=["ez_setup"]), + namespace_packages=["collective"], + package_dir={"": "src"}, + include_package_data=True, + zip_safe=False, + python_requires=">=3.8.0", + install_requires=[ + "Mastodon.py", + "prettyconf", + "Plone", + "setuptools", + "plone.restapi>=8.34.0", + ], + extras_require={ + "test": [ + "gocept.pytestlayer", + "plone.app.testing", + "plone.restapi[test]", + "pytest-cov", + "pytest-plone>=0.2.0", + "pytest-docker", + "pytest-mock", + "pytest", + "zest.releaser[recommended]", + "vcrpy", + "pytest-vcr", + "pytest-mock", + "requests-mock", + ], + }, + entry_points=""" + [z3c.autoinclude.plugin] + target = plone + [console_scripts] + update_locale = collective.mastodon.locales.update:update_locale + """, +) diff --git a/src/collective/__init__.py b/src/collective/__init__.py new file mode 100644 index 0000000..5284146 --- /dev/null +++ b/src/collective/__init__.py @@ -0,0 +1 @@ +__import__("pkg_resources").declare_namespace(__name__) diff --git a/src/collective/mastodon/__init__.py b/src/collective/mastodon/__init__.py new file mode 100644 index 0000000..e11ae4c --- /dev/null +++ b/src/collective/mastodon/__init__.py @@ -0,0 +1,14 @@ +"""Init and utils.""" +from collective.mastodon import startup +from zope.i18nmessageid import MessageFactory + +import logging + + +PACKAGE_NAME = "collective.mastodon" + +_ = MessageFactory(PACKAGE_NAME) + +logger = logging.getLogger(PACKAGE_NAME) + +startup.register_apps(logger) diff --git a/src/collective/mastodon/actions/__init__.py b/src/collective/mastodon/actions/__init__.py new file mode 100644 index 0000000..8c2c97d --- /dev/null +++ b/src/collective/mastodon/actions/__init__.py @@ -0,0 +1 @@ +"""Content rules actions.""" diff --git a/src/collective/mastodon/actions/configure.zcml b/src/collective/mastodon/actions/configure.zcml new file mode 100644 index 0000000..c342ac4 --- /dev/null +++ b/src/collective/mastodon/actions/configure.zcml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + diff --git a/src/collective/mastodon/actions/mastodon.pt b/src/collective/mastodon/actions/mastodon.pt new file mode 100644 index 0000000..b860daa --- /dev/null +++ b/src/collective/mastodon/actions/mastodon.pt @@ -0,0 +1,34 @@ + + + +

+ Contents in the status field may be replaced with "${}" variables from the table on the bottom of this form. +

+
+
+
+

Substitutions

+ + + + + + + + + + + + + + + + + +
CategoryVariableSubstitution
All Content${url}URL
+
+
diff --git a/src/collective/mastodon/actions/mastodon.py b/src/collective/mastodon/actions/mastodon.py new file mode 100644 index 0000000..b34d4be --- /dev/null +++ b/src/collective/mastodon/actions/mastodon.py @@ -0,0 +1,206 @@ +from collective.mastodon import _ +from collective.mastodon import utils +from collective.mastodon.app import MastodonApp +from OFS.SimpleItem import SimpleItem +from plone import api +from plone.app.contentrules.actions import ActionAddForm +from plone.app.contentrules.actions import ActionEditForm +from plone.app.contentrules.browser.formhelper import ContentRuleFormWrapper +from plone.contentrules.rule.interfaces import IExecutable +from plone.contentrules.rule.interfaces import IRuleElementData +from plone.dexterity.content import DexterityContent +from plone.stringinterp.interfaces import IStringInterpolator +from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile +from threading import Thread +from typing import Any +from zope import schema +from zope.component import adapter +from zope.i18nmessageid import Message +from zope.interface import implementer +from zope.interface import Interface + + +DEFAULT_STATUS = """${title}, ${description} +${tags} +${url} +""" + + +def safe_attr(element: "MastodonAction", attr: str) -> Any: + """Return attribute value.""" + value = getattr(element, attr) + return value if value is not None else "" + + +class IMastodonAction(Interface): + """Definition of the configuration available for a mastodon action.""" + + app = schema.Choice( + title=_("Mastodon App"), + description=_("App to be used"), + vocabulary="collective.mastodon.apps", + required=True, + ) + visibility = schema.Choice( + title=_("Visibility"), + description=_("Visibility of the post"), + vocabulary="collective.mastodon.visibility", + default="public", + required=True, + ) + spoiler_text = schema.TextLine( + title=_("Spoiler Text"), + description=_("Text used for warning."), + default="", + required=False, + ) + language = schema.TextLine( + title=_("Language"), + description=_("If not set, the content language will be used."), + default="", + required=False, + ) + sensitive = schema.Bool( + title=_("Sensitive Content"), + description=_("Is this a sensitive content?"), + default=False, + required=False, + ) + scheduling = schema.Bool( + title=_("Schedule post?"), + description=_("If content effective date is in the future, schedule the post"), + default=True, + required=False, + ) + status = schema.Text( + title=_("Status"), + description=_("Main text of the post."), + default=DEFAULT_STATUS, + required=True, + ) + + +@implementer(IMastodonAction, IRuleElementData) +class MastodonAction(SimpleItem): + """The implementation of the action defined before.""" + + app: str = "" + visibility: str = "" + spoiler_text: str = "" + language: str = "" + sensitive: bool = False + scheduling: bool = True + status: str = "" + + element: str = "plone.actions.Mastodon" + + @property + def _app(self) -> MastodonApp: + return utils.get_app(self.app) + + @property + def summary(self) -> Message: + app = self._app + username = utils.mastodon_username(app.instance, app.user) + return _( + "Post a new toot as ${user}", + mapping=dict(user=username), + ) + + +@implementer(IExecutable) +@adapter(Interface, IMastodonAction, Interface) +class MastodonActionExecutor: + """Executor for the Mastodon Action.""" + + content: DexterityContent + + def __init__(self, context, element: "MastodonAction", event): + """Initialize action executor.""" + self.context = context + self.element = element + self.event = event + self.content = event.object + self.app = utils.get_app(element.app) + + def _prepare_payload(self) -> dict: + """Process the action and return a dictionary with the Mastodon message payload. + + :returns: Mastodon message payload. + """ + content = self.content + element = self.element + interpolator = IStringInterpolator(content) + status = interpolator(safe_attr(element, "status")).strip() + spoiler_text = interpolator(safe_attr(element, "spoiler_text")).strip() + sensitive = safe_attr(element, "sensitive") + visibility = safe_attr(element, "visibility") + idempotency_key = api.content.get_uuid(content) + language = safe_attr(element, "language") + if not language: + language = content.language[:2] if content.language else None + media_list = [] + media = utils.media_from_content(content) + if media: + media_list = [media] + payload = { + "status": status, + "media_list": media_list, + "sensitive": sensitive, + "visibility": visibility, + "spoiler_text": spoiler_text, + "language": language, + "idempotency_key": idempotency_key, + } + scheduling = safe_attr(element, "scheduling") + if scheduling: + payload["scheduled_at"] = utils.schedule_date(content) + return payload + + def _post(self, payload: dict) -> Thread: + """Post a status to Mastodon.""" + app = self.app + return app.status_post(**payload) + + def __call__(self) -> bool: + """Execute the action.""" + payload = self._prepare_payload() + self._post(payload) + return True + + +class MastodonAddForm(ActionAddForm): + """An add form for the Mastodon Action.""" + + schema = IMastodonAction + label = _("Add Mastodon Action") + description = _("Action to post a toot to a Mastodon account.") + form_name = _("Configure element") + Type = MastodonAction + + # custom template will allow us to add help text + template = ViewPageTemplateFile("mastodon.pt") + + +class MastodonAddFormView(ContentRuleFormWrapper): + """Wrapped add form for Mastodon Action.""" + + form = MastodonAddForm + + +class MastodonEditForm(ActionEditForm): + """An edit form for the mastodon action.""" + + schema = IMastodonAction + label = _("Edit Mastodon Action") + description = _("Action to post a toot to a Mastodon account.") + form_name = _("Configure element") + + # custom template will allow us to add help text + template = ViewPageTemplateFile("mastodon.pt") + + +class MastodonEditFormView(ContentRuleFormWrapper): + """Wrapped edit form for Mastodon Action.""" + + form = MastodonEditForm diff --git a/src/collective/mastodon/app.py b/src/collective/mastodon/app.py new file mode 100644 index 0000000..67f2784 --- /dev/null +++ b/src/collective/mastodon/app.py @@ -0,0 +1,94 @@ +from collective.mastodon import logger +from collective.mastodon.interfaces import IMastodonApp +from collective.mastodon.interfaces import MastodonMedia +from datetime import datetime +from mastodon import Mastodon +from threading import Thread +from typing import List +from zope.interface import implementer + + +USER_AGENT = "collective.mastodon" + + +@implementer(IMastodonApp) +class MastodonApp: + """Mastodon App""" + + _app: Mastodon + name: str + instance: str + user: str + + def __init__(self, name: str, instance: str, token: str, user: str): + self.name = name + self.instance = instance + self.user = user + self._app = Mastodon( + access_token=token, api_base_url=instance, user_agent=USER_AGENT + ) + self.thread_name = f"MastodonApp-Thread-{instance}-{name}" + + def status_post( + self, + status: str, + media_list: List[MastodonMedia] = None, + sensitive: bool = False, + visibility: str = "public", + spoiler_text: str = None, + language: str = None, + idempotency_key: str = None, + scheduled_at: datetime = None, + ) -> Thread: + """Post a status to a Mastodon instance (using thread).""" + payload = { + "status": status, + "media_list": media_list if media_list else [], + "sensitive": sensitive, + "visibility": visibility, + "spoiler_text": spoiler_text, + "language": language, + "idempotency_key": idempotency_key, + "scheduled_at": scheduled_at, + } + name = self.thread_name + name = f"{name}-{idempotency_key}" if idempotency_key else name + thread = Thread( + target=self._status_post, + name=name, + kwargs=payload, + ) + + thread.start() + return thread + + def _status_post(self, **payload) -> dict: + """Post a status to a Mastodon instance and return the response.""" + app = self._app + media_ids = [] + media_list = payload.get("media_list", []) + if media_list: + for media in media_list: + response = app.media_post( + media.file, + mime_type=media.mime_type, + description=media.description, + focus=None, + ) + media_ids.append(response["id"]) + # Prepare payload for post + del payload["media_list"] + payload["media_ids"] = media_ids + post = app.status_post(**payload) + scheduled_at = post.get("scheduled_at", None) + if scheduled_at: + logger.info(f"Scheduled status for {scheduled_at}") + else: + visibility = post["visibility"] + url = post["url"] + logger.info(f"Posted {visibility} status {url}") + return post + + def scheduled_statuses(self): + """Return a list of scheduled statuses.""" + return self._app.scheduled_statuses() diff --git a/src/collective/mastodon/configure.zcml b/src/collective/mastodon/configure.zcml new file mode 100644 index 0000000..d9d20d5 --- /dev/null +++ b/src/collective/mastodon/configure.zcml @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/src/collective/mastodon/interfaces.py b/src/collective/mastodon/interfaces.py new file mode 100644 index 0000000..d47d5c4 --- /dev/null +++ b/src/collective/mastodon/interfaces.py @@ -0,0 +1,53 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import BinaryIO +from typing import List +from zope.interface import Interface + + +@dataclass +class MastodonAppInfo: + """Mastodon App Information.""" + + name: str + instance: str + user: str + token: str + + +@dataclass +class MastodonMedia: + """Mastodon media object.""" + + file: BinaryIO + mime_type: str + description: str + + +class IMastodonRegistry(Interface): + """A singleton utility listing a.""" + + def get_app(name): + """Returns a MastodonApp.""" + + def get_apps(): + """Returns a list of registered apps.""" + + +class IMastodonApp(Interface): + """A named utility for mastodon.""" + + def status_post( + status: str, + media_list: List[MastodonMedia], + sensitive: bool, + visibility: str, + spoiler_text: str, + language: str, + idempotency_key: str, + scheduled_at: datetime, + ): + """Post a status.""" + + def scheduled_statuses(): + """Return a list of scheduled statuses.""" diff --git a/src/collective/mastodon/interpolators/__init__.py b/src/collective/mastodon/interpolators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/collective/mastodon/interpolators/adapters.py b/src/collective/mastodon/interpolators/adapters.py new file mode 100644 index 0000000..bffb9a2 --- /dev/null +++ b/src/collective/mastodon/interpolators/adapters.py @@ -0,0 +1,15 @@ +from collective.mastodon import _ +from plone.base import PloneMessageFactory as _PM +from plone.stringinterp.adapters import BaseSubstitution +from Products.CMFCore.interfaces import IDublinCore +from zope.component import adapter + + +@adapter(IDublinCore) +class TagsSubstitution(BaseSubstitution): + category = _PM("Dublin Core") + description = _("Tags") + + def safe_call(self): + tags = [f"#{item}" for item in self.context.Subject()] + return " ".join(tags) diff --git a/src/collective/mastodon/interpolators/configure.zcml b/src/collective/mastodon/interpolators/configure.zcml new file mode 100644 index 0000000..8ed9c6d --- /dev/null +++ b/src/collective/mastodon/interpolators/configure.zcml @@ -0,0 +1,11 @@ + + + diff --git a/src/collective/mastodon/locales/collective.mastodon.pot b/src/collective/mastodon/locales/collective.mastodon.pot new file mode 100644 index 0000000..fd597f6 --- /dev/null +++ b/src/collective/mastodon/locales/collective.mastodon.pot @@ -0,0 +1,147 @@ +#--- PLEASE EDIT THE LINES BELOW CORRECTLY --- +#SOME DESCRIPTIVE TITLE. +#FIRST AUTHOR , YEAR. +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2023-08-25 19:18+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \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" +"Language-Code: en\n" +"Language-Name: English\n" +"Preferred-Encodings: utf-8 latin1\n" +"Domain: collective.mastodon\n" + +#: collective/mastodon/actions/mastodon.py:177 +msgid "Action to post a toot to a Mastodon account." +msgstr "" + +#: collective/mastodon/actions/mastodon.py:176 +msgid "Add Mastodon Action" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:40 +msgid "App to be used" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:178 +msgid "Configure element" +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:12 +msgid "Direct" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:195 +msgid "Edit Mastodon Action" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:71 +msgid "If content effective date is in the future, schedule the post" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:59 +msgid "If not set, the content language will be used." +msgstr "" + +#: collective/mastodon/actions/mastodon.py:65 +msgid "Is this a sensitive content?" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:58 +msgid "Language" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:77 +msgid "Main text of the post." +msgstr "" + +#: collective/mastodon/actions/mastodon.py:39 +msgid "Mastodon App" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:105 +msgid "Post a new toot as ${user}" +msgstr "" + +#: collective/mastodon/actions/configure.zcml:36 +msgid "Post a status to Mastodon" +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:11 +msgid "Private" +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:9 +msgid "Public" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:70 +msgid "Schedule post?" +msgstr "" + +#: collective/mastodon/actions/configure.zcml:36 +msgid "Send a status (toot) to Mastodon" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:64 +msgid "Sensitive Content" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:52 +msgid "Spoiler Text" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:76 +msgid "Status" +msgstr "" + +#: collective/mastodon/interpolators/adapters.py:11 +msgid "Tags" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:53 +msgid "Text used for warning." +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:10 +msgid "Unlisted" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:45 +msgid "Visibility" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:46 +msgid "Visibility of the post" +msgstr "" + +#. Default: "Category" +#: collective/mastodon/actions/mastodon.pt:14 +msgid "category-contentrules-mastodonsub" +msgstr "" + +#. Default: "Contents in the status field may be replaced with \"${}\" variables from the table on the bottom of this form." +#: collective/mastodon/actions/mastodon.pt:4 +msgid "description-contentrules-mastodonsub" +msgstr "" + +#. Default: "Substitution" +#: collective/mastodon/actions/mastodon.pt:16 +msgid "substitution-contentrules-mastodonsub" +msgstr "" + +#. Default: "Substitutions" +#: collective/mastodon/actions/mastodon.pt:10 +msgid "title_contentrules_mastodonsub" +msgstr "" + +#. Default: "Variable" +#: collective/mastodon/actions/mastodon.pt:15 +msgid "variable-contentrules-mastodonsub" +msgstr "" diff --git a/src/collective/mastodon/locales/de/LC_MESSAGES/collective.mastodon.po b/src/collective/mastodon/locales/de/LC_MESSAGES/collective.mastodon.po new file mode 100644 index 0000000..226fa8e --- /dev/null +++ b/src/collective/mastodon/locales/de/LC_MESSAGES/collective.mastodon.po @@ -0,0 +1,144 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2023-08-25 19:18+0000\n" +"PO-Revision-Date: 2017-10-11 21:18+0000\n" +"Last-Translator: Érico Andrei \n" +"Language-Team: Érico Andrei \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" +"Language-Code: de\n" +"Language-Name: Deutsch\n" +"Preferred-Encodings: utf-8 latin1\n" +"Domain: DOMAIN\n" + +#: collective/mastodon/actions/mastodon.py:177 +msgid "Action to post a toot to a Mastodon account." +msgstr "" + +#: collective/mastodon/actions/mastodon.py:176 +msgid "Add Mastodon Action" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:40 +msgid "App to be used" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:178 +msgid "Configure element" +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:12 +msgid "Direct" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:195 +msgid "Edit Mastodon Action" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:71 +msgid "If content effective date is in the future, schedule the post" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:59 +msgid "If not set, the content language will be used." +msgstr "" + +#: collective/mastodon/actions/mastodon.py:65 +msgid "Is this a sensitive content?" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:58 +msgid "Language" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:77 +msgid "Main text of the post." +msgstr "" + +#: collective/mastodon/actions/mastodon.py:39 +msgid "Mastodon App" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:105 +msgid "Post a new toot as ${user}" +msgstr "" + +#: collective/mastodon/actions/configure.zcml:36 +msgid "Post a status to Mastodon" +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:11 +msgid "Private" +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:9 +msgid "Public" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:70 +msgid "Schedule post?" +msgstr "" + +#: collective/mastodon/actions/configure.zcml:36 +msgid "Send a status (toot) to Mastodon" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:64 +msgid "Sensitive Content" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:52 +msgid "Spoiler Text" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:76 +msgid "Status" +msgstr "" + +#: collective/mastodon/interpolators/adapters.py:11 +msgid "Tags" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:53 +msgid "Text used for warning." +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:10 +msgid "Unlisted" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:45 +msgid "Visibility" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:46 +msgid "Visibility of the post" +msgstr "" + +#. Default: "Category" +#: collective/mastodon/actions/mastodon.pt:14 +msgid "category-contentrules-mastodonsub" +msgstr "" + +#. Default: "Contents in the status field may be replaced with \"${}\" variables from the table on the bottom of this form." +#: collective/mastodon/actions/mastodon.pt:4 +msgid "description-contentrules-mastodonsub" +msgstr "" + +#. Default: "Substitution" +#: collective/mastodon/actions/mastodon.pt:16 +msgid "substitution-contentrules-mastodonsub" +msgstr "" + +#. Default: "Substitutions" +#: collective/mastodon/actions/mastodon.pt:10 +msgid "title_contentrules_mastodonsub" +msgstr "" + +#. Default: "Variable" +#: collective/mastodon/actions/mastodon.pt:15 +msgid "variable-contentrules-mastodonsub" +msgstr "" diff --git a/src/collective/mastodon/locales/en/LC_MESSAGES/collective.mastodon.po b/src/collective/mastodon/locales/en/LC_MESSAGES/collective.mastodon.po new file mode 100644 index 0000000..9643994 --- /dev/null +++ b/src/collective/mastodon/locales/en/LC_MESSAGES/collective.mastodon.po @@ -0,0 +1,144 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2023-08-25 19:18+0000\n" +"PO-Revision-Date: 2017-10-11 21:18+0000\n" +"Last-Translator: Érico Andrei \n" +"Language-Team: Érico Andrei \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" +"Language-Code: en\n" +"Language-Name: English\n" +"Preferred-Encodings: utf-8 latin1\n" +"Domain: DOMAIN\n" + +#: collective/mastodon/actions/mastodon.py:177 +msgid "Action to post a toot to a Mastodon account." +msgstr "" + +#: collective/mastodon/actions/mastodon.py:176 +msgid "Add Mastodon Action" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:40 +msgid "App to be used" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:178 +msgid "Configure element" +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:12 +msgid "Direct" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:195 +msgid "Edit Mastodon Action" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:71 +msgid "If content effective date is in the future, schedule the post" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:59 +msgid "If not set, the content language will be used." +msgstr "" + +#: collective/mastodon/actions/mastodon.py:65 +msgid "Is this a sensitive content?" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:58 +msgid "Language" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:77 +msgid "Main text of the post." +msgstr "" + +#: collective/mastodon/actions/mastodon.py:39 +msgid "Mastodon App" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:105 +msgid "Post a new toot as ${user}" +msgstr "" + +#: collective/mastodon/actions/configure.zcml:36 +msgid "Post a status to Mastodon" +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:11 +msgid "Private" +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:9 +msgid "Public" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:70 +msgid "Schedule post?" +msgstr "" + +#: collective/mastodon/actions/configure.zcml:36 +msgid "Send a status (toot) to Mastodon" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:64 +msgid "Sensitive Content" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:52 +msgid "Spoiler Text" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:76 +msgid "Status" +msgstr "" + +#: collective/mastodon/interpolators/adapters.py:11 +msgid "Tags" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:53 +msgid "Text used for warning." +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:10 +msgid "Unlisted" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:45 +msgid "Visibility" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:46 +msgid "Visibility of the post" +msgstr "" + +#. Default: "Category" +#: collective/mastodon/actions/mastodon.pt:14 +msgid "category-contentrules-mastodonsub" +msgstr "" + +#. Default: "Contents in the status field may be replaced with \"${}\" variables from the table on the bottom of this form." +#: collective/mastodon/actions/mastodon.pt:4 +msgid "description-contentrules-mastodonsub" +msgstr "" + +#. Default: "Substitution" +#: collective/mastodon/actions/mastodon.pt:16 +msgid "substitution-contentrules-mastodonsub" +msgstr "" + +#. Default: "Substitutions" +#: collective/mastodon/actions/mastodon.pt:10 +msgid "title_contentrules_mastodonsub" +msgstr "" + +#. Default: "Variable" +#: collective/mastodon/actions/mastodon.pt:15 +msgid "variable-contentrules-mastodonsub" +msgstr "" diff --git a/src/collective/mastodon/locales/es/LC_MESSAGES/collective.mastodon.po b/src/collective/mastodon/locales/es/LC_MESSAGES/collective.mastodon.po new file mode 100644 index 0000000..34a868c --- /dev/null +++ b/src/collective/mastodon/locales/es/LC_MESSAGES/collective.mastodon.po @@ -0,0 +1,144 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2023-08-25 19:18+0000\n" +"PO-Revision-Date: 2017-10-11 21:18+0000\n" +"Last-Translator: Érico Andrei \n" +"Language-Team: Érico Andrei \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" +"Language-Code: es\n" +"Language-Name: Español\n" +"Preferred-Encodings: utf-8 latin1\n" +"Domain: DOMAIN\n" + +#: collective/mastodon/actions/mastodon.py:177 +msgid "Action to post a toot to a Mastodon account." +msgstr "" + +#: collective/mastodon/actions/mastodon.py:176 +msgid "Add Mastodon Action" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:40 +msgid "App to be used" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:178 +msgid "Configure element" +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:12 +msgid "Direct" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:195 +msgid "Edit Mastodon Action" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:71 +msgid "If content effective date is in the future, schedule the post" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:59 +msgid "If not set, the content language will be used." +msgstr "" + +#: collective/mastodon/actions/mastodon.py:65 +msgid "Is this a sensitive content?" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:58 +msgid "Language" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:77 +msgid "Main text of the post." +msgstr "" + +#: collective/mastodon/actions/mastodon.py:39 +msgid "Mastodon App" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:105 +msgid "Post a new toot as ${user}" +msgstr "" + +#: collective/mastodon/actions/configure.zcml:36 +msgid "Post a status to Mastodon" +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:11 +msgid "Private" +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:9 +msgid "Public" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:70 +msgid "Schedule post?" +msgstr "" + +#: collective/mastodon/actions/configure.zcml:36 +msgid "Send a status (toot) to Mastodon" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:64 +msgid "Sensitive Content" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:52 +msgid "Spoiler Text" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:76 +msgid "Status" +msgstr "" + +#: collective/mastodon/interpolators/adapters.py:11 +msgid "Tags" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:53 +msgid "Text used for warning." +msgstr "" + +#: collective/mastodon/vocabularies/visibility.py:10 +msgid "Unlisted" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:45 +msgid "Visibility" +msgstr "" + +#: collective/mastodon/actions/mastodon.py:46 +msgid "Visibility of the post" +msgstr "" + +#. Default: "Category" +#: collective/mastodon/actions/mastodon.pt:14 +msgid "category-contentrules-mastodonsub" +msgstr "" + +#. Default: "Contents in the status field may be replaced with \"${}\" variables from the table on the bottom of this form." +#: collective/mastodon/actions/mastodon.pt:4 +msgid "description-contentrules-mastodonsub" +msgstr "" + +#. Default: "Substitution" +#: collective/mastodon/actions/mastodon.pt:16 +msgid "substitution-contentrules-mastodonsub" +msgstr "" + +#. Default: "Substitutions" +#: collective/mastodon/actions/mastodon.pt:10 +msgid "title_contentrules_mastodonsub" +msgstr "" + +#. Default: "Variable" +#: collective/mastodon/actions/mastodon.pt:15 +msgid "variable-contentrules-mastodonsub" +msgstr "" diff --git a/src/collective/mastodon/locales/pt_BR/LC_MESSAGES/collective.mastodon.po b/src/collective/mastodon/locales/pt_BR/LC_MESSAGES/collective.mastodon.po new file mode 100644 index 0000000..33061a1 --- /dev/null +++ b/src/collective/mastodon/locales/pt_BR/LC_MESSAGES/collective.mastodon.po @@ -0,0 +1,144 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2023-08-25 19:18+0000\n" +"PO-Revision-Date: 2017-10-11 21:18+0000\n" +"Last-Translator: Érico Andrei \n" +"Language-Team: Érico Andrei \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" +"Language-Code: en\n" +"Language-Name: English\n" +"Preferred-Encodings: utf-8 latin1\n" +"Domain: DOMAIN\n" + +#: collective/mastodon/actions/mastodon.py:177 +msgid "Action to post a toot to a Mastodon account." +msgstr "Ação que envia um novo toot para uma conta Mastodon." + +#: collective/mastodon/actions/mastodon.py:176 +msgid "Add Mastodon Action" +msgstr "Adicionar ação Mastodon" + +#: collective/mastodon/actions/mastodon.py:40 +msgid "App to be used" +msgstr "Aplicação a ser utilizada" + +#: collective/mastodon/actions/mastodon.py:178 +msgid "Configure element" +msgstr "Configurar elemento" + +#: collective/mastodon/vocabularies/visibility.py:12 +msgid "Direct" +msgstr "Apenas pessoas mencionadas" + +#: collective/mastodon/actions/mastodon.py:195 +msgid "Edit Mastodon Action" +msgstr "Editar ação Mastodon" + +#: collective/mastodon/actions/mastodon.py:71 +msgid "If content effective date is in the future, schedule the post" +msgstr "Caso a data de efetivação esteja no futuro, agendar a postagem" + +#: collective/mastodon/actions/mastodon.py:59 +msgid "If not set, the content language will be used." +msgstr "Caso não seja informada, o idioma do conteúdo será utilizado." + +#: collective/mastodon/actions/mastodon.py:65 +msgid "Is this a sensitive content?" +msgstr "Este é um conteúdo sensível?" + +#: collective/mastodon/actions/mastodon.py:58 +msgid "Language" +msgstr "Idioma" + +#: collective/mastodon/actions/mastodon.py:77 +msgid "Main text of the post." +msgstr "Texto da postagem." + +#: collective/mastodon/actions/mastodon.py:39 +msgid "Mastodon App" +msgstr "Aplicação Mastodon" + +#: collective/mastodon/actions/mastodon.py:105 +msgid "Post a new toot as ${user}" +msgstr "Postar um novo toot como ${user}" + +#: collective/mastodon/actions/configure.zcml:36 +msgid "Post a status to Mastodon" +msgstr "Postar um toot no Mastodon" + +#: collective/mastodon/vocabularies/visibility.py:11 +msgid "Private" +msgstr "Privado" + +#: collective/mastodon/vocabularies/visibility.py:9 +msgid "Public" +msgstr "Público" + +#: collective/mastodon/actions/mastodon.py:70 +msgid "Schedule post?" +msgstr "Agendar postagem?" + +#: collective/mastodon/actions/configure.zcml:36 +msgid "Send a status (toot) to Mastodon" +msgstr "Enviar um status (toot) para o Mastodon" + +#: collective/mastodon/actions/mastodon.py:64 +msgid "Sensitive Content" +msgstr "Conteúdo sensível" + +#: collective/mastodon/actions/mastodon.py:52 +msgid "Spoiler Text" +msgstr "Aviso de conteúdo" + +#: collective/mastodon/actions/mastodon.py:76 +msgid "Status" +msgstr "Texto" + +#: collective/mastodon/interpolators/adapters.py:11 +msgid "Tags" +msgstr "Tags" + +#: collective/mastodon/actions/mastodon.py:53 +msgid "Text used for warning." +msgstr "Texto utilizado como aviso de conteúdo." + +#: collective/mastodon/vocabularies/visibility.py:10 +msgid "Unlisted" +msgstr "Não-listado" + +#: collective/mastodon/actions/mastodon.py:45 +msgid "Visibility" +msgstr "Visibilidade" + +#: collective/mastodon/actions/mastodon.py:46 +msgid "Visibility of the post" +msgstr "Visibilidade da postagem" + +#. Default: "Category" +#: collective/mastodon/actions/mastodon.pt:14 +msgid "category-contentrules-mastodonsub" +msgstr "Categoria" + +#. Default: "Contents in the status field may be replaced with \"${}\" variables from the table on the bottom of this form." +#: collective/mastodon/actions/mastodon.pt:4 +msgid "description-contentrules-mastodonsub" +msgstr "Conteúdo no campo de texto podem ser substituídos por variáveis \"${}\" da tabela abaixo." + +#. Default: "Substitution" +#: collective/mastodon/actions/mastodon.pt:16 +msgid "substitution-contentrules-mastodonsub" +msgstr "Substituição" + +#. Default: "Substitutions" +#: collective/mastodon/actions/mastodon.pt:10 +msgid "title_contentrules_mastodonsub" +msgstr "Substituições" + +#. Default: "Variable" +#: collective/mastodon/actions/mastodon.pt:15 +msgid "variable-contentrules-mastodonsub" +msgstr "Variável" diff --git a/src/collective/mastodon/locales/update.py b/src/collective/mastodon/locales/update.py new file mode 100644 index 0000000..29a25e1 --- /dev/null +++ b/src/collective/mastodon/locales/update.py @@ -0,0 +1,78 @@ +"""Update locales.""" +from pathlib import Path + +import logging +import re +import subprocess + + +logger = logging.getLogger("i18n") +logger.setLevel(logging.DEBUG) + + +PATTERN = r"^[a-z]{2}.*" +domains = ("collective.mastodon",) +cwd = Path.cwd() +target_path = Path(__file__).parent.parent.resolve() +locale_path = target_path / "locales" + +i18ndude = cwd / "bin" / "i18ndude" +if not i18ndude.exists(): + i18ndude = cwd / "i18ndude" + +# ignore node_modules files resulting in errors +excludes = '"*.html *json-schema*.xml"' + + +def locale_folder_setup(domain: str): + languages = [path for path in locale_path.glob("*") if path.is_dir()] + for lang_folder in languages: + lc_messages_path = lang_folder / "LC_MESSAGES" + lang = lang_folder.name + if lc_messages_path.exists(): + continue + elif re.match(PATTERN, lang): + lc_messages_path.mkdir() + cmd = ( + f"msginit --locale={lang} " + f"--input={locale_path}/{domain}.pot " + f"--output={locale_path}/{lang}/LC_MESSAGES/{domain}.po" + ) + subprocess.call( + cmd, + shell=True, + ) + + +def _rebuild(domain: str): + cmd = ( + f"{i18ndude} rebuild-pot --pot {locale_path}/{domain}.pot " + f"--exclude {excludes} " + f"--create {domain} {target_path}" + ) + subprocess.call( + cmd, + shell=True, + ) + + +def _sync(domain: str): + cmd = ( + f"{i18ndude} sync --pot {locale_path}/{domain}.pot " + f"{locale_path}/*/LC_MESSAGES/{domain}.po" + ) + subprocess.call( + cmd, + shell=True, + ) + + +def update_locale(): + if i18ndude.exists(): + for domain in domains: + logger.info(f"Updating translations for {domain}") + locale_folder_setup(domain) + _rebuild(domain) + _sync(domain) + else: + logger.error("Not able to find i18ndude") diff --git a/src/collective/mastodon/registry.py b/src/collective/mastodon/registry.py new file mode 100644 index 0000000..03adb8d --- /dev/null +++ b/src/collective/mastodon/registry.py @@ -0,0 +1,20 @@ +from collective.mastodon.app import MastodonApp +from collective.mastodon.interfaces import IMastodonApp +from collective.mastodon.interfaces import IMastodonRegistry +from typing import List +from zope.component import getAllUtilitiesRegisteredFor +from zope.component import getUtility +from zope.interface import implementer + + +@implementer(IMastodonRegistry) +class MastodonRegistry: + """Mastodon Utility""" + + def get_app(self, name: str) -> MastodonApp: + """Return a named Mastodon application.""" + return getUtility(IMastodonApp, name=name) + + def get_apps(self) -> List[MastodonApp]: + """Return a list of registered Mastodon applications.""" + return getAllUtilitiesRegisteredFor(IMastodonApp) diff --git a/src/collective/mastodon/settings.py b/src/collective/mastodon/settings.py new file mode 100644 index 0000000..5e99705 --- /dev/null +++ b/src/collective/mastodon/settings.py @@ -0,0 +1,5 @@ +from prettyconf import config + + +def get_mastodon_apps(): + return config("MASTODON_APPS", cast=config.json, default="[]") diff --git a/src/collective/mastodon/startup/__init__.py b/src/collective/mastodon/startup/__init__.py new file mode 100644 index 0000000..63a39b8 --- /dev/null +++ b/src/collective/mastodon/startup/__init__.py @@ -0,0 +1,24 @@ +from logging import Logger + + +def register_apps(logger: Logger): + """Register Mastodon apps as utilities.""" + from collective.mastodon.app import MastodonApp + from collective.mastodon.interfaces import IMastodonApp + from collective.mastodon.interfaces import MastodonAppInfo + from collective.mastodon.settings import get_mastodon_apps + from zope.component import getGlobalSiteManager + + apps_info = [] + for payload in get_mastodon_apps(): + try: + app = MastodonAppInfo(**payload) + except TypeError as exc: + logger.warning(f"Wrong format for AppInfo {exc.args}") + else: + apps_info.append(app) + for info in apps_info: + app = MastodonApp( + name=info.name, instance=info.instance, token=info.token, user=info.user + ) + getGlobalSiteManager().registerUtility(app, IMastodonApp, name=info.name) diff --git a/src/collective/mastodon/testing.py b/src/collective/mastodon/testing.py new file mode 100644 index 0000000..c0efe89 --- /dev/null +++ b/src/collective/mastodon/testing.py @@ -0,0 +1,30 @@ +from plone.app.contenttypes.testing import PLONE_APP_CONTENTTYPES_FIXTURE +from plone.app.testing import IntegrationTesting +from plone.app.testing import PloneSandboxLayer + +import collective.mastodon + + +class MastodonLayer(PloneSandboxLayer): + defaultBases = (PLONE_APP_CONTENTTYPES_FIXTURE,) + + def setUpZope(self, app, configurationContext): + import plone.restapi + import plone.volto + + self.loadZCML(package=plone.restapi) + self.loadZCML(package=plone.volto) + self.loadZCML(package=collective.mastodon) + + def setUpPloneSite(self, portal): + st = portal.portal_setup + st.runAllImportStepsFromProfile("plone.volto:default") + + +FIXTURE = MastodonLayer() + + +INTEGRATION_TESTING = IntegrationTesting( + bases=(FIXTURE,), + name="MastodonLayer:IntegrationTesting", +) diff --git a/src/collective/mastodon/utils/__init__.py b/src/collective/mastodon/utils/__init__.py new file mode 100644 index 0000000..080b9eb --- /dev/null +++ b/src/collective/mastodon/utils/__init__.py @@ -0,0 +1,4 @@ +from .content import media_from_content # noQA +from .content import schedule_date # noQA +from .tools import get_app # noQA +from .username import mastodon_username # noQA diff --git a/src/collective/mastodon/utils/content.py b/src/collective/mastodon/utils/content.py new file mode 100644 index 0000000..56edebf --- /dev/null +++ b/src/collective/mastodon/utils/content.py @@ -0,0 +1,63 @@ +from Acquisition import aq_base +from collective.mastodon.interfaces import MastodonMedia +from datetime import datetime +from DateTime import DateTime +from io import BytesIO +from plone.dexterity.content import DexterityContent +from typing import Optional +from typing import Union + + +__all__ = ["media_from_content", "schedule_date"] + + +IMAGE_ORDER = [ + ("opengraph_image_link", "image_caption", "relation"), + ("opengraph_image", "image_caption", "field"), + ("preview_image_link", "preview_caption_link", "relation"), + ("preview_image", "preview_caption", "field"), + ("image_link", "image_caption", "relation"), + ("image", "image_caption", "field"), +] + + +def media_from_content(content: DexterityContent) -> Union[MastodonMedia, None]: + """Parse a content item and return a MastodonMedia object.""" + content = aq_base(content) + for field_name, field_caption, field_type in IMAGE_ORDER: + title = content.title + description = content.description + # Image does not have an attribute image_caption + if content.portal_type == "Image": + caption = description + else: + caption = getattr(content, field_caption, None) + field = getattr(content, field_name, None) + if not field: + continue + if field_type == "relation": + target = field.to_object + field = target.image + caption = caption if caption else (target.description or target.title) + data = field.data + if data: + caption = caption if caption else title + return MastodonMedia( + file=BytesIO(data), mime_type=field.contentType, description=caption + ) + + +def _schedule_at(date: Optional[Union[DateTime, datetime]]) -> Union[datetime, None]: + schedule_at = None + if isinstance(date, DateTime) and date.isFuture(): + schedule_at = date.asdatetime() + elif isinstance(date, datetime): + now = datetime.now(date.tzinfo) + if date > now: + schedule_at = date + return schedule_at + + +def schedule_date(content: DexterityContent) -> Union[datetime, None]: + """Extract from content the date to schedule the post.""" + return _schedule_at(content.effective_date) diff --git a/src/collective/mastodon/utils/tools.py b/src/collective/mastodon/utils/tools.py new file mode 100644 index 0000000..7fcbf68 --- /dev/null +++ b/src/collective/mastodon/utils/tools.py @@ -0,0 +1,9 @@ +from collective.mastodon.app import MastodonApp +from collective.mastodon.interfaces import IMastodonRegistry +from zope.component import getUtility + + +def get_app(name: str) -> MastodonApp: + """Given a name, return a MastodonApp.""" + util = getUtility(IMastodonRegistry) + return util.get_app(name) diff --git a/src/collective/mastodon/utils/username.py b/src/collective/mastodon/utils/username.py new file mode 100644 index 0000000..6a5889b --- /dev/null +++ b/src/collective/mastodon/utils/username.py @@ -0,0 +1,8 @@ +from urllib.parse import urlparse + + +def mastodon_username(instance: str, user: str) -> str: + """Format username from instance information and user.""" + parsed = urlparse(instance) + hostname = parsed.hostname + return f"@{user}@{hostname}" diff --git a/src/collective/mastodon/vocabularies/__init__.py b/src/collective/mastodon/vocabularies/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/collective/mastodon/vocabularies/apps.py b/src/collective/mastodon/vocabularies/apps.py new file mode 100644 index 0000000..a14dfe0 --- /dev/null +++ b/src/collective/mastodon/vocabularies/apps.py @@ -0,0 +1,20 @@ +from collective.mastodon.interfaces import IMastodonRegistry +from collective.mastodon.utils import mastodon_username +from zope.component import getUtility +from zope.interface import provider +from zope.schema.interfaces import IVocabularyFactory +from zope.schema.vocabulary import SimpleTerm +from zope.schema.vocabulary import SimpleVocabulary + + +@provider(IVocabularyFactory) +def mastodon_apps(_): + """List registered Mastodon apps.""" + registry = getUtility(IMastodonRegistry) + apps = registry.get_apps() + terms = [] + for app in apps: + title = mastodon_username(app.instance, app.user) + terms.append(SimpleTerm(value=app.name, token=app.name, title=title)) + + return SimpleVocabulary(terms) diff --git a/src/collective/mastodon/vocabularies/configure.zcml b/src/collective/mastodon/vocabularies/configure.zcml new file mode 100644 index 0000000..98c44cf --- /dev/null +++ b/src/collective/mastodon/vocabularies/configure.zcml @@ -0,0 +1,12 @@ + + + + + + diff --git a/src/collective/mastodon/vocabularies/visibility.py b/src/collective/mastodon/vocabularies/visibility.py new file mode 100644 index 0000000..16bf2da --- /dev/null +++ b/src/collective/mastodon/vocabularies/visibility.py @@ -0,0 +1,23 @@ +from collective.mastodon import _ +from zope.interface import provider +from zope.schema.interfaces import IVocabularyFactory +from zope.schema.vocabulary import SimpleTerm +from zope.schema.vocabulary import SimpleVocabulary + + +TERMS = [ + ("public", _("Public")), + ("unlisted", _("Unlisted")), + ("private", _("Private")), + ("direct", _("Direct")), +] + + +@provider(IVocabularyFactory) +def post_visibility(context): + """Mastodon post visibility.""" + terms = [] + for token, title in TERMS: + terms.append(SimpleTerm(value=token, token=token, title=title)) + + return SimpleVocabulary(terms) diff --git a/tests/actions/cassettes/TestAction.test_call.yaml b/tests/actions/cassettes/TestAction.test_call.yaml new file mode 100644 index 0000000..0938afa --- /dev/null +++ b/tests/actions/cassettes/TestAction.test_call.yaml @@ -0,0 +1,60 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + User-Agent: + - collective.mastodon + method: GET + uri: http://localhost/api/v1/instance/ + response: + body: + string: '{"uri":"localhost:3000","title":"Mastodon","short_description":"","description":"","email":"","version":"4.1.6","urls":{"streaming_api":"ws://localhost:4000"},"stats":{"user_count":1,"status_count":22,"domain_count":0},"thumbnail":"http://127.0.0.1/packs/media/images/preview-6399aebd96ccf025654e2977454f168f.png","languages":["en"],"registrations":true,"approval_required":false,"invites_enabled":false,"configuration":{"accounts":{"max_featured_tags":10},"statuses":{"max_characters":500,"max_media_attachments":4,"characters_reserved_per_url":23},"media_attachments":{"supported_mime_types":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":2304000},"polls":{"max_options":4,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"contact_account":null,"rules":[]}' + headers: + Cache-Control: + - max-age=180, public + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 25 Aug 2023 18:15:55 GMT + ETag: + - W/"141c61bbc3aa4191b095358694f82c93" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '292' + X-RateLimit-Reset: + - '2023-08-25T18:20:00.968524Z' + X-Request-Id: + - f0cdbdbd-362b-4b45-99e6-2e872dbddd63 + X-Runtime: + - '0.030118' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/actions/cassettes/TestAction.test_execute.yaml b/tests/actions/cassettes/TestAction.test_execute.yaml new file mode 100644 index 0000000..fbfb859 --- /dev/null +++ b/tests/actions/cassettes/TestAction.test_execute.yaml @@ -0,0 +1,60 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + User-Agent: + - collective.mastodon + method: GET + uri: http://localhost/api/v1/instance/ + response: + body: + string: '{"uri":"localhost:3000","title":"Mastodon","short_description":"","description":"","email":"","version":"4.1.6","urls":{"streaming_api":"ws://localhost:4000"},"stats":{"user_count":1,"status_count":20,"domain_count":0},"thumbnail":"http://127.0.0.1/packs/media/images/preview-6399aebd96ccf025654e2977454f168f.png","languages":["en"],"registrations":true,"approval_required":false,"invites_enabled":false,"configuration":{"accounts":{"max_featured_tags":10},"statuses":{"max_characters":500,"max_media_attachments":4,"characters_reserved_per_url":23},"media_attachments":{"supported_mime_types":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":2304000},"polls":{"max_options":4,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"contact_account":null,"rules":[]}' + headers: + Cache-Control: + - max-age=180, public + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 25 Aug 2023 15:17:08 GMT + ETag: + - W/"1570e6866e69f9c3c16a8214834c902b" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '293' + X-RateLimit-Reset: + - '2023-08-25T15:20:00.554820Z' + X-Request-Id: + - 1fe9583d-6047-4409-aad3-9fbcc31e0cf0 + X-Runtime: + - '0.027529' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/actions/test_action_mastodon.py b/tests/actions/test_action_mastodon.py new file mode 100644 index 0000000..e7a4d19 --- /dev/null +++ b/tests/actions/test_action_mastodon.py @@ -0,0 +1,128 @@ +from collective.mastodon.actions.mastodon import MastodonAction +from collective.mastodon.actions.mastodon import MastodonAddFormView +from collective.mastodon.actions.mastodon import MastodonEditFormView +from plone.app.contentrules.rule import Rule +from plone.contentrules.engine.interfaces import IRuleStorage +from plone.contentrules.rule.interfaces import IExecutable +from plone.contentrules.rule.interfaces import IRuleAction +from zope.component import getMultiAdapter +from zope.component import getUtility +from zope.interface import implementer +from zope.interface.interfaces import IObjectEvent + +import pytest + + +@pytest.fixture +def action_payload() -> dict: + return { + "app": "localhost-admin", + "visibility": "public", + "spoiler_text": "${title}", + "language": "en", + "sensitive": False, + "scheduling": True, + "status": "Hello word! ${tags} ${absolute_url}", + } + + +@pytest.fixture +def mastodon_action(action_payload) -> MastodonAction: + e = MastodonAction() + for attr, value in action_payload.items(): + setattr(e, attr, value) + return e + + +@implementer(IObjectEvent) +class DummyEvent: + def __init__(self, object): + self.object = object + + +class TestAction: + name: str = "plone.actions.Mastodon" + + @pytest.fixture(autouse=True) + def _init(self, portal): + self.portal = portal + self.image = portal["an-image"] + + def add_view(self, http_request): + element = getUtility(IRuleAction, name=self.name) + storage = getUtility(IRuleStorage) + storage["foo"] = Rule() + rule = self.portal.restrictedTraverse("++rule++foo") + adding = getMultiAdapter((rule, http_request), name="+action") + addview = getMultiAdapter((adding, http_request), name=element.addview) + return addview + + def test_registered(self): + element = getUtility(IRuleAction, name=self.name) + assert self.name == element.addview + assert "edit" == element.editview + assert element.for_ is None + + def test_summary(self, mastodon_action): + from zope.i18nmessageid.message import Message + + summary = mastodon_action.summary + assert isinstance(summary, Message) + assert summary == "Post a new toot as ${user}" + assert summary.mapping == {"user": "@admin@localhost"} + + def test_add_view(self, http_request, action_payload): + addview = self.add_view(http_request) + assert isinstance(addview, MastodonAddFormView) is True + addview.form_instance.update() + output = addview.form_instance() + assert "

Substitutions

" in output + content = addview.form_instance.create(data=action_payload) + addview.form_instance.add(content) + rule = self.portal.restrictedTraverse("++rule++foo") + e = rule.actions[0] + assert isinstance(e, MastodonAction) + assert e.app == "localhost-admin" + + def test_edit_view(self, http_request): + element = getUtility(IRuleAction, name=self.name) + e = MastodonAction() + editview = getMultiAdapter((e, http_request), name=element.editview) + assert isinstance(editview, MastodonEditFormView) + + @pytest.mark.parametrize( + "key,expected", + [ + ("status", "Hello word! #Image #Plone http://nohost/plone/an-image"), + ("spoiler_text", "A Random Image"), + ], + ) + def test_payload_interpolation(self, mastodon_action, key: str, expected: str): + ex = getMultiAdapter( + (self.portal, mastodon_action, DummyEvent(self.image)), IExecutable + ) + payload = ex._prepare_payload() + assert payload[key] == expected + + def test_language_from_content(self, mastodon_action): + mastodon_action.language = "" + ex = getMultiAdapter( + (self.portal, mastodon_action, DummyEvent(self.image)), IExecutable + ) + payload = ex._prepare_payload() + assert payload["language"] == self.image.language + + @pytest.mark.vcr(match_on=["path"]) + def test_execute(self, mastodon_action, wait_for): + ex = getMultiAdapter( + (self.portal, mastodon_action, DummyEvent(self.image)), IExecutable + ) + payload = ex._prepare_payload() + wait_for(ex._post(payload)) + + @pytest.mark.vcr(match_on=["path"]) + def test_call(self, mastodon_action, wait_for): + ex = getMultiAdapter( + (self.portal, mastodon_action, DummyEvent(self.image)), IExecutable + ) + assert ex() is True diff --git a/tests/app/cassettes/TestAppScheduledStatuses.test_scheduled_posts.yaml b/tests/app/cassettes/TestAppScheduledStatuses.test_scheduled_posts.yaml new file mode 100644 index 0000000..8524bac --- /dev/null +++ b/tests/app/cassettes/TestAppScheduledStatuses.test_scheduled_posts.yaml @@ -0,0 +1,121 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + User-Agent: + - collective.mastodon + method: GET + uri: http://localhost/api/v1/instance/ + response: + body: + string: '{"uri":"localhost:3000","title":"Mastodon","short_description":"","description":"","email":"","version":"4.1.6","urls":{"streaming_api":"ws://localhost:4000"},"stats":{"user_count":1,"status_count":18,"domain_count":0},"thumbnail":"http://127.0.0.1/packs/media/images/preview-6399aebd96ccf025654e2977454f168f.png","languages":["en"],"registrations":true,"approval_required":false,"invites_enabled":false,"configuration":{"accounts":{"max_featured_tags":10},"statuses":{"max_characters":500,"max_media_attachments":4,"characters_reserved_per_url":23},"media_attachments":{"supported_mime_types":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":2304000},"polls":{"max_options":4,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"contact_account":null,"rules":[]}' + headers: + Cache-Control: + - max-age=180, public + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 25 Aug 2023 14:19:29 GMT + ETag: + - W/"8ef40649e0b83341c46990808ac25c8a" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '296' + X-RateLimit-Reset: + - '2023-08-25T14:20:00.927853Z' + X-Request-Id: + - 4d4e22e3-2d69-4e62-9e6c-6c9b82ba7d38 + X-Runtime: + - '0.028665' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + User-Agent: + - collective.mastodon + method: GET + uri: http://localhost/api/v1/scheduled_statuses + response: + body: + string: '[{"id":"4","scheduled_at":"4000-01-01T14:13:14.000Z","params":{"poll":null,"text":"Just + a toot, with #tags","language":"en","media_ids":null,"sensitive":null,"visibility":"public","idempotency":null,"scheduled_at":null,"spoiler_text":"","application_id":1,"in_reply_to_id":null,"with_rate_limit":false},"media_attachments":[]},{"id":"1","scheduled_at":"2023-08-26T00:01:00.000Z","params":{"poll":null,"text":"A + new package is available: Just added a new Plone addon. collective.mastodon!\r\nPlone, + AddOn\r\nhttp://localhost:8080/Plone/a-new-package-is-available\r\nhttps://ericof.com/foo","language":"en","media_ids":null,"sensitive":null,"visibility":"public","idempotency":null,"scheduled_at":null,"spoiler_text":"","application_id":1,"in_reply_to_id":null,"with_rate_limit":false},"media_attachments":[]}]' + headers: + Cache-Control: + - private, no-store + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"3b7aa20d34fe14ae1029d9a974ab35a4" + Link: + - ; rel="prev" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '295' + X-RateLimit-Reset: + - '2023-08-25T14:20:00.028530Z' + X-Request-Id: + - 7c1428da-bad3-44f4-b64c-a4141f2f6a82 + X-Runtime: + - '0.037609' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/app/cassettes/TestAppStatusPost.test_post.yaml b/tests/app/cassettes/TestAppStatusPost.test_post.yaml new file mode 100644 index 0000000..0e7128b --- /dev/null +++ b/tests/app/cassettes/TestAppStatusPost.test_post.yaml @@ -0,0 +1,122 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + User-Agent: + - collective.mastodon + method: GET + uri: http://localhost/api/v1/instance/ + response: + body: + string: '{"uri":"localhost:3000","title":"Mastodon","short_description":"","description":"","email":"","version":"4.1.6","urls":{"streaming_api":"ws://localhost:4000"},"stats":{"user_count":1,"status_count":10,"domain_count":0},"thumbnail":"http://127.0.0.1/packs/media/images/preview-6399aebd96ccf025654e2977454f168f.png","languages":["en"],"registrations":true,"approval_required":false,"invites_enabled":false,"configuration":{"accounts":{"max_featured_tags":10},"statuses":{"max_characters":500,"max_media_attachments":4,"characters_reserved_per_url":23},"media_attachments":{"supported_mime_types":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":2304000},"polls":{"max_options":4,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"contact_account":null,"rules":[]}' + headers: + Cache-Control: + - max-age=180, public + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 25 Aug 2023 14:07:47 GMT + ETag: + - W/"1aca0f9b1cfe4a7330d15375f45b85b1" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '291' + X-RateLimit-Reset: + - '2023-08-25T14:10:00.916205Z' + X-Request-Id: + - 0f75e2da-04c1-4851-8599-26c31de154a5 + X-Runtime: + - '0.089583' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: status=Just+a+toot%2C+with+%23tags&visibility=public&spoiler_text=&language=en + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + Content-Length: + - '78' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - collective.mastodon + method: POST + uri: http://localhost/api/v1/statuses + response: + body: + string: '{"id":"110950643667554559","created_at":"2023-08-25T14:07:48.090Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"http://127.0.0.1/users/user/statuses/110950643667554559","url":"http://127.0.0.1/@user/110950643667554559","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":null,"favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"\u003cp\u003eJust + a toot, with \u003ca href=\"http://127.0.0.1/tags/tags\" class=\"mention hashtag\" + rel=\"tag\"\u003e#\u003cspan\u003etags\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e","filtered":[],"reblog":null,"application":{"name":"Foo","website":"http://localhost"},"account":{"id":"110945794030490080","username":"user","acct":"user","display_name":"","locked":false,"bot":false,"discoverable":null,"group":false,"created_at":"2023-08-24T00:00:00.000Z","note":"","url":"http://127.0.0.1/@user","avatar":"http://127.0.0.1/avatars/original/missing.png","avatar_static":"http://127.0.0.1/avatars/original/missing.png","header":"http://127.0.0.1/headers/original/missing.png","header_static":"http://127.0.0.1/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":15,"last_status_at":"2023-08-25","noindex":false,"emojis":[],"roles":[{"id":"3","name":"Owner","color":""}],"fields":[]},"media_attachments":[],"mentions":[],"tags":[{"name":"tags","url":"http://127.0.0.1/tags/tags"}],"emojis":[],"card":null,"poll":null}' + headers: + Cache-Control: + - private, no-store + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"3c8f476ceff1674df6508fa317a43a15" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '289' + X-RateLimit-Reset: + - '2023-08-25T15:00:00.232338Z' + X-Request-Id: + - 38ae4149-84e1-48b6-8736-3a2253bd154c + X-Runtime: + - '0.275739' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/app/cassettes/TestAppStatusPost.test_post_language.yaml b/tests/app/cassettes/TestAppStatusPost.test_post_language.yaml new file mode 100644 index 0000000..d87990e --- /dev/null +++ b/tests/app/cassettes/TestAppStatusPost.test_post_language.yaml @@ -0,0 +1,122 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + User-Agent: + - collective.mastodon + method: GET + uri: http://localhost/api/v1/instance/ + response: + body: + string: '{"uri":"localhost:3000","title":"Mastodon","short_description":"","description":"","email":"","version":"4.1.6","urls":{"streaming_api":"ws://localhost:4000"},"stats":{"user_count":1,"status_count":20,"domain_count":0},"thumbnail":"http://127.0.0.1/packs/media/images/preview-6399aebd96ccf025654e2977454f168f.png","languages":["en"],"registrations":true,"approval_required":false,"invites_enabled":false,"configuration":{"accounts":{"max_featured_tags":10},"statuses":{"max_characters":500,"max_media_attachments":4,"characters_reserved_per_url":23},"media_attachments":{"supported_mime_types":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":2304000},"polls":{"max_options":4,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"contact_account":null,"rules":[]}' + headers: + Cache-Control: + - max-age=180, public + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 25 Aug 2023 15:19:04 GMT + ETag: + - W/"1570e6866e69f9c3c16a8214834c902b" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '223' + X-RateLimit-Reset: + - '2023-08-25T15:20:00.008425Z' + X-Request-Id: + - 2c93a169-45af-4bd3-8925-92cf8d42dc37 + X-Runtime: + - '0.029888' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: status=Just+a+toot%2C+with+%23tags&visibility=public&spoiler_text=&language=pt + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + Content-Length: + - '78' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - collective.mastodon + method: POST + uri: http://localhost/api/v1/statuses + response: + body: + string: '{"id":"110950923906006089","created_at":"2023-08-25T15:19:04.178Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"pt","uri":"http://127.0.0.1/users/user/statuses/110950923906006089","url":"http://127.0.0.1/@user/110950923906006089","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":null,"favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"\u003cp\u003eJust + a toot, with \u003ca href=\"http://127.0.0.1/tags/tags\" class=\"mention hashtag\" + rel=\"tag\"\u003e#\u003cspan\u003etags\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e","filtered":[],"reblog":null,"application":{"name":"Foo","website":"http://localhost"},"account":{"id":"110945794030490080","username":"user","acct":"user","display_name":"","locked":false,"bot":false,"discoverable":null,"group":false,"created_at":"2023-08-24T00:00:00.000Z","note":"","url":"http://127.0.0.1/@user","avatar":"http://127.0.0.1/avatars/original/missing.png","avatar_static":"http://127.0.0.1/avatars/original/missing.png","header":"http://127.0.0.1/headers/original/missing.png","header_static":"http://127.0.0.1/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":22,"last_status_at":"2023-08-25","noindex":false,"emojis":[],"roles":[{"id":"3","name":"Owner","color":""}],"fields":[]},"media_attachments":[],"mentions":[],"tags":[{"name":"tags","url":"http://127.0.0.1/tags/tags"}],"emojis":[],"card":null,"poll":null}' + headers: + Cache-Control: + - private, no-store + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"d370db87fac58ab89321561f18e37484" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '222' + X-RateLimit-Reset: + - '2023-08-25T15:20:00.137067Z' + X-Request-Id: + - 286ab21a-227f-4597-b372-84d0bd65c1aa + X-Runtime: + - '0.167884' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/app/cassettes/TestAppStatusPost.test_post_media.yaml b/tests/app/cassettes/TestAppStatusPost.test_post_media.yaml new file mode 100644 index 0000000..722913a --- /dev/null +++ b/tests/app/cassettes/TestAppStatusPost.test_post_media.yaml @@ -0,0 +1,192 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + User-Agent: + - collective.mastodon + method: GET + uri: http://localhost/api/v1/instance/ + response: + body: + string: '{"uri":"localhost:3000","title":"Mastodon","short_description":"","description":"","email":"","version":"4.1.6","urls":{"streaming_api":"ws://localhost:4000"},"stats":{"user_count":1,"status_count":18,"domain_count":0},"thumbnail":"http://127.0.0.1/packs/media/images/preview-6399aebd96ccf025654e2977454f168f.png","languages":["en"],"registrations":true,"approval_required":false,"invites_enabled":false,"configuration":{"accounts":{"max_featured_tags":10},"statuses":{"max_characters":500,"max_media_attachments":4,"characters_reserved_per_url":23},"media_attachments":{"supported_mime_types":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":2304000},"polls":{"max_options":4,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"contact_account":null,"rules":[]}' + headers: + Cache-Control: + - max-age=180, public + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 25 Aug 2023 14:30:39 GMT + ETag: + - W/"8ef40649e0b83341c46990808ac25c8a" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2023-08-25T14:35:00.241893Z' + X-Request-Id: + - 02763790-b8af-451f-9bdd-516eebb42aeb + X-Runtime: + - '0.095484' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: !!binary | + LS02Mzg4Njg0M2EyZDA2MDQ1NDcwYWRlNDNiNjA0YWE0Yw0KQ29udGVudC1EaXNwb3NpdGlvbjog + Zm9ybS1kYXRhOyBuYW1lPSJkZXNjcmlwdGlvbiINCg0KV2l0aCBzb21lIGRldGFpbHMNCi0tNjM4 + ODY4NDNhMmQwNjA0NTQ3MGFkZTQzYjYwNGFhNGMNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0t + ZGF0YTsgbmFtZT0iZmlsZSI7IGZpbGVuYW1lPSJtYXN0b2RvbnB5dXBsb2FkXzE2OTI5NzM4Mzku + MzYzNjk2OF9iNjhmMGNmNWFmYTA0NjJjYWM4MzgyOGIyZDMxYzQ1Yy5wbmciDQpDb250ZW50LVR5 + cGU6IGltYWdlL3BuZw0KDQqJUE5HDQoaCgAAAA1JSERSAAAAAQAAAAEIBgAAAB8VxIkAAAABc1JH + QgCuzhzpAAAADUlEQVQYV2MIM779HwAEqwJkP74JwwAAAABJRU5ErkJggg0KLS02Mzg4Njg0M2Ey + ZDA2MDQ1NDcwYWRlNDNiNjA0YWE0Yy0tDQo= + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + Content-Length: + - '425' + Content-Type: + - multipart/form-data; boundary=63886843a2d06045470ade43b604aa4c + User-Agent: + - collective.mastodon + method: POST + uri: http://localhost/api/v2/media + response: + body: + string: '{"id":"110950733579166937","type":"image","url":"http://127.0.0.1/system/media_attachments/files/110/950/733/579/166/937/original/2f515b9f98f38a04.png","preview_url":"http://127.0.0.1/system/media_attachments/files/110/950/733/579/166/937/small/2f515b9f98f38a04.png","remote_url":null,"preview_remote_url":null,"text_url":null,"meta":{"original":{"width":1,"height":1,"size":"1x1","aspect":1.0},"small":{"width":1,"height":1,"size":"1x1","aspect":1.0}},"description":"With + some details","blurhash":"U~9?4x$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;"}' + headers: + Cache-Control: + - private, no-store + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"2b49cda7fae39dd977af9a4481538fda" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '30' + X-RateLimit-Remaining: + - '29' + X-RateLimit-Reset: + - '2023-08-25T15:00:00.401971Z' + X-Request-Id: + - e0de898a-4961-4d79-8b18-f4b65aaf6ce4 + X-Runtime: + - '0.658469' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: status=Just+a+toot%2C+with+%23tags&visibility=public&spoiler_text=&language=en&media_ids%5B%5D=110950733579166937 + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + Content-Length: + - '113' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - collective.mastodon + method: POST + uri: http://localhost/api/v1/statuses + response: + body: + string: '{"id":"110950733584495739","created_at":"2023-08-25T14:30:40.100Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"http://127.0.0.1/users/user/statuses/110950733584495739","url":"http://127.0.0.1/@user/110950733584495739","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":null,"favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"\u003cp\u003eJust + a toot, with \u003ca href=\"http://127.0.0.1/tags/tags\" class=\"mention hashtag\" + rel=\"tag\"\u003e#\u003cspan\u003etags\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e","filtered":[],"reblog":null,"application":{"name":"Foo","website":"http://localhost"},"account":{"id":"110945794030490080","username":"user","acct":"user","display_name":"","locked":false,"bot":false,"discoverable":null,"group":false,"created_at":"2023-08-24T00:00:00.000Z","note":"","url":"http://127.0.0.1/@user","avatar":"http://127.0.0.1/avatars/original/missing.png","avatar_static":"http://127.0.0.1/avatars/original/missing.png","header":"http://127.0.0.1/headers/original/missing.png","header_static":"http://127.0.0.1/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":20,"last_status_at":"2023-08-25","noindex":false,"emojis":[],"roles":[{"id":"3","name":"Owner","color":""}],"fields":[]},"media_attachments":[{"id":"110950733579166937","type":"image","url":"http://127.0.0.1/system/media_attachments/files/110/950/733/579/166/937/original/2f515b9f98f38a04.png","preview_url":"http://127.0.0.1/system/media_attachments/files/110/950/733/579/166/937/small/2f515b9f98f38a04.png","remote_url":null,"preview_remote_url":null,"text_url":null,"meta":{"original":{"width":1,"height":1,"size":"1x1","aspect":1.0},"small":{"width":1,"height":1,"size":"1x1","aspect":1.0}},"description":"With + some details","blurhash":"U~9?4x$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;"}],"mentions":[],"tags":[{"name":"tags","url":"http://127.0.0.1/tags/tags"}],"emojis":[],"card":null,"poll":null}' + headers: + Cache-Control: + - private, no-store + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"39995bfa28ed386e74d0d9f693311071" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '284' + X-RateLimit-Reset: + - '2023-08-25T15:00:00.207983Z' + X-Request-Id: + - f817444f-6659-4601-9dc0-ecc2a536cbb7 + X-Runtime: + - '0.159441' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/app/cassettes/TestAppStatusPost.test_post_private.yaml b/tests/app/cassettes/TestAppStatusPost.test_post_private.yaml new file mode 100644 index 0000000..a8e9e56 --- /dev/null +++ b/tests/app/cassettes/TestAppStatusPost.test_post_private.yaml @@ -0,0 +1,122 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + User-Agent: + - collective.mastodon + method: GET + uri: http://localhost/api/v1/instance/ + response: + body: + string: '{"uri":"localhost:3000","title":"Mastodon","short_description":"","description":"","email":"","version":"4.1.6","urls":{"streaming_api":"ws://localhost:4000"},"stats":{"user_count":1,"status_count":10,"domain_count":0},"thumbnail":"http://127.0.0.1/packs/media/images/preview-6399aebd96ccf025654e2977454f168f.png","languages":["en"],"registrations":true,"approval_required":false,"invites_enabled":false,"configuration":{"accounts":{"max_featured_tags":10},"statuses":{"max_characters":500,"max_media_attachments":4,"characters_reserved_per_url":23},"media_attachments":{"supported_mime_types":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":2304000},"polls":{"max_options":4,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"contact_account":null,"rules":[]}' + headers: + Cache-Control: + - max-age=180, public + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 25 Aug 2023 14:07:48 GMT + ETag: + - W/"1aca0f9b1cfe4a7330d15375f45b85b1" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '289' + X-RateLimit-Reset: + - '2023-08-25T14:10:00.273246Z' + X-Request-Id: + - 5b6ee86b-4f87-416e-b75a-4141ec93c825 + X-Runtime: + - '0.021555' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: status=Just+a+toot%2C+with+%23tags&visibility=private&spoiler_text=&language=en + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + Content-Length: + - '79' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - collective.mastodon + method: POST + uri: http://localhost/api/v1/statuses + response: + body: + string: '{"id":"110950643688445304","created_at":"2023-08-25T14:07:48.404Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"private","language":"en","uri":"http://127.0.0.1/users/user/statuses/110950643688445304","url":"http://127.0.0.1/@user/110950643688445304","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":null,"favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"\u003cp\u003eJust + a toot, with \u003ca href=\"http://127.0.0.1/tags/tags\" class=\"mention hashtag\" + rel=\"tag\"\u003e#\u003cspan\u003etags\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e","filtered":[],"reblog":null,"application":{"name":"Foo","website":"http://localhost"},"account":{"id":"110945794030490080","username":"user","acct":"user","display_name":"","locked":false,"bot":false,"discoverable":null,"group":false,"created_at":"2023-08-24T00:00:00.000Z","note":"","url":"http://127.0.0.1/@user","avatar":"http://127.0.0.1/avatars/original/missing.png","avatar_static":"http://127.0.0.1/avatars/original/missing.png","header":"http://127.0.0.1/headers/original/missing.png","header_static":"http://127.0.0.1/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":16,"last_status_at":"2023-08-25","noindex":false,"emojis":[],"roles":[{"id":"3","name":"Owner","color":""}],"fields":[]},"media_attachments":[],"mentions":[],"tags":[{"name":"tags","url":"http://127.0.0.1/tags/tags"}],"emojis":[],"card":null,"poll":null}' + headers: + Cache-Control: + - private, no-store + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"6a5c1c8095de586b02e87f17278f7916" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '288' + X-RateLimit-Reset: + - '2023-08-25T15:00:00.553057Z' + X-Request-Id: + - 4dcaaf8a-2b98-4788-93a5-4c117b441a88 + X-Runtime: + - '0.261715' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/app/cassettes/TestAppStatusPost.test_post_scheduled_at.yaml b/tests/app/cassettes/TestAppStatusPost.test_post_scheduled_at.yaml new file mode 100644 index 0000000..df3770a --- /dev/null +++ b/tests/app/cassettes/TestAppStatusPost.test_post_scheduled_at.yaml @@ -0,0 +1,121 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + User-Agent: + - collective.mastodon + method: GET + uri: http://localhost/api/v1/instance/ + response: + body: + string: '{"uri":"localhost:3000","title":"Mastodon","short_description":"","description":"","email":"","version":"4.1.6","urls":{"streaming_api":"ws://localhost:4000"},"stats":{"user_count":1,"status_count":10,"domain_count":0},"thumbnail":"http://127.0.0.1/packs/media/images/preview-6399aebd96ccf025654e2977454f168f.png","languages":["en"],"registrations":true,"approval_required":false,"invites_enabled":false,"configuration":{"accounts":{"max_featured_tags":10},"statuses":{"max_characters":500,"max_media_attachments":4,"characters_reserved_per_url":23},"media_attachments":{"supported_mime_types":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":2304000},"polls":{"max_options":4,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"contact_account":null,"rules":[]}' + headers: + Cache-Control: + - max-age=180, public + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 25 Aug 2023 14:14:07 GMT + ETag: + - W/"1aca0f9b1cfe4a7330d15375f45b85b1" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '298' + X-RateLimit-Reset: + - '2023-08-25T14:15:00.384064Z' + X-Request-Id: + - b9faafac-d0c1-4e3f-bf2e-a07a4f70a2ce + X-Runtime: + - '0.088956' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: status=Just+a+toot%2C+with+%23tags&visibility=public&spoiler_text=&language=en&scheduled_at=4000-01-01T14%3A13%3A14%2B00%3A00 + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + Content-Length: + - '125' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - collective.mastodon + method: POST + uri: http://localhost/api/v1/statuses + response: + body: + string: '{"id":"4","scheduled_at":"4000-01-01T14:13:14.000Z","params":{"text":"Just + a toot, with #tags","media_ids":null,"sensitive":null,"spoiler_text":"","visibility":"public","language":"en","scheduled_at":null,"poll":null,"idempotency":null,"with_rate_limit":false,"in_reply_to_id":null,"application_id":1},"media_attachments":[]}' + headers: + Cache-Control: + - private, no-store + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"8b8a44fa21502b88eac8b3dfb3ca2bf2" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '286' + X-RateLimit-Reset: + - '2023-08-25T15:00:00.534369Z' + X-Request-Id: + - 7700936e-2d3e-4649-b7cd-9a59982ff182 + X-Runtime: + - '0.119814' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/app/cassettes/TestAppStatusPost.test_post_sensitive.yaml b/tests/app/cassettes/TestAppStatusPost.test_post_sensitive.yaml new file mode 100644 index 0000000..f10de84 --- /dev/null +++ b/tests/app/cassettes/TestAppStatusPost.test_post_sensitive.yaml @@ -0,0 +1,122 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + User-Agent: + - collective.mastodon + method: GET + uri: http://localhost/api/v1/instance/ + response: + body: + string: '{"uri":"localhost:3000","title":"Mastodon","short_description":"","description":"","email":"","version":"4.1.6","urls":{"streaming_api":"ws://localhost:4000"},"stats":{"user_count":1,"status_count":10,"domain_count":0},"thumbnail":"http://127.0.0.1/packs/media/images/preview-6399aebd96ccf025654e2977454f168f.png","languages":["en"],"registrations":true,"approval_required":false,"invites_enabled":false,"configuration":{"accounts":{"max_featured_tags":10},"statuses":{"max_characters":500,"max_media_attachments":4,"characters_reserved_per_url":23},"media_attachments":{"supported_mime_types":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":2304000},"polls":{"max_options":4,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"contact_account":null,"rules":[]}' + headers: + Cache-Control: + - max-age=180, public + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 25 Aug 2023 14:07:48 GMT + ETag: + - W/"1aca0f9b1cfe4a7330d15375f45b85b1" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '287' + X-RateLimit-Reset: + - '2023-08-25T14:10:00.601549Z' + X-Request-Id: + - b8a1c9ec-3c4c-4993-9ef0-569f79b8bd18 + X-Runtime: + - '0.018760' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: status=Just+a+toot%2C+with+%23tags&sensitive=1&visibility=public&spoiler_text=&language=en + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + Content-Length: + - '90' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - collective.mastodon + method: POST + uri: http://localhost/api/v1/statuses + response: + body: + string: '{"id":"110950643705404353","created_at":"2023-08-25T14:07:48.653Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":true,"spoiler_text":"","visibility":"public","language":"en","uri":"http://127.0.0.1/users/user/statuses/110950643705404353","url":"http://127.0.0.1/@user/110950643705404353","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":null,"favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"\u003cp\u003eJust + a toot, with \u003ca href=\"http://127.0.0.1/tags/tags\" class=\"mention hashtag\" + rel=\"tag\"\u003e#\u003cspan\u003etags\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e","filtered":[],"reblog":null,"application":{"name":"Foo","website":"http://localhost"},"account":{"id":"110945794030490080","username":"user","acct":"user","display_name":"","locked":false,"bot":false,"discoverable":null,"group":false,"created_at":"2023-08-24T00:00:00.000Z","note":"","url":"http://127.0.0.1/@user","avatar":"http://127.0.0.1/avatars/original/missing.png","avatar_static":"http://127.0.0.1/avatars/original/missing.png","header":"http://127.0.0.1/headers/original/missing.png","header_static":"http://127.0.0.1/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":17,"last_status_at":"2023-08-25","noindex":false,"emojis":[],"roles":[{"id":"3","name":"Owner","color":""}],"fields":[]},"media_attachments":[],"mentions":[],"tags":[{"name":"tags","url":"http://127.0.0.1/tags/tags"}],"emojis":[],"card":null,"poll":null}' + headers: + Cache-Control: + - private, no-store + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"b86aa941a04786a67abb9ef111e9cf89" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '286' + X-RateLimit-Reset: + - '2023-08-25T14:10:00.630715Z' + X-Request-Id: + - e8965370-3c1f-4d9e-9427-03fe2e657422 + X-Runtime: + - '0.125905' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/app/cassettes/TestAppStatusPost.test_post_spoiler_text.yaml b/tests/app/cassettes/TestAppStatusPost.test_post_spoiler_text.yaml new file mode 100644 index 0000000..d2360b4 --- /dev/null +++ b/tests/app/cassettes/TestAppStatusPost.test_post_spoiler_text.yaml @@ -0,0 +1,123 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + User-Agent: + - collective.mastodon + method: GET + uri: http://localhost/api/v1/instance/ + response: + body: + string: '{"uri":"localhost:3000","title":"Mastodon","short_description":"","description":"","email":"","version":"4.1.6","urls":{"streaming_api":"ws://localhost:4000"},"stats":{"user_count":1,"status_count":20,"domain_count":0},"thumbnail":"http://127.0.0.1/packs/media/images/preview-6399aebd96ccf025654e2977454f168f.png","languages":["en"],"registrations":true,"approval_required":false,"invites_enabled":false,"configuration":{"accounts":{"max_featured_tags":10},"statuses":{"max_characters":500,"max_media_attachments":4,"characters_reserved_per_url":23},"media_attachments":{"supported_mime_types":["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":2304000},"polls":{"max_options":4,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"contact_account":null,"rules":[]}' + headers: + Cache-Control: + - max-age=180, public + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 25 Aug 2023 15:17:12 GMT + ETag: + - W/"1570e6866e69f9c3c16a8214834c902b" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '289' + X-RateLimit-Reset: + - '2023-08-25T15:20:00.363012Z' + X-Request-Id: + - 12f9d01c-5814-45f6-b886-99cd49edc2e0 + X-Runtime: + - '0.124719' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: status=Just+a+toot%2C+with+%23tags&visibility=public&spoiler_text=Sketch+a+Day&language=en + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M + Connection: + - keep-alive + Content-Length: + - '90' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - collective.mastodon + method: POST + uri: http://localhost/api/v1/statuses + response: + body: + string: '{"id":"110950916590999211","created_at":"2023-08-25T15:17:12.570Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":true,"spoiler_text":"Sketch + a Day","visibility":"public","language":"en","uri":"http://127.0.0.1/users/user/statuses/110950916590999211","url":"http://127.0.0.1/@user/110950916590999211","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":null,"favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"\u003cp\u003eJust + a toot, with \u003ca href=\"http://127.0.0.1/tags/tags\" class=\"mention hashtag\" + rel=\"tag\"\u003e#\u003cspan\u003etags\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e","filtered":[],"reblog":null,"application":{"name":"Foo","website":"http://localhost"},"account":{"id":"110945794030490080","username":"user","acct":"user","display_name":"","locked":false,"bot":false,"discoverable":null,"group":false,"created_at":"2023-08-24T00:00:00.000Z","note":"","url":"http://127.0.0.1/@user","avatar":"http://127.0.0.1/avatars/original/missing.png","avatar_static":"http://127.0.0.1/avatars/original/missing.png","header":"http://127.0.0.1/headers/original/missing.png","header_static":"http://127.0.0.1/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":21,"last_status_at":"2023-08-25","noindex":false,"emojis":[],"roles":[{"id":"3","name":"Owner","color":""}],"fields":[]},"media_attachments":[],"mentions":[],"tags":[{"name":"tags","url":"http://127.0.0.1/tags/tags"}],"emojis":[],"card":null,"poll":null}' + headers: + Cache-Control: + - private, no-store + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"f02c36b713640b471be9bfd0e39d5e48" + Permissions-Policy: + - interest-cohort=() + Referrer-Policy: + - same-origin + Server: + - Mastodon + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '288' + X-RateLimit-Reset: + - '2023-08-25T15:20:00.525508Z' + X-Request-Id: + - bdfa7f8e-2fad-4f5d-a854-1147c2d86b51 + X-Runtime: + - '0.224874' + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/app/test_app.py b/tests/app/test_app.py new file mode 100644 index 0000000..a11975c --- /dev/null +++ b/tests/app/test_app.py @@ -0,0 +1,117 @@ +from collective.mastodon.app import MastodonApp +from collective.mastodon.interfaces import IMastodonApp +from collective.mastodon.utils import media_from_content +from datetime import datetime +from plone import api +from zope.component import getAllUtilitiesRegisteredFor +from zope.component import getUtility + +import pytest +import pytz + + +DEFAULT_APP = "localhost-admin" + + +class TestAppDiscovery: + @pytest.fixture(autouse=True) + def _init(self, app): + self.zope_app = app + + def test_all_apps(self): + all_apps = getAllUtilitiesRegisteredFor(IMastodonApp) + assert len(all_apps) == 2 + + @pytest.mark.parametrize( + "name,instance,user", + [ + ("localhost-admin", "http://localhost", "admin"), + ("mastodon.localhost-plone", "http://mastodon.localhost", "plone"), + ], + ) + def test_app_is_registered(self, name: str, instance: str, user: str): + app = getUtility(IMastodonApp, name=name) + assert isinstance(app, MastodonApp) + assert app.name == name + assert app.instance == instance + assert app.user == user + + +class TestAppMethods: + app: MastodonApp + + @pytest.fixture(autouse=True) + def _init(self, portal): + self.portal = portal + self.app = getUtility(IMastodonApp, name=DEFAULT_APP) + + +class TestAppStatusPost(TestAppMethods): + @pytest.mark.vcr() + def test_post(self, post_payload): + payload = post_payload() + response = self.app._status_post(**payload) + assert isinstance(response, dict) + assert "id" in response + assert response["language"] == "en" + assert response["visibility"] == "public" + assert response["sensitive"] is False + assert "Just a toot" in response["content"] + + @pytest.mark.vcr() + def test_post_private(self, post_payload): + payload = post_payload(visibility="private") + response = self.app._status_post(**payload) + assert response["visibility"] == "private" + + @pytest.mark.vcr() + def test_post_sensitive(self, post_payload): + payload = post_payload(sensitive=True) + response = self.app._status_post(**payload) + assert response["sensitive"] is True + + @pytest.mark.vcr() + def test_post_language(self, post_payload): + payload = post_payload(language="pt") + response = self.app._status_post(**payload) + assert response["language"] == "pt" + + @pytest.mark.vcr() + def test_post_spoiler_text(self, post_payload): + payload = post_payload(spoiler_text="Sketch a Day") + response = self.app._status_post(**payload) + assert response["spoiler_text"] == "Sketch a Day" + + @pytest.mark.vcr() + def test_post_scheduled_at(self, post_payload): + future = datetime(4000, 1, 1, 12, 13, 14, 0, pytz.timezone("Etc/GMT+2")) + payload = post_payload(scheduled_at=future) + response = self.app._status_post(**payload) + scheduled_at = response["scheduled_at"] + assert scheduled_at.year == future.year + assert scheduled_at.month == future.month + assert scheduled_at.day == future.day + assert response["params"]["text"] == payload["status"] + + @pytest.mark.vcr(match_on=["path"]) + def test_post_media(self, post_payload): + content = api.content.get("/an-image") + media_list = [media_from_content(content)] + payload = post_payload(media_list=media_list) + response = self.app._status_post(**payload) + media_attachments = response["media_attachments"] + assert isinstance(media_attachments, list) + assert len(media_attachments) == 1 + assert media_attachments[0]["type"] == "image" + assert media_attachments[0]["meta"]["original"]["width"] == 1 + assert media_attachments[0]["meta"]["original"]["height"] == 1 + + +class TestAppScheduledStatuses(TestAppMethods): + @pytest.mark.vcr() + def test_scheduled_posts(self): + response = self.app.scheduled_statuses() + assert isinstance(response, list) + scheduled_post = response[0] + assert "id" in scheduled_post + assert "scheduled_at" in scheduled_post diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..47485b8 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,218 @@ +from base64 import b64decode +from collections import defaultdict +from collective.mastodon.testing import INTEGRATION_TESTING +from DateTime import DateTime +from plone import api +from plone.app.multilingual.interfaces import ITranslationManager +from plone.namedfile import NamedBlobImage +from pytest_plone import fixtures_factory +from typing import List +from zope.component.hooks import setSite + +import pytest + + +pytest_plugins = ["pytest_plone"] + +globals().update(fixtures_factory(((INTEGRATION_TESTING, "integration"),))) + +APPS = [ + { + "name": "localhost-admin", + "instance": "http://localhost", + "token": "jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M", + "user": "admin", + }, + { + "name": "mastodon.localhost-plone", + "instance": "http://mastodon.localhost", + "token": "jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M", + "user": "plone", + }, +] + + +@pytest.fixture +def mock_settings_mastodon_apps(mocker): + mocker.patch("collective.mastodon.settings.get_mastodon_apps", return_value=APPS) + + +@pytest.fixture(scope="module") +def vcr_config(): + return dict( + match_on=["method", "path", "query", "body"], decode_compressed_response=True + ) + + +@pytest.fixture +def contents() -> List: + """Content to be created.""" + future_effective_date = DateTime() + 2 # Two days in the future + past_effective_date = DateTime() - 2 # Two days in the past + return [ + { + "_container": "", + "type": "Image", + "id": "an-image", + "title": "A Random Image", + "description": "With some details", + "language": "de", + "subject": ["Image", "Plone"], + "_image": b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjCDO+/R8ABKsCZD++CcMAAAAASUVORK5CYII=", # noQA + }, + { + "_container": "", + "type": "Document", + "id": "future", + "title": "Future", + "description": "A document in the future", + "effective_date": future_effective_date, + "subject": ["Future", "Mastodon"], + }, + { + "_container": "", + "type": "Document", + "id": "past", + "title": "Past", + "description": "A document in the past", + "effective_date": past_effective_date, + "subject": ["Past", "Mastodon"], + }, + { + "_container": "", + "type": "Document", + "id": "document_preview", + "title": "Illustrated document", + "description": "A document with a preview image", + "effective_date": past_effective_date, + "subject": ["Preview", "Mastodon"], + "preview_caption_link": "An image", + "_preview_image_link": "/an-image", + }, + { + "_container": "", + "type": "News Item", + "id": "mynews", + "title": "A News Item", + "description": "A News Item about Mastodon", + "subject": ["News", "Mastodon"], + "_image": b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjCDO+/R8ABKsCZD++CcMAAAAASUVORK5CYII=", # noQA + }, + ] + + +@pytest.fixture +def create_contents(contents): + """Helper fixture to create initial content.""" + + def func(portal) -> dict: + ids = defaultdict(list) + for item in contents: + container_path = item["_container"] + container = portal.unrestrictedTraverse(container_path) + payload = {"container": container, "language": "en"} + if "_image" in item: + payload["image"] = NamedBlobImage(b64decode(item["_image"])) + for key, value in item.items(): + if key.startswith("_"): + continue + payload[key] = value + content = api.content.create(**payload) + content.language = payload["language"] + # Relation via preview_image_link + if "_preview_image_link" in item: + target = api.content.get(item["_preview_image_link"]) + api.relation.create(content, target, "preview_image_link") + # Set translation + if "_translation_of" in item: + source = portal.unrestrictedTraverse(item["_translation_of"]) + ITranslationManager(source).register_translation( + content.language, content + ) + # Transition items + if "_transitions" in item: + transitions = item["_transitions"] + for transition in transitions: + api.content.transition(content, transition=transition) + content.reindexObject() + ids[container_path].append(content.getId()) + return ids + + return func + + +@pytest.fixture() +def update_behaviors(get_fti): + def update_behaviors( + type_name: str, add: List[str] = None, remove: List[str] = None + ): + """Add a behavior to a content type.""" + from plone.dexterity.schema import invalidate_cache + + fti = get_fti(type_name) + current = list(fti.behaviors) + behaviors = [beh for beh in current if beh not in remove] + add + fti.behaviors = tuple(behaviors) + invalidate_cache(fti) + return fti.behaviors + + return update_behaviors + + +@pytest.fixture +def app(integration, mock_settings_mastodon_apps): + from collective.mastodon import logger + from collective.mastodon.startup import register_apps + + register_apps(logger) + return integration["app"] + + +@pytest.fixture() +def portal(app, create_contents, update_behaviors): + """Plone portal with additional content.""" + portal = app["plone"] + setSite(portal) + with api.env.adopt_roles(["Manager"]): + update_behaviors( + type_name="Document", + remove=["volto.preview_image"], + add=["volto.preview_image_link"], + ) + content_ids = create_contents(portal) + + yield portal + with api.env.adopt_roles(["Manager"]): + containers = sorted([path for path in content_ids.keys()], reverse=True) + for container_path in containers: + container = portal.unrestrictedTraverse(container_path) + container.manage_delObjects(content_ids[container_path]) + + +@pytest.fixture +def post_payload(): + def post_payload(**kwargs): + payload = { + "status": "Just a toot, with #tags", + "media_list": [], + "sensitive": False, + "visibility": "public", + "spoiler_text": "", + "language": "en", + } + # Override default values + if kwargs: + payload.update(kwargs) + return payload + + return post_payload + + +@pytest.fixture +def wait_for(): + def func(thread): + if not thread: + return + thread.join() + + return func diff --git a/tests/interpolators/test_tags.py b/tests/interpolators/test_tags.py new file mode 100644 index 0000000..08e3b20 --- /dev/null +++ b/tests/interpolators/test_tags.py @@ -0,0 +1,26 @@ +from plone import api +from plone.stringinterp.interfaces import IStringInterpolator + +import pytest + + +class TestInterpolatorsTags: + @pytest.fixture(autouse=True) + def _init(self, portal): + self.portal = portal + + @pytest.mark.parametrize( + "path,expected", + [ + ("/an-image", "#Image #Plone"), + ("/document_preview", "#Preview #Mastodon"), + ("/future", "#Future #Mastodon"), + ("/past", "#Past #Mastodon"), + ("/mynews", "#News #Mastodon"), + ], + ) + def test_tags(self, path: str, expected: str): + content = api.content.get(path=path) + interpolator = IStringInterpolator(content) + result = interpolator("${tags}") + assert result == expected diff --git a/tests/registry/test_registry.py b/tests/registry/test_registry.py new file mode 100644 index 0000000..bf83e3b --- /dev/null +++ b/tests/registry/test_registry.py @@ -0,0 +1,26 @@ +from collective.mastodon.interfaces import IMastodonRegistry +from collective.mastodon.registry import MastodonRegistry +from zope.component import getUtility + +import pytest + + +class TestRegistry: + @pytest.fixture(autouse=True) + def _init(self, app): + self.zope_app = app + + def test_registry_discovery(self): + registry = getUtility(IMastodonRegistry) + assert isinstance(registry, MastodonRegistry) + + def test_registry_get_apps(self): + registry = getUtility(IMastodonRegistry) + apps = registry.get_apps() + assert len(apps) == 2 + + def test_registry_get_app(self): + registry = getUtility(IMastodonRegistry) + name = "localhost-admin" + app = registry.get_app(name=name) + assert app.name == name diff --git a/tests/startup/conftest.py b/tests/startup/conftest.py new file mode 100644 index 0000000..06d1cf1 --- /dev/null +++ b/tests/startup/conftest.py @@ -0,0 +1,29 @@ +import pytest + + +APPS = [ + # Missing user attribute + { + "name": "localhost-admin", + "instance": "http://localhost", + "token": "jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M", + }, + # Missing token attribute + { + "name": "mastodon.localhost-plone", + "instance": "http://mastodon.localhost", + "user": "plone", + }, + # Good entry + { + "name": "mastodon.localhost-foo", + "instance": "http://mastodon.localhost", + "token": "jutbgrhNDS1EvUvpoHD0ox4a7obSCT9_IpliStv799M", + "user": "foo", + }, +] + + +@pytest.fixture +def mock_settings_mastodon_apps(mocker): + mocker.patch("collective.mastodon.settings.get_mastodon_apps", return_value=APPS) diff --git a/tests/startup/test_register_apps.py b/tests/startup/test_register_apps.py new file mode 100644 index 0000000..d2c8e67 --- /dev/null +++ b/tests/startup/test_register_apps.py @@ -0,0 +1,16 @@ +from collective.mastodon.interfaces import IMastodonApp +from zope.component import getAllUtilitiesRegisteredFor + +import pytest + + +class TestRegisterApps: + @pytest.fixture(autouse=True) + def _init(self, app): + self.zope_app = app + + def test_register_apps_result(self): + apps = getAllUtilitiesRegisteredFor(IMastodonApp) + assert len(apps) == 1 + assert apps[0].name == "mastodon.localhost-foo" + assert apps[0].user == "foo" diff --git a/tests/utils/test_content.py b/tests/utils/test_content.py new file mode 100644 index 0000000..b68a34f --- /dev/null +++ b/tests/utils/test_content.py @@ -0,0 +1,106 @@ +from collective.mastodon.interfaces import MastodonMedia +from collective.mastodon.utils import content +from datetime import datetime +from datetime import timedelta +from DateTime import DateTime +from plone import api + +import pytest + + +class TestUtilsContentScheduleAt: + @property + def func(self): + return content._schedule_at + + def test_schedule_at_zope_datetime_future(self): + value = DateTime() + 1 + result = self.func(value) + assert isinstance(result, datetime) + + def test_schedule_at_zope_datetime_past(self): + value = DateTime() - 1 + result = self.func(value) + assert result is None + + def test_schedule_at_stdlib_datetime_future(self): + value = datetime.now() + timedelta(days=1) + result = self.func(value) + assert isinstance(result, datetime) + + def test_schedule_at_stdlib_datetime_past(self): + value = datetime.now() - timedelta(days=1) + result = self.func(value) + assert result is None + + +class TestUtilsContentScheduleDate: + @property + def func(self): + return content.schedule_date + + @pytest.fixture(autouse=True) + def _init(self, portal): + self.portal = portal + + @pytest.mark.parametrize( + "path,expected", + [ + ("/an-image", False), + ("/document_preview", False), + ("/future", True), + ("/past", False), + ("/mynews", False), + ], + ) + def test_schedule_date(self, path: str, expected: bool): + content = api.content.get(path=path) + result = self.func(content) + if expected: + assert isinstance(result, datetime) + else: + assert result is None + + +class TestUtilsContentMediaFromContent: + @property + def func(self): + return content.media_from_content + + @pytest.fixture(autouse=True) + def _init(self, portal): + self.portal = portal + + @pytest.mark.parametrize( + "path,expected", + [ + ("/an-image", True), + ("/document_preview", True), + ("/future", False), + ("/past", False), + ("/mynews", True), + ], + ) + def test_media_from_content(self, path: str, expected: bool): + content = api.content.get(path=path) + result = self.func(content) + if not expected: + assert result is None + else: + assert isinstance(result, MastodonMedia) + + @pytest.mark.parametrize( + "path,expected", + [ + # Description + ("/an-image", "With some details"), + # preview_caption_link + ("/document_preview", "An image"), + # No image_caption, fallback to title + ("/mynews", "A News Item"), + ], + ) + def test_media_from_content_description(self, path: str, expected: str): + content = api.content.get(path=path) + result = self.func(content) + assert result.description == expected diff --git a/tests/utils/test_tools.py b/tests/utils/test_tools.py new file mode 100644 index 0000000..fc8a0d0 --- /dev/null +++ b/tests/utils/test_tools.py @@ -0,0 +1,18 @@ +from collective.mastodon.utils import tools + +import pytest + + +class TestUtilsTools: + @pytest.mark.parametrize( + "name,instance,user", + [ + ("localhost-admin", "http://localhost", "admin"), + ("mastodon.localhost-plone", "http://mastodon.localhost", "plone"), + ], + ) + def test_mastodon_username(self, app, name: str, instance: str, user: str): + func = tools.get_app + app = func(name) + assert app.instance == instance + assert app.user == user diff --git a/tests/utils/test_username.py b/tests/utils/test_username.py new file mode 100644 index 0000000..afb513c --- /dev/null +++ b/tests/utils/test_username.py @@ -0,0 +1,18 @@ +from collective.mastodon.utils import username + +import pytest + + +class TestUtilsUsername: + @pytest.mark.parametrize( + "instance,user,expected", + [ + ("http://localhost", "user", "@user@localhost"), + ("http://mastodon.localhost", "user", "@user@mastodon.localhost"), + ("https://pynews.com.br", "ericof", "@ericof@pynews.com.br"), + ("https://fosstodon.org", "ericof", "@ericof@fosstodon.org"), + ], + ) + def test_mastodon_username(self, instance: str, user: str, expected: str): + func = username.mastodon_username + assert func(instance, user) == expected diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..d271a5a --- /dev/null +++ b/tox.ini @@ -0,0 +1,212 @@ +# Generated from: +# https://github.com/plone/meta/tree/master/config/default +# See the inline comments on how to expand/tweak this configuration file +[tox] +# We need 4.4.0 for constrain_package_deps. +min_version = 4.4.0 +envlist = + lint + test + dependencies + + +## +# Add extra configuration options in .meta.toml: +# [tox] +# envlist_lines = """ +# my_other_environment +# """ +# config_lines = """ +# my_extra_top_level_tox_configuration_lines +# """ +## + +[testenv] +skip_install = true +allowlist_externals = + echo + false +# Make sure typos like `tox -e formaat` are caught instead of silently doing nothing. +# See https://github.com/tox-dev/tox/issues/2858. +commands = + echo "Unrecognized environment name {envname}" + false + +[testenv:init] +description = Prepare environment +skip_install = true +deps = + mxdev +commands = + mxdev -c mx.ini + echo "Initial setup for mxdev" + + +[testenv:format] +description = automatically reformat code +skip_install = true +deps = + pre-commit +commands = + pre-commit run -a pyupgrade + pre-commit run -a isort + pre-commit run -a black + pre-commit run -a zpretty + +[testenv:lint] +description = run linters that will help improve the code style +skip_install = true +deps = + pre-commit +commands = + pre-commit run -a + +[testenv:dependencies] +description = check if the package defines all its dependencies +skip_install = true +deps = + build + z3c.dependencychecker==2.11 +commands = + python -m build --sdist --no-isolation + dependencychecker + +[testenv:dependencies-graph] +description = generate a graph out of the dependencies of the package +skip_install = false +allowlist_externals = + sh +deps = + pipdeptree==2.5.1 + graphviz # optional dependency of pipdeptree +commands = + sh -c 'pipdeptree --exclude setuptools,wheel,pipdeptree,zope.interface,zope.component --graph-output svg > dependencies.svg' + +[testenv:test] +description = run the distribution tests +use_develop = true +skip_install = false +constrain_package_deps = false +set_env = + ROBOT_BROWSER=headlesschrome + +## +# Specify extra test environment variables in .meta.toml: +# [tox] +# test_environment_variables = """ +# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/ +# """ +# +# Set constrain_package_deps .meta.toml: +# [tox] +# constrain_package_deps = false +## +deps = + pytest-plone + pytest + -c constraints-mxdev.txt + + +## +# Specify additional deps in .meta.toml: +# [tox] +# test_deps_additional = """ +# -esources/plonegovbr.portal_base[test] +# """ +# +# Specify a custom constraints file in .meta.toml: +# [tox] +# constraints_file = "https://my-server.com/constraints.txt" +## +commands = + pytest --disable-warnings {posargs} {toxinidir}/tests +extras = + test + + +[testenv:coverage] +description = get a test coverage report +use_develop = true +skip_install = false +constrain_package_deps = false +set_env = + ROBOT_BROWSER=headlesschrome + +## +# Specify extra test environment variables in .meta.toml: +# [tox] +# test_environment_variables = """ +# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/ +# """ +# +# Set constrain_package_deps .meta.toml: +# [tox] +# constrain_package_deps = "false" +## +deps = + pytest-plone + pytest + coverage + -c constraints-mxdev.txt + +commands = + coverage run --source collective.mastodon -m pytest {posargs} --disable-warnings {toxinidir}/tests + coverage report -m --format markdown + coverage xml +extras = + test + + +[testenv:release-check] +description = ensure that the distribution is ready to release +skip_install = true +deps = + twine + build + towncrier + -c constraints-mxdev.txt + +commands = + # fake version to not have to install the package + # we build the change log as news entries might break + # the README that is displayed on PyPI + towncrier build --version=100.0.0 --yes + python -m build --sdist --no-isolation + twine check dist/* + +[testenv:circular] +description = ensure there are no cyclic dependencies +use_develop = true +skip_install = false +set_env = + +## +# Specify extra test environment variables in .meta.toml: +# [tox] +# test_environment_variables = """ +# PIP_EXTRA_INDEX_URL=https://my-pypi.my-server.com/ +# """ +## +allowlist_externals = + sh +deps = + pipdeptree + pipforester + -c constraints-mxdev.txt + +commands = + # Generate the full dependency tree + sh -c 'pipdeptree -j > forest.json' + # Generate a DOT graph with the circular dependencies, if any + pipforester -i forest.json -o forest.dot --cycles + # Report if there are any circular dependencies, i.e. error if there are any + pipforester -i forest.json --check-cycles -o /dev/null + + +## +# Add extra configuration options in .meta.toml: +# [tox] +# extra_lines = """ +# _your own configuration lines_ +# """ +##