├── .gitignore
├── .travis.yml
├── CHANGES.txt
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── annotran.egg-info
├── PKG-INFO
├── SOURCES.txt
├── dependency_links.txt
├── entry_points.txt
├── not-zip-safe
├── requires.txt
└── top_level.txt
├── annotran
├── __init__.py
├── accounts
│ ├── __init__.py
│ └── views.py
├── admin.py
├── api
│ ├── __init__.py
│ └── search
│ │ ├── __init__.py
│ │ ├── core.py
│ │ └── query.py
├── app.py
├── assets.yaml
├── client.py
├── conftest.py
├── groups
│ ├── __init__.py
│ └── views.py
├── help
│ ├── __init__.py
│ └── views.py
├── languages
│ ├── __init__.py
│ ├── models.py
│ ├── schemas.py
│ ├── test
│ │ ├── __init__.py
│ │ ├── models_test.py
│ │ └── views_test.py
│ └── views.py
├── mailer.py
├── pages
│ ├── __init__.py
│ ├── models.py
│ ├── test
│ │ ├── __init__.py
│ │ ├── models_test.py
│ │ └── views_test.py
│ └── views.py
├── reports
│ ├── __init__.py
│ ├── models.py
│ ├── test
│ │ ├── __init__.py
│ │ ├── models_test.py
│ │ └── views_test.py
│ └── views.py
├── resources.py
├── session.py
├── static
│ ├── images
│ │ ├── home.png
│ │ ├── home.xcf
│ │ └── stars.png
│ └── scripts
│ │ ├── annotator
│ │ ├── guest.coffee
│ │ ├── host.coffee
│ │ ├── main.js
│ │ ├── plugin
│ │ │ ├── cssmodify.coffee
│ │ │ ├── sentenceselection.coffee
│ │ │ ├── substitution.coffee
│ │ │ └── toolbar.coffee
│ │ └── sidebar.coffee
│ │ ├── app-controller.coffee
│ │ ├── apps.js
│ │ ├── directive
│ │ ├── annotation.js
│ │ ├── language-list.js
│ │ ├── language-service.js
│ │ ├── publish-annotation-btn.js
│ │ ├── share-dialog.coffee
│ │ ├── sidebar-tutorial.js
│ │ ├── signin-control.js
│ │ ├── thread.coffee
│ │ ├── top-bar.js
│ │ └── user-list.js
│ │ ├── events.js
│ │ ├── groups.js
│ │ ├── languages.js
│ │ ├── pages.js
│ │ ├── reports.js
│ │ ├── session.js
│ │ ├── translations.js
│ │ ├── vendor
│ │ ├── annotator.js
│ │ ├── css
│ │ │ └── annotator.css
│ │ ├── google.translate.lang.list.js
│ │ ├── img
│ │ │ ├── annotator-glyph-sprite.png
│ │ │ └── annotator-icon-sprite.png
│ │ └── plugins
│ │ │ └── tags.coffee
│ │ ├── votes.js
│ │ └── widget-controller.coffee
├── styles
│ └── annotran.css
├── templates
│ ├── 5xx.html.jinja2
│ ├── accounts
│ │ └── profile.html.jinja2
│ ├── admin
│ │ ├── index.html.jinja2
│ │ ├── reports.html.jinja2
│ │ └── translation.html.jinja2
│ ├── annotran_help.html.jinja2
│ ├── app.html.jinja2
│ ├── client
│ │ ├── annotation.html
│ │ ├── language_list.html
│ │ ├── publish_annotation_btn.html
│ │ ├── share_dialog.html
│ │ ├── sidebar_tutorial.html
│ │ ├── signin_control.html
│ │ ├── top_bar.html
│ │ └── user_list.html
│ ├── community-guidelines.html.jinja2
│ ├── groups
│ │ ├── about-groups.html.jinja2
│ │ └── share.html.jinja2
│ ├── home.html.jinja2
│ ├── includes
│ │ └── logo-header.html.jinja2
│ ├── layouts
│ │ ├── admin.html.jinja2
│ │ └── base.html.jinja2
│ ├── notfound.html.jinja2
│ ├── privacy-policy.html.jinja2
│ └── terms-of-service.html.jinja2
├── test
│ ├── __init__.py
│ └── admin_test.py
├── tests.py
├── translations
│ ├── __init__.py
│ ├── models.py
│ ├── test
│ │ ├── __init__.py
│ │ ├── models_test.py
│ │ └── views_test.py
│ └── views.py
├── util
│ ├── __init__.py
│ └── util.py
├── views.py
└── votes
│ ├── __init__.py
│ ├── models.py
│ ├── test
│ ├── __init__.py
│ ├── models_test.py
│ └── views_test.py
│ └── views.py
├── conf
├── development.ini
├── production.ini
└── test.ini
├── docs
├── QuickStart.odt
├── QuickStartGuide.pdf
└── images
│ ├── Adder.png
│ ├── Blanksidebar.png
│ ├── Bookmarklet.png
│ ├── Dropdownlanguage.png
│ ├── Rateandreport.png
│ ├── Selecteditmode.png
│ ├── Sidebarsignedin.png
│ ├── Substitution.png
│ ├── Userstranslations.png
│ ├── Viewmodeclicktochange.png
│ └── Writetranslation.png
├── package.json
├── requirements.txt
├── scripts
└── postcss-filter.js
├── setup.cfg
├── setup.py
└── setup.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Python template
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | env/
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *,cover
48 | .hypothesis/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # IPython Notebook
72 | .ipynb_checkpoints
73 |
74 | # pyenv
75 | .python-version
76 |
77 | # celery beat schedule file
78 | celerybeat-schedule
79 |
80 | # dotenv
81 | .env
82 |
83 | # virtualenv
84 | venv/
85 | ENV/
86 |
87 | # Spyder project settings
88 | .spyderproject
89 |
90 | # Rope project settings
91 | .ropeproject
92 | ### JetBrains template
93 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
94 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
95 |
96 | # User-specific stuff:
97 | .idea/workspace.xml
98 | .idea/tasks.xml
99 | .idea/dictionaries
100 | .idea/vcs.xml
101 | .idea/jsLibraryMappings.xml
102 |
103 | # Sensitive or high-churn files:
104 | .idea/dataSources.ids
105 | .idea/dataSources.xml
106 | .idea/dataSources.local.xml
107 | .idea/sqlDataSources.xml
108 | .idea/dynamic.xml
109 | .idea/uiDesigner.xml
110 |
111 | # Gradle:
112 | .idea/gradle.xml
113 | .idea/libraries
114 |
115 | # Mongo Explorer plugin:
116 | .idea/mongoSettings.xml
117 |
118 | ## File-based project format:
119 | *.iws
120 |
121 | ## Plugin-specific files:
122 |
123 | # IntelliJ
124 | /out/
125 |
126 | # mpeltonen/sbt-idea plugin
127 | .idea_modules/
128 |
129 | # JIRA plugin
130 | atlassian-ide-plugin.xml
131 |
132 | # Crashlytics plugin (for Android Studio and IntelliJ)
133 | com_crashlytics_export_strings.xml
134 | crashlytics.properties
135 | crashlytics-build.properties
136 | fabric.properties
137 | ### VirtualEnv template
138 | # Virtualenv
139 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
140 | .Python
141 | [Bb]in
142 | [Ii]nclude
143 | [Ll]ib
144 | [Ll]ib64
145 | [Ll]ocal
146 | pyvenv.cfg
147 | .venv
148 | pip-selfcheck.json
149 |
150 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # use Travis container build infrastructure
2 |
3 |
--------------------------------------------------------------------------------
/CHANGES.txt:
--------------------------------------------------------------------------------
1 | 0.0
2 | ---
3 |
4 | - Initial version
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Birkbeck College, University of London
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include annotran *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
2 | include annotran/assets.yaml
3 | include Makefile AUTHORS CHANGES LICENSE NOTICE
4 | include Dockerfile
5 | include gunicorn.conf.py
6 | include package.json
7 | include requirements.txt
8 | graft conf
9 | graft docs
10 | prune docs/_build
11 | graft annotran/migrations
12 | graft annotran/static
13 | prune annotran/static/.webassets-cache
14 | graft annotran/templates
15 | graft scripts
16 | global-exclude __pycache__
17 | global-exclude *.py[co]
18 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | default: deps
3 |
4 | deps: annotran.egg-info/.uptodate node_modules/.uptodate
5 |
6 | annotran.egg-info/.uptodate: setup.py requirements.txt
7 | pip install --use-wheel -e .[dev,testing,YAML]
8 | touch $@
9 |
10 | node_modules/.uptodate: package.json
11 | npm install
12 | touch $@
13 |
14 | clean:
15 | find . -type f -name "*.py[co]" -delete
16 | find . -type d -name "__pycache__" -delete
17 | rm -rf annotran/static/webassets-external
18 | rm -f annotran/static/scripts/vendor/*.min.js
19 | rm -f annotran/static/scripts/account.*js
20 | rm -f annotran/static/scripts/app.*js
21 | rm -f annotran/static/scripts/config.*js
22 | rm -f annotran/static/scripts/hypothesis.*js
23 | rm -f annotran/static/styles/*.css
24 | rm -f .coverage
25 | rm -f node_modules/.uptodate .eggs/.uptodate
26 |
27 | dev: deps
28 | @gunicorn --reload --paste conf/development.ini
29 |
30 | test: backend-test
31 |
32 | #backend-test: deps
33 | # @python setup.py test
34 |
35 | backend-test:
36 | py.test
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://landscape.io/github/birkbeckOLH/annotran/master) []() [](https://github.com/birkbeckOLH/annotran/blob/master/LICENSE)
2 |
3 | #annotran
4 |
5 | ## About
6 |
7 | Annotran is an extension to the hypothesis annotation framework that allows users to translate web pages themselves and to view other users' translations. It is developed at the [Open Library of Humanities](https://about.openlibhums.org) and was funded by the Andrew W. Mellon Foundation. A [public alpha version is now available](https://annotran.openlibhums.org) to use (but with no guarantee that the server will always be up or that translations will not be deleted).
8 |
9 | ## Quirks
10 |
11 | Annotran does not work well on extremely dynamic pages. If the web page that you are translating changes substantially, then it is likely that your translation will break.
12 |
13 | ## Development
14 |
15 | This project is built as an extension to the hypothesis annotation framework: https://hypothes.is/. If you would like to join us in development efforts, or wish to run your own server, you can set up your development environment in the following way. Please note these instructions for Ubuntu 14.04.
16 |
17 | Download the source code:
18 | ```
19 | git clone https://github.com/birkbeckOLH/annotran.git
20 | ```
21 | Download the hypothes.is version 0.8.14:
22 | ```
23 | git clone https://github.com/hypothesis/h.git
24 | cd h
25 | git reset --hard v0.8.14
26 | ```
27 | Install `h` by refering to its documentation on how to install it. Search for this document in project files: ../h/docs/hacking/install.rst. In addition to the Docker containers there, you will also need nsqd. The full set of container installs are:
28 |
29 | ```
30 | docker run -d --name postgres -p 5432:5432 postgres
31 | docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 nickstenning/elasticsearch-icu
32 | docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 --hostname rabbit rabbitmq:3-management
33 | docker run -d --name redis -p 6379:6379 redis
34 | docker run -d --name nsqd -p 4150:4150 -p 4151:4151 nsqio/nsq /nsqd
35 | ```
36 |
37 | If you want to be able to easily monitor the emails that annotran/hypothes.is sends in your development environment, then you may wish to use the following command:
38 |
39 | ```
40 | docker exec nsqd nsq_tail --topic email --nsqd-tcp-address localhost:4150
41 | ```
42 |
43 | This can be important as account sign-up confirmation links are sent by email.
44 |
45 | Create a Python virtual environment. Refer to the documentation on how to create virtual environments: http://docs.python-guide.org/en/latest/dev/virtualenvs/
46 |
47 | Run the following commands to install hypothes.is into your virtual environment:
48 | ```
49 | cd ..
50 | cd annotran
51 | sudo apt-get install -y --no-install-recommends ruby-compass build-essential git libevent-dev libffi-dev libfontconfig libpq-dev python-dev python-pip python-virtualenv nodejs npm
52 | pip install -r requirements.txt
53 | pip install setuptools --upgrade
54 | sudo ln -s /usr/bin/nodejs /usr/bin/node
55 | make dev
56 | ```
57 |
58 | ###Steps performed to extend and overwrite hypothes.is
59 |
60 | 1. Boot system. Since the annotran package has been installed into the same Python environment as the hypothes.is application, it is possibile to start the hypothes.is application from annotran. To do so, place a version of h's pyramid configuration inside annotran.
61 |
62 | 2. When extending Pyramid application (see documentation on how to do that: http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/extending.html), it is necessary to override views, routes and static assets (http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/assets.html#assets-chapter). To extend hypothes.is UI code, there are following steps performed:
63 | - There is assets.yaml file in annotran that is a copy of the same file from hypothes.is. Paths for assets that are overwritten in annotran are appropriately updated within this file.
64 | - Assets are overwritten by invoking config.override_asset(..) method.
65 | - Javascript is overwritten using Angular dependency injection. h/static/scripts/app.coffee is required from the main module within the annotran.
66 |
67 | 3. In app.py we replace a set of hypothesis functions using Python monkey patching. The replacement functions are in replacements.py.
68 |
69 | 4. To override Angular directives, add the directive file in static/scripts/directive and then edit apps.js to add an app decorator that selects the override directive.
70 |
71 | ###Understanding the different components and languages used by hypothes.is and overridden by annotran
72 | Hypothesis uses multiple technologies:
73 |
74 | * Pyramid for URL routing and setting handling
75 | * The Python files (.py) in the project are part of the Pyramid framework's handling
76 | * SQLAlchemy is used by the python files to read from and write to a PostgresSQL database and can be accessed by the python components
77 | * An elasticsearch instance is used to store annotations themselves and can be accessed by the python components
78 | * Annotator.js provides the core annotation functions
79 | * A set of coffeescript and javascript files (.coffee and .js) extend Annotator.js and provide the hypothes.is sidebar and plugins to the document we are annotating
80 | * Some javascript files make calls to the Pyramid framework's server-side python scripts (see above) and load database values in the client (see session.js for example)
81 | * An event bubbling framework exists within the javascript files that can be fired and extended
82 |
83 | ##How to contribute
84 |
85 | Tasks under development are available to investigate under the issues. You can also join in the discussion over there.
86 |
87 |
--------------------------------------------------------------------------------
/annotran.egg-info/SOURCES.txt:
--------------------------------------------------------------------------------
1 | CHANGES.txt
2 | LICENSE
3 | MANIFEST.in
4 | Makefile
5 | package.json
6 | production.ini
7 | requirements.txt
8 | setup.cfg
9 | setup.py
10 | setup.txt
11 | annotran/__init__.py
12 | annotran/admin.py
13 | annotran/app.py
14 | annotran/assets.yaml
15 | annotran/client.py
16 | annotran/conftest.py
17 | annotran/resources.py
18 | annotran/session.py
19 | annotran/tests.py
20 | annotran/views.py
21 | annotran.egg-info/.uptodate
22 | annotran.egg-info/PKG-INFO
23 | annotran.egg-info/SOURCES.txt
24 | annotran.egg-info/dependency_links.txt
25 | annotran.egg-info/not-zip-safe
26 | annotran.egg-info/requires.txt
27 | annotran.egg-info/top_level.txt
28 | annotran/accounts/__init__.py
29 | annotran/accounts/views.py
30 | annotran/api/__init__.py
31 | annotran/api/search/__init__.py
32 | annotran/api/search/core.py
33 | annotran/api/search/query.py
34 | annotran/groups/__init__.py
35 | annotran/groups/views.py
36 | annotran/help/__init__.py
37 | annotran/help/views.py
38 | annotran/languages/__init__.py
39 | annotran/languages/models.py
40 | annotran/languages/schemas.py
41 | annotran/languages/views.py
42 | annotran/pages/__init__.py
43 | annotran/pages/models.py
44 | annotran/pages/views.py
45 | annotran/reports/__init__.py
46 | annotran/reports/models.py
47 | annotran/reports/views.py
48 | annotran/static/images/home.png
49 | annotran/static/images/home.xcf
50 | annotran/static/images/stars.png
51 | annotran/static/scripts/app-controller.coffee
52 | annotran/static/scripts/apps.js
53 | annotran/static/scripts/events.js
54 | annotran/static/scripts/groups.js
55 | annotran/static/scripts/languages.js
56 | annotran/static/scripts/pages.js
57 | annotran/static/scripts/reports.js
58 | annotran/static/scripts/session.js
59 | annotran/static/scripts/votes.js
60 | annotran/static/scripts/widget-controller.coffee
61 | annotran/static/scripts/annotator/guest.coffee
62 | annotran/static/scripts/annotator/host.coffee
63 | annotran/static/scripts/annotator/main.js
64 | annotran/static/scripts/annotator/sidebar.coffee
65 | annotran/static/scripts/annotator/plugin/cssmodify.coffee
66 | annotran/static/scripts/annotator/plugin/sentenceselection.coffee
67 | annotran/static/scripts/annotator/plugin/substitution.coffee
68 | annotran/static/scripts/annotator/plugin/toolbar.coffee
69 | annotran/static/scripts/directive/annotation.js
70 | annotran/static/scripts/directive/language-list.js
71 | annotran/static/scripts/directive/language-service.js
72 | annotran/static/scripts/directive/share-dialog.coffee
73 | annotran/static/scripts/directive/signin-control.js
74 | annotran/static/scripts/directive/thread.coffee
75 | annotran/static/scripts/directive/top-bar.js
76 | annotran/static/scripts/directive/user-list.js
77 | annotran/static/scripts/vendor/annotator.js
78 | annotran/static/scripts/vendor/google.translate.lang.list.js
79 | annotran/static/scripts/vendor/css/annotator.css
80 | annotran/static/scripts/vendor/img/annotator-glyph-sprite.png
81 | annotran/static/scripts/vendor/img/annotator-icon-sprite.png
82 | annotran/static/scripts/vendor/plugins/tags.coffee
83 | annotran/styles/annotran.css
84 | annotran/templates/5xx.html.jinja2
85 | annotran/templates/annotran_help.html.jinja2
86 | annotran/templates/app.html.jinja2
87 | annotran/templates/home.html.jinja2
88 | annotran/templates/notfound.html.jinja2
89 | annotran/templates/accounts/profile.html.jinja2
90 | annotran/templates/admin/index.html.jinja2
91 | annotran/templates/admin/reports.html.jinja2
92 | annotran/templates/admin/translation.html.jinja2
93 | annotran/templates/client/annotation.html
94 | annotran/templates/client/language_list.html
95 | annotran/templates/client/share_dialog.html
96 | annotran/templates/client/sidebar_tutorial.html
97 | annotran/templates/client/signin_control.html
98 | annotran/templates/client/top_bar.html
99 | annotran/templates/client/user_list.html
100 | annotran/templates/groups/about-groups.html.jinja2
101 | annotran/templates/groups/share.html.jinja2
102 | annotran/templates/includes/logo-header.html.jinja2
103 | annotran/templates/layouts/admin.html.jinja2
104 | annotran/util/__init__.py
105 | annotran/util/util.py
106 | annotran/votes/__init__.py
107 | annotran/votes/models.py
108 | annotran/votes/views.py
109 | conf/development.ini
110 | conf/test.ini
111 | scripts/postcss-filter.js
--------------------------------------------------------------------------------
/annotran.egg-info/dependency_links.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/annotran.egg-info/entry_points.txt:
--------------------------------------------------------------------------------
1 | [paste.app_factory]
2 | main = annotran.app:main
3 | [console_scripts]
4 | initialize_annotran_db = annotran.scripts.initializedb:main
5 |
--------------------------------------------------------------------------------
/annotran.egg-info/not-zip-safe:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/annotran.egg-info/requires.txt:
--------------------------------------------------------------------------------
1 | PyJWT>=1.0.0,<2.0.0
2 | SQLAlchemy>=0.8.0
3 | alembic>=0.7.0
4 | annotator>=0.14.2,<0.15
5 | blinker>=1.3,<1.4
6 | cryptacular>=1.4,<1.5
7 | cryptography>=0.7
8 | deform>=0.9,<1.0
9 | deform-jinja2>=0.5,<0.6
10 | elasticsearch>=1.1.0,<2.0.0
11 | gevent>=1.0.2,<1.1.0
12 | gnsq>=0.3.0,<0.4.0
13 | gunicorn>=19.2,<20
14 | itsdangerous>=0.24
15 | jsonpointer==1.0
16 | jsonschema>=2.5.1,<2.6
17 | pyramid>=1.5,<1.6
18 | psycogreen>=1.0
19 | psycopg2>=2.6.1
20 | pyramid_mailer>=0.13
21 | pyramid_tm>=0.7
22 | python-dateutil>=2.1
23 | python-slugify>=1.1.3,<1.2.0
24 | python-statsd>=1.7.0,<1.8.0
25 | webassets>=0.10,<0.11
26 | pyramid_webassets>=0.9,<1.0
27 | pyramid-jinja2>=2.3.3
28 | raven>=5.10.2,<5.11.0
29 | requests>=2.7.0
30 | unicodecsv>=0.14.1,<0.15
31 | ws4py>=0.3,<0.4
32 | zope.sqlalchemy>=0.7.6,<0.8.0
33 | repoze.sendmail<4.2
34 |
35 | [YAML]
36 | PyYAML
37 |
38 | [testing]
39 | mock>=1.3.0
40 | pytest>=2.5
41 | pytest-cov
42 | factory-boy
43 |
44 | [dev]
45 | pyramid_debugtoolbar>=2.1
46 | prospector[with_pyroma]
47 | pep257
48 | sphinxcontrib-httpdomain
49 | PyYAML
--------------------------------------------------------------------------------
/annotran.egg-info/top_level.txt:
--------------------------------------------------------------------------------
1 | annotran
2 |
--------------------------------------------------------------------------------
/annotran/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | def includeme(config):
4 | config.include('.views')
5 |
--------------------------------------------------------------------------------
/annotran/accounts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openlibhums/annotran/42678afaee6d4b57cfaddb402bc6f15b37fdd027/annotran/accounts/__init__.py
--------------------------------------------------------------------------------
/annotran/accounts/views.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | """
24 |
25 | import annotran
26 | import annotran.views
27 | import deform
28 |
29 | from h import i18n
30 | from pyramid.view import view_config
31 | from h.accounts import schemas
32 |
33 | _ = i18n.TranslationString
34 |
35 |
36 | def register_controller_init_patch(self, request):
37 | tos_link = ('' +
38 | _('Terms of Service') +
39 | '')
40 | cg_link = ('' +
41 | _('Community Guidelines') +
42 | '')
43 | privacy_link = ('' +
44 | _('Privacy Policy') +
45 | '')
46 | form_footer = _(
47 | 'You are agreeing to be bound by our {tos_link}, '
48 | '{cg_link} and {privacy_link}.').format(tos_link=tos_link, cg_link=cg_link, privacy_link=privacy_link)
49 |
50 | self.request = request
51 | self.schema = schemas.RegisterSchema().bind(request=self.request)
52 | self.form = deform.Form(self.schema,
53 | buttons=(_('Sign up'),),
54 | footer=form_footer)
55 |
56 |
57 | class ProfileController(object):
58 | def __init__(self):
59 | pass
60 |
61 | @staticmethod
62 | @view_config(request_method='GET')
63 | def profile_get(controller_instance):
64 | """
65 | Shows the user's profile
66 | :param controller_instance: an instance of the ProfileController
67 | :return: a context dictionary for the profile template
68 | """
69 | return {'email': controller_instance.request.authenticated_user.email,
70 | 'email_form': controller_instance.forms['email'].render(),
71 | 'password_form': controller_instance.forms['password'].render(),
72 | 'support_address': annotran.views.Shared.support_address}
73 |
74 |
75 | def auth_controller_init_patch(self, request):
76 | """
77 | Replace the constructor of the h's h.account.views AuthController class - in order to skip the stream loading that
78 | is not used in the annotran
79 | :param request: the current request
80 | :return: None
81 | """
82 | form_footer = '{text}'.format(
83 | href=request.route_path('forgot_password'),
84 | text=_('Forgot your password?'))
85 | self.request = request
86 | self.schema = schemas.LoginSchema().bind(request=self.request)
87 | self.form = deform.Form(self.schema,
88 | buttons=(_('Sign in'),),
89 | footer=form_footer)
90 | self.login_redirect = self.request.route_url('index')
91 | self.logout_redirect = self.request.route_url('index')
92 |
--------------------------------------------------------------------------------
/annotran/api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openlibhums/annotran/42678afaee6d4b57cfaddb402bc6f15b37fdd027/annotran/api/__init__.py
--------------------------------------------------------------------------------
/annotran/api/search/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openlibhums/annotran/42678afaee6d4b57cfaddb402bc6f15b37fdd027/annotran/api/search/__init__.py
--------------------------------------------------------------------------------
/annotran/api/search/core.py:
--------------------------------------------------------------------------------
1 | from h.api.search import query
2 | from h.api import nipsa
3 |
4 |
5 | def delete(request, params, private=True):
6 | """
7 | Delete with the given params and return the matching annotations.
8 |
9 | :param request: the request object
10 | :type request: pyramid.request.Request
11 |
12 | :param params: the search parameters
13 | :type params: dict-like
14 |
15 | :param private: whether or not to include private annotations in the search
16 | results
17 | :type private: bool
18 |
19 | :returns: A dict with keys:
20 | "rows" (the list of matching annotations, as dicts)
21 | "total" (the number of matching annotations, an int)
22 | "replies": (the list of all replies to the annotations in "rows", if
23 | separate_replies=True was passed)
24 | :rtype: dict
25 | """
26 | def make_builder():
27 | builder_output = query.Builder()
28 | builder_output.append_filter(query.AuthFilter(request, private=private))
29 | builder_output.append_filter(query.UriFilter())
30 | builder_output.append_filter(lambda _: nipsa.nipsa_filter(request.authenticated_userid))
31 | builder_output.append_filter(query.GroupFilter())
32 | builder_output.append_matcher(query.AnyMatcher())
33 | builder_output.append_matcher(query.TagsMatcher())
34 |
35 | return builder_output
36 |
37 | builder = make_builder()
38 |
39 | es = request.es
40 |
41 | # N.B.: this function _does_ take a sort argument because hypothesis's version of the function is monkey-patched
42 | # to point to the version in query.py while still retaining the rest of the class
43 | es.conn.delete_by_query(index=es.index, doc_type=es.t.annotation, body=builder.build(params, sort=False))
44 |
--------------------------------------------------------------------------------
/annotran/api/search/query.py:
--------------------------------------------------------------------------------
1 | from h.api.search import query as hq
2 |
3 |
4 | def build(self, params, sort=True):
5 | """
6 | Get the resulting query object from this query builder.
7 | :param self: an instance of a query object
8 | :param params: a set of parameters to pass to a query
9 | :param sort: whether or not to include sort and other keys that pertain only to a search query
10 | :return:
11 | """
12 | params = params.copy()
13 |
14 | p_from = hq.extract_offset(params)
15 | p_size = hq.extract_limit(params)
16 | p_sort = hq.extract_sort(params)
17 |
18 | filters = [f(params) for f in self.filters]
19 | matchers = [m(params) for m in self.matchers]
20 | filters = [f for f in filters if f is not None]
21 | matchers = [m for m in matchers if m is not None]
22 |
23 | # Remaining parameters are added as straightforward key-value matchers
24 | for key, value in params.items():
25 | matchers.append({"match": {key: value}})
26 |
27 | query = {"match_all": {}}
28 |
29 | if matchers:
30 | query = {"bool": {"must": matchers}}
31 |
32 | if filters:
33 | query = {
34 | "filtered": {
35 | "filter": {"and": filters},
36 | "query": query,
37 | }
38 | }
39 |
40 | ret = {
41 | "query": query,
42 | }
43 |
44 | if sort:
45 | ret["from"] = p_from
46 | ret["sort"] = p_sort
47 | ret["size"] = p_size
48 |
49 | return ret
50 |
--------------------------------------------------------------------------------
/annotran/conftest.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | """
24 |
25 | # -*- coding: utf-8 -*-
26 |
27 | import os
28 |
29 | import pytest
30 | from h import conftest
31 | from h.config import normalize_database_url
32 | from pyramid.paster import get_appsettings
33 |
34 |
35 | class DummyFeature(object):
36 | """
37 | A dummy feature flag looker-upper.
38 |
39 | Because we're probably testing all feature-flagged functionality, this
40 | feature client defaults every flag to *True*, which is the exact opposite
41 | of what happens outside of testing.
42 | """
43 |
44 | def __init__(self):
45 | self.flags = {}
46 |
47 |
48 | class DummySession(object):
49 | """
50 | A dummy database session.
51 | """
52 |
53 | def __init__(self):
54 | self.added = []
55 | self.deleted = []
56 | self.flushed = False
57 |
58 |
59 | @pytest.fixture(scope='session', autouse=True)
60 | def settings():
61 | """
62 | Default app settings (conf/test.ini).
63 | :return: a list of loaded settings
64 | """
65 | loaded_settings = get_appsettings('conf/test.ini')
66 |
67 | if 'TEST_DATABASE_URL' in os.environ:
68 | loaded_settings['sqlalchemy.url'] = normalize_database_url(os.environ['TEST_DATABASE_URL'])
69 |
70 | return loaded_settings
71 |
72 |
73 | DummySession.settings = settings
74 |
75 | setup_database = conftest.setup_database
76 | database_session = conftest.database_session
77 | config = conftest.config
78 | deform = conftest.deform
79 | authn_policy = conftest.authn_policy
80 | mailer = conftest.mailer
81 | notify = conftest.notify
82 | routes_mapper = conftest.routes_mapper
83 | _make_session = conftest._make_session
84 |
--------------------------------------------------------------------------------
/annotran/groups/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openlibhums/annotran/42678afaee6d4b57cfaddb402bc6f15b37fdd027/annotran/groups/__init__.py
--------------------------------------------------------------------------------
/annotran/groups/views.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | """
24 | # monkey patching of hypothesis methods
25 |
26 | # annotran's version of h.groups.views._read_group
27 | import collections
28 |
29 | import h
30 | from h import presenters
31 | from h.api import search
32 | from h.api import uri
33 | from pyramid import renderers
34 | import h.models
35 |
36 |
37 | def read_group(request, group, language=None, search_url=None, user=None, render=True):
38 | """
39 | Return the rendered "Share this group" page.
40 |
41 | This is the page that's shown when a user who is already a member of a group visits the group's URL.
42 | :param request: a request object
43 | :param group: a group object
44 | :param language: a language object to search or None
45 | :param search_url: the URL to search or None
46 | :param user: the user object to search or None
47 | :param render: whether or not to return a context object
48 | :return: if render, a context object for a template. If render is false, a list of annotations
49 | """
50 |
51 | if group is None:
52 | public_group_id = "__world__"
53 | slug = "Public"
54 | else:
55 | public_group_id = group.pubid
56 | slug = group.slug
57 |
58 | url = request.route_url('group_read', pubid=public_group_id, slug=slug)
59 |
60 | parameters = {"group": public_group_id, "limit": 1000}
61 |
62 | if language:
63 | parameters['language'] = language.pubid
64 |
65 | if search_url:
66 | parameters['uri'] = search_url
67 |
68 | if user:
69 | parameters['user'] = user
70 |
71 | result = search.search(request,
72 | private=False,
73 | params=parameters)
74 |
75 | annotations = [presenters.AnnotationHTMLPresenter(h.models.Annotation(a))
76 | for a in result['rows']]
77 |
78 | if render:
79 | # Group the annotations by URI.
80 | # Create a dict mapping the (normalized) URIs of the annotated documents
81 | # to the most recent annotation of each document.
82 | annotations_by_uri = collections.OrderedDict()
83 | for annotation in annotations:
84 | normalized_uri = uri.normalize(annotation.uri)
85 | if normalized_uri not in annotations_by_uri:
86 | annotations_by_uri[normalized_uri] = annotation
87 | if len(annotations_by_uri) >= 25:
88 | break
89 |
90 | document_links = [annotation.document_link
91 | for annotation in annotations_by_uri.values()]
92 |
93 | template_data = {
94 | 'group': group, 'group_url': url, 'document_links': document_links}
95 |
96 | return renderers.render_to_response(
97 | renderer_name='h:templates/groups/share.html.jinja2',
98 | value=template_data, request=request)
99 | else:
100 | return annotations
101 |
--------------------------------------------------------------------------------
/annotran/help/__init__.py:
--------------------------------------------------------------------------------
1 | def includeme(config):
2 | config.include('.views')
3 |
--------------------------------------------------------------------------------
/annotran/help/views.py:
--------------------------------------------------------------------------------
1 | from pyramid.view import view_config
2 |
3 |
4 | @view_config(renderer='annotran:templates/annotran_help.html.jinja2', route_name='annotran_help')
5 | @view_config(renderer='annotran:templates/annotran_help.html.jinja2', route_name='annotran_onboarding')
6 | def help_page(request):
7 | """
8 | Render the help page for annotran
9 | :param request: the current request
10 | :return: a context for the template
11 | """
12 | current_route = request.matched_route.name
13 | return {
14 | 'embed_js_url': request.route_path('embed'),
15 | 'is_help': current_route == 'annotran_help',
16 | 'is_onboarding': current_route == 'annotran_onboarding',
17 | }
18 |
19 |
20 | def includeme(config):
21 | """
22 | Pyramid's includeme setup
23 | :param config: the configuration to which to add routes
24 | :return: None
25 | """
26 | config.add_route('annotran_help', '/docs/annotran_help')
27 | config.add_route('annotran_onboarding', '/annotran_welcome')
28 |
29 | config.scan(__name__)
30 |
--------------------------------------------------------------------------------
/annotran/languages/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | def includeme(config):
5 | config.include('.views')
6 |
--------------------------------------------------------------------------------
/annotran/languages/models.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | """
25 |
26 | # -*- coding: utf-8 -*-
27 |
28 | import datetime
29 | import h.pubid
30 | import sqlalchemy as sa
31 | from h.db import Base
32 | from sqlalchemy.orm import exc
33 |
34 | LANGUAGE_NAME_MIN_LENGTH = 4
35 | LANGUAGE_NAME_MAX_LENGTH = 25
36 |
37 |
38 | class Language(Base):
39 | """
40 | Represents a language in the database
41 | """
42 | __tablename__ = 'language'
43 |
44 | id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
45 | # We don't expose the integer PK to the world, so we generate a short
46 | # random string to use as the publicly visible ID.
47 | pubid = sa.Column(sa.Text(),
48 | default=h.pubid.generate,
49 | unique=True,
50 | nullable=False)
51 | name = sa.Column(sa.UnicodeText(), nullable=False, unique=True)
52 | created = sa.Column(sa.DateTime,
53 | default=datetime.datetime.utcnow,
54 | server_default=sa.func.now(),
55 | nullable=False)
56 |
57 | def __init__(self, name):
58 | """
59 | Initialize a language
60 | :param name: the name of the language
61 | """
62 | self.name = name
63 |
64 | @sa.orm.validates('name')
65 | def validate_name(self, key, name):
66 | """
67 | Validate that the language name is of an acceptable length
68 | :param key: the uniquely identifying key (not used)
69 | :param name: the name of the language
70 | :return: either the name or a ValueError exception
71 | """
72 | if not LANGUAGE_NAME_MIN_LENGTH <= len(name) <= LANGUAGE_NAME_MAX_LENGTH:
73 | raise ValueError('name must be between {min} and {max} characters '
74 | 'long'.format(min=LANGUAGE_NAME_MIN_LENGTH,
75 | max=LANGUAGE_NAME_MAX_LENGTH))
76 | return name
77 |
78 | @classmethod
79 | def get_by_public_language_id(cls, public_language_id):
80 | """
81 | Get a language with the given public language ID
82 | :param public_language_id: the public language ID
83 | :return: either a language or None
84 | """
85 | if public_language_id:
86 | return cls.query.filter(cls.pubid == public_language_id).first()
87 | else:
88 | return None
89 |
90 | @classmethod
91 | def get_by_id(cls, id_):
92 | """
93 | Get a language by its ID
94 | :param id_: the ID of the language
95 | :return: a language or None
96 | """
97 | try:
98 | return cls.query.filter(
99 | cls.id == id_).one()
100 | except exc.NoResultFound:
101 | return None
102 |
103 | @classmethod
104 | def get_by_name(cls, name):
105 | """
106 | Get a language by name
107 | :param name: the name of the language
108 | :return: a language or None
109 | """
110 | try:
111 | return cls.query.filter(
112 | cls.name == name).one()
113 | except exc.NoResultFound:
114 | return None
115 |
116 | def __repr__(self):
117 | """
118 | The internal representation of the language
119 | :return: the name of the language
120 | """
121 | return '{0}'.format(self.name)
122 |
--------------------------------------------------------------------------------
/annotran/languages/schemas.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | """
25 |
26 | # -*- coding: utf-8 -*-
27 |
28 | import colander
29 | import deform
30 | import deform.widget
31 | from annotran.languages.models import LANGUAGE_NAME_MAX_LENGTH
32 | from annotran.languages.models import LANGUAGE_NAME_MIN_LENGTH
33 | from h import i18n
34 | from h.accounts.schemas import CSRFSchema
35 |
36 | _ = i18n.TranslationString
37 |
38 |
39 | class LanguageSchema(CSRFSchema):
40 | """The schema for the create-a-new-language form."""
41 |
42 | name = colander.SchemaNode(
43 | colander.String(),
44 | title=_("What do you want to call the language?"),
45 | validator=colander.Length(
46 | min=LANGUAGE_NAME_MIN_LENGTH,
47 | max=LANGUAGE_NAME_MAX_LENGTH),
48 | widget=deform.widget.TextInputWidget(
49 | autofocus=True,
50 | css_class="language-form__name-input js-language-name-input",
51 | disable_autocomplete=True,
52 | label_css_class="language-form__name-label",
53 | max_length=LANGUAGE_NAME_MAX_LENGTH,
54 | placeholder=_("Language Name")))
55 |
--------------------------------------------------------------------------------
/annotran/languages/test/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openlibhums/annotran/42678afaee6d4b57cfaddb402bc6f15b37fdd027/annotran/languages/test/__init__.py
--------------------------------------------------------------------------------
/annotran/languages/test/models_test.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import pytest
3 |
4 | from h import db
5 | from annotran.languages import models as lang_models
6 |
7 |
8 | def test_init():
9 | name = "abc_language"
10 | l = lang_models.Language(name=name)
11 | db.Session.add(l)
12 | db.Session.flush()
13 | assert l.id
14 | assert l.name == name
15 |
16 |
17 | def test_with_short_name():
18 | """Should raise ValueError if name shorter than 4 characters."""
19 | with pytest.raises(ValueError):
20 | lang_models.Language(name="abc")
21 |
22 |
23 | def test_with_long_name():
24 | """Should raise ValueError if name longer than 25 characters."""
25 | with pytest.raises(ValueError):
26 | lang_models.Language(name="abcdefghijklmnopqrstuvwxyz")
27 |
28 |
29 | def test_repr():
30 | name = "abc_language"
31 |
32 | l = lang_models.Language(name=name)
33 | db.Session.add(l)
34 | db.Session.flush()
35 |
36 | assert repr(l) == name
37 |
38 | def test_get_by_id_when_id_does_exist():
39 | l = lang_models.Language(name="abc_language")
40 |
41 | db.Session.add(l)
42 | db.Session.flush()
43 |
44 | assert lang_models.Language.get_by_id(l.id) == l
45 |
46 |
47 | def test_get_by_id_when_id_does_not_exist():
48 | l = lang_models.Language(name="abc_language")
49 |
50 | db.Session.add(l)
51 | db.Session.flush()
52 |
53 | assert lang_models.Language.get_by_id(23) is None
54 |
55 | def test_get_by_name():
56 | name="abc_language"
57 |
58 | l = lang_models.Language(name=name)
59 | db.Session.add(l)
60 | db.Session.flush()
61 |
62 | assert lang_models.Language.get_by_name(name) == l
63 |
64 | def test_get_by_public_language_id():
65 | l = lang_models.Language(name="abc_language")
66 |
67 | db.Session.add(l)
68 | db.Session.flush()
69 |
70 | assert lang_models.Language.get_by_public_language_id(l.pubid) == l
71 |
--------------------------------------------------------------------------------
/annotran/languages/test/views_test.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import mock
3 | import pytest
4 | from pyramid import httpexceptions
5 |
6 | from annotran.languages import views
7 |
8 | _SENTINEL = object()
9 |
10 |
11 | def _mock_request(feature=None, settings=None, params=None,
12 | authenticated_userid=_SENTINEL, route_url=None, **kwargs):
13 | """Return a mock Pyramid request object."""
14 | params = params or {"foo": "bar"}
15 | if authenticated_userid is _SENTINEL:
16 | authenticated_userid = "acct:fred@annotran.com"
17 | return mock.Mock(
18 | feature=feature or (lambda feature: True),
19 | registry=mock.Mock(settings=settings or {}),
20 | params=params, POST=params,
21 | authenticated_userid=authenticated_userid,
22 | route_url=route_url or mock.Mock(return_value="test-read-url"),
23 | **kwargs)
24 |
25 |
26 | def _matchdict():
27 | """Return a matchdict like the one the translation_read route receives."""
28 | return {"pubid": mock.sentinel.pubid, "slug": mock.sentinel.slug}
29 |
30 |
31 | # The fixtures required to mock create()'s dependencies when a language does not exist in a db.
32 | create_fixtures = pytest.mark.usefixtures('session_model')
33 |
34 | @create_fixtures
35 | def test_create_adds_language_to_db():
36 | """
37 | This should add the new language to the database session, whih is added only if it does not exit in a db.
38 | After successfully creating a new language it should redirect
39 | """
40 | request = _mock_request(matchdict={'public_group_id': '12345', 'language': 'zrrtgy'})
41 | result = views.add_language(request)
42 | request.db.add.assert_called_once()
43 | assert isinstance(result, httpexceptions.HTTPRedirection)
44 |
45 |
46 | # The fixtures required to mock create()'s dependencies for an existing language.
47 | create_fixtures = pytest.mark.usefixtures('LanguageSchema', 'Language',
48 | 'session_model')
49 | @create_fixtures
50 | def test_create_redirects_to_translation_read_page(Language):
51 | """
52 | After successfully fetching a mock Language object it
53 | should not add that one into db but it should redirect.
54 | """
55 | language = mock.Mock(public_group_id='12345', language='language')
56 | Language.return_value = language
57 | request = _mock_request(matchdict={'public_group_id': '12345', 'language': 'test_language'})
58 | result = views.add_language(request)
59 | assert not request.db.add.called
60 | assert isinstance(result, httpexceptions.HTTPRedirection)
61 |
62 | @create_fixtures
63 | def test_create_with_non_ascii_name():
64 | request = _mock_request(matchdict={'public_group_id': 'abc', 'language': u"☆ ßüper Gröup ☆"})
65 | views.add_language(request)
66 |
67 |
68 | @pytest.fixture
69 | def LanguageSchema(request):
70 | patcher = mock.patch('annotran.languages.schemas.LanguageSchema', autospec=True)
71 | request.addfinalizer(patcher.stop)
72 | return patcher.start()
73 |
74 | @pytest.fixture
75 | def Language(request):
76 | patcher = mock.patch('annotran.languages.models.Language', autospec=True)
77 | request.addfinalizer(patcher.stop)
78 | return patcher.start()
79 |
80 | @pytest.fixture
81 | def session_model(request):
82 | patcher = mock.patch('annotran.session.model')
83 | request.addfinalizer(patcher.stop)
84 | return patcher.start()
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/annotran/languages/views.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | """
25 |
26 | # -*- coding: utf-8 -*-
27 | from pyramid import httpexceptions as exc
28 | from pyramid.view import view_config
29 | from h import i18n
30 |
31 | import models
32 |
33 | _ = i18n.TranslationString
34 |
35 |
36 | @view_config(route_name='language_add',
37 | request_method='POST')
38 | def add_language(request):
39 | """
40 | This view adds a language
41 | :param request: a request object
42 | :return: None
43 | """
44 | if request.authenticated_userid is None:
45 | raise exc.HTTPNotFound()
46 |
47 | name = request.matchdict["language"]
48 | public_group_id = request.matchdict["public_group_id"]
49 |
50 | language = models.Language.get_by_name(name)
51 |
52 | if not language:
53 | language = models.Language(name=name)
54 | request.db.add(language)
55 | # We need to flush the db session here so that language.id will be generated.
56 | request.db.flush()
57 | url = request.route_url('translation_read', public_language_id=language.pubid, public_group_id=public_group_id)
58 | return exc.HTTPSeeOther(url)
59 |
60 | def includeme(config):
61 | """
62 | Pyramid's router configuration
63 | :param config: the config to which to commit the routes
64 | :return: None
65 | """
66 | config.add_route('language_add', 'languages/{language}/{public_group_id}/addLanguage')
67 | config.scan(__name__)
68 |
--------------------------------------------------------------------------------
/annotran/mailer.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """A module for sending emails asynchronously.
3 |
4 | Most code that sends emails should do so through this module.
5 |
6 | User code should call the send() function to cause an email to be sent
7 | asynchronously.
8 |
9 | """
10 |
11 | import pyramid_mailer
12 | import pyramid_mailer.message
13 |
14 |
15 | def send(request, recipients, subject, body):
16 | """Cause an email to be sent asynchronously.
17 |
18 | :param request: the request object for the request that wants to send the
19 | email
20 | :type request: pyramid.request.Request
21 |
22 | :param recipients: the list of email addresses to send the email to
23 | :type recipients: list of unicode strings
24 |
25 | :param subject: the subject of the email
26 | :type subject: unicode
27 |
28 | :param body: the body of the email
29 | :type body: unicode
30 |
31 | """
32 |
33 | email = pyramid_mailer.message.Message(subject=subject, recipients=recipients, body=body)
34 |
35 | pyramid_mailer.get_mailer(request).send_immediately(email)
36 |
--------------------------------------------------------------------------------
/annotran/pages/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | def includeme(config):
5 | config.include('.views')
6 |
--------------------------------------------------------------------------------
/annotran/pages/models.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | """
24 |
25 | # -*- coding: utf-8 -*-
26 |
27 | import datetime
28 |
29 | import sqlalchemy as sa
30 | from h.db import Base
31 | from sqlalchemy.orm import exc
32 |
33 |
34 | class Page(Base):
35 | """
36 | Represents a page in the database
37 | """
38 | __tablename__ = 'page'
39 |
40 | id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
41 | uri = sa.Column(sa.Text(),
42 | unique=True,
43 | nullable=False)
44 | created = sa.Column(sa.DateTime,
45 | default=datetime.datetime.utcnow,
46 | server_default=sa.func.now(),
47 | nullable=False)
48 |
49 | def __init__(self, uri):
50 | """
51 | Initialize a page
52 | :param uri: the URI of the page
53 | """
54 | self.uri = uri
55 |
56 | @classmethod
57 | def get_by_uri(cls, uri):
58 | """
59 | Get the page with the given URI
60 | :param uri: the URI to fetch
61 | :return: a page or None
62 | """
63 | return cls.query.filter(cls.uri == uri).first()
64 |
65 | @classmethod
66 | def get_by_id(cls, id_):
67 | """
68 | Get the page by a specified ID
69 | :param id_: the ID
70 | :return: a page or None
71 | """
72 | try:
73 | return cls.query.filter(
74 | cls.id == id_).one()
75 | except exc.NoResultFound:
76 | return None
77 |
--------------------------------------------------------------------------------
/annotran/pages/test/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openlibhums/annotran/42678afaee6d4b57cfaddb402bc6f15b37fdd027/annotran/pages/test/__init__.py
--------------------------------------------------------------------------------
/annotran/pages/test/models_test.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from h import db
4 | from annotran.pages import models as pages_models
5 |
6 | def test_init():
7 | uri="http://www.annotran_test.com/"
8 |
9 | p = pages_models.Page(uri=uri)
10 | db.Session.add(p)
11 | db.Session.flush()
12 |
13 | assert p.id
14 | assert p.uri == uri
15 |
16 | def test_get_by_uri():
17 | uri="http://www.annotran_test.com/"
18 |
19 | p = pages_models.Page(uri=uri)
20 | db.Session.add(p)
21 | db.Session.flush()
22 |
23 | assert pages_models.Page.get_by_uri(uri) == p
24 |
25 |
26 | def test_get_by_id():
27 | uri="http://www.annotran_test.com/"
28 |
29 | p = pages_models.Page(uri=uri)
30 | db.Session.add(p)
31 | db.Session.flush()
32 |
33 | assert pages_models.Page.get_by_id(p.id) == p
--------------------------------------------------------------------------------
/annotran/pages/test/views_test.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import mock
3 | import pytest
4 | from pyramid import httpexceptions
5 |
6 | from annotran.pages import views
7 |
8 |
9 | _SENTINEL = object()
10 |
11 |
12 | def _mock_request(feature=None, settings=None, params=None,
13 | authenticated_userid=_SENTINEL, route_url=None, **kwargs):
14 | """Return a mock Pyramid request object."""
15 | params = params or {"foo": "bar"}
16 | if authenticated_userid is _SENTINEL:
17 | authenticated_userid = "acct:fred@annotran.com"
18 | return mock.Mock(
19 | feature=feature or (lambda feature: True),
20 | registry=mock.Mock(settings=settings or {}),
21 | params=params, POST=params,
22 | authenticated_userid=authenticated_userid,
23 | route_url=route_url or mock.Mock(return_value="test-read-url"),
24 | **kwargs)
25 |
26 | # The fixtures required to mock create()'s dependencies when a page does not exist in a db.
27 | create_fixtures = pytest.mark.usefixtures('Language',
28 | 'session_model')
29 |
30 | @create_fixtures
31 | def test_create_adds_page_to_db(Language):
32 | """
33 | This should add the new page to the database session, which is added only if it does not exit in a db.
34 | After successfully creating a new page it should redirect.
35 | """
36 | language = mock.Mock()
37 | Language.return_value = language
38 |
39 | request = _mock_request(matchdict={'language_name': 'test', 'public_group_id': 12345,
40 | 'page_url': 'http://www.annotran_testing.com'})
41 | result = views.add_page(request)
42 |
43 | request.db.add.assert_called_once()
44 | assert isinstance(result, httpexceptions.HTTPRedirection)
45 |
46 | # The fixtures required to mock create()'s dependencies for an existing page.
47 | create_fixtures = pytest.mark.usefixtures('Page', 'Language',
48 | 'session_model')
49 |
50 | @create_fixtures
51 | def test_create_redirects_to_page_read_page(Page, Language):
52 | """
53 | After successfully fetching mock Page and Language objects it
54 | should not add that page into db but it should redirect.
55 | """
56 | language = mock.Mock()
57 | Language.return_value = language
58 | page = mock.Mock(id=1)
59 | Page.return_value = page
60 |
61 | request = _mock_request(matchdict={'language_name': 'test', 'public_group_id': 12345,
62 | 'page_url': 'http://www.annotran_testing.com'})
63 | result = views.add_page(request)
64 | assert not request.db.add.called
65 | assert isinstance(result, httpexceptions.HTTPRedirection)
66 |
67 | @pytest.fixture
68 | def Page(request):
69 | patcher = mock.patch('annotran.pages.models.Page', autospec=True)
70 | request.addfinalizer(patcher.stop)
71 | return patcher.start()
72 |
73 | @pytest.fixture
74 | def Language(request):
75 | patcher = mock.patch('annotran.languages.models.Language', autospec=True)
76 | request.addfinalizer(patcher.stop)
77 | return patcher.start()
78 |
79 | @pytest.fixture
80 | def session_model(request):
81 | patcher = mock.patch('annotran.session.model')
82 | request.addfinalizer(patcher.stop)
83 | return patcher.start()
84 |
--------------------------------------------------------------------------------
/annotran/pages/views.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | """
24 |
25 | # -*- coding: utf-8 -*-
26 | import urllib
27 |
28 | import annotran
29 | import annotran.languages.models
30 | import annotran.pages.models
31 | from h import i18n
32 | from pyramid import httpexceptions as exc
33 | from pyramid.view import view_config
34 |
35 | _ = i18n.TranslationString
36 |
37 |
38 | @view_config(route_name='page_add',
39 | request_method='POST')
40 | def add_page(request):
41 | """
42 | Add a page to the database
43 | :param request: a request object
44 | :return: a redirect to the translation_read URL
45 | """
46 | if request.authenticated_userid is None:
47 | raise exc.HTTPNotFound()
48 |
49 | name = request.matchdict["language_name"]
50 | page_id = urllib.unquote(urllib.unquote(request.matchdict["page_url"]))
51 | public_group_id = request.matchdict["public_group_id"]
52 |
53 | language = annotran.languages.models.Language.get_by_name(name)
54 | page = annotran.pages.models.Page.get_by_uri(page_id)
55 |
56 | if not page:
57 | page = annotran.pages.models.Page(uri=page_id)
58 | request.db.add(page)
59 | request.db.flush()
60 |
61 | url = request.route_url('translation_read', public_language_id=language.pubid, public_group_id=public_group_id)
62 | return exc.HTTPSeeOther(url)
63 |
64 |
65 | def includeme(config):
66 | """
67 | Pyramid's router configuration
68 | :param config: the config object to which to append routes
69 | :return: None
70 | """
71 | config.add_route('page_add', 'pages/{language_name}/{page_url}/{public_group_id}/addPage')
72 | config.scan(__name__)
73 |
--------------------------------------------------------------------------------
/annotran/reports/__init__.py:
--------------------------------------------------------------------------------
1 | def includeme(config):
2 | config.include('.views')
3 |
--------------------------------------------------------------------------------
/annotran/reports/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | import annotran.languages.models
5 | import annotran.pages.models
6 | import annotran.translations.models
7 | import h.accounts.models
8 | import h.groups.models
9 | import sqlalchemy as sa
10 | from h.db import Base
11 | from sqlalchemy.orm import exc
12 | from sqlalchemy import ForeignKeyConstraint
13 |
14 |
15 | class Report(Base):
16 | """
17 | Represents an abuse report in the database
18 | """
19 | __tablename__ = 'report'
20 |
21 | id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
22 |
23 |
24 | page_id = sa.Column(sa.Integer)
25 | language_id = sa.Column(sa.Integer)
26 | group_id = sa.Column(sa.Integer)
27 |
28 | sa.ForeignKeyConstraint([page_id, language_id, group_id],
29 | [annotran.translations.models.Translation.page_id,
30 | annotran.translations.models.Translation.language_id,
31 | annotran.translations.models.Translation.group_id])
32 |
33 | author_id = sa.Column(sa.Integer, sa.ForeignKey(h.accounts.models.User.id))
34 | author = sa.orm.relationship('User', backref='author_report', foreign_keys=[author_id])
35 |
36 | reporter_id = sa.Column(sa.Integer, sa.ForeignKey(h.accounts.models.User.id))
37 | Reporter = sa.orm.relationship('User', backref='reporter_report', foreign_keys=[reporter_id])
38 |
39 | def __init__(self, translation, author, reporter):
40 | """
41 | Initialize a report
42 | :param translation: the translation (page, group, language) to which the report corresponds
43 | :param author: the translation author to which the report corresponds
44 | :param reporter: the reporting user to which the report corresponds
45 | """
46 | if translation and author and reporter:
47 | self.page_id = translation.page_id
48 | self.language_id = translation.language_id
49 | self.group_id = translation.group_id
50 | self.author_id = author.id
51 | self.reporter_id = reporter.id
52 |
53 | @classmethod
54 | def get_report(cls, translation, author, reporter):
55 | """
56 | Get a report by page, language, group, author, and reporter
57 | :param translation: the translation (page, group, language) to which the report corresponds
58 | :param author: the author to query
59 | :param reporter: the reporting user to query
60 | :return:
61 | """
62 | if translation and author and reporter:
63 | try:
64 | return cls.query.filter(
65 | cls.page_id == translation.page_id,
66 | cls.language_id == translation.language_id,
67 | cls.group_id == translation.group_id,
68 | cls.author_id == author.id,
69 | cls.reporter_id == reporter.id).one()
70 | except exc.NoResultFound:
71 | return None
72 | return None
73 |
74 | @classmethod
75 | def get_by_id(cls, id_):
76 | """
77 | Get a report with a specific ID
78 | :param id_: the ID to query
79 | :return: a report or None
80 | """
81 | try:
82 | return cls.query.filter(
83 | cls.id == id_).one()
84 | except exc.NoResultFound:
85 | return None
86 |
87 | @classmethod
88 | def get_all(cls):
89 | """
90 | Get all reports
91 | :return: a list of all reports or an empty list
92 | """
93 | return cls.query.all()
94 |
--------------------------------------------------------------------------------
/annotran/reports/test/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openlibhums/annotran/42678afaee6d4b57cfaddb402bc6f15b37fdd027/annotran/reports/test/__init__.py
--------------------------------------------------------------------------------
/annotran/reports/views.py:
--------------------------------------------------------------------------------
1 | import urllib
2 |
3 | import annotran
4 | import annotran.translations.models
5 | import annotran.mailer
6 | import annotran.pages.models
7 | import annotran.languages.models
8 | import annotran.reports.models
9 | import annotran.views
10 | import h
11 | import h.groups.models
12 | import h.models
13 | from pyramid import httpexceptions as exc
14 | from pyramid.view import view_config
15 |
16 |
17 | @view_config(route_name='report_add', request_method='POST', renderer='annotran:templates/home.html.jinja2')
18 | def add_report(request):
19 | """
20 | Add an abuse report to the database
21 | :param request: a request object
22 | :return: a redirect to the abuse reports page
23 | """
24 | if request.authenticated_userid is None:
25 | raise exc.HTTPNotFound()
26 |
27 | reporter = request.authenticated_user
28 | if reporter is None:
29 | raise exc.HTTPNotFound()
30 |
31 | public_language_id = request.matchdict["public_language_id"]
32 | page_url = urllib.unquote(urllib.unquote(request.matchdict["page_uri"]))
33 | public_group_id = request.matchdict['public_group_id']
34 | user_id = request.matchdict['user_id']
35 |
36 | page = annotran.pages.models.Page.get_by_uri(page_url)
37 | author = h.models.User.get_by_username(user_id)
38 | reporter = h.models.User.get_by_username(request.authenticated_user.username)
39 | group = h.groups.models.Group.get_by_pubid(public_group_id)
40 | language = annotran.languages.models.Language.get_by_public_language_id(public_language_id)
41 |
42 | if page is None or author is None or reporter is None or group is None or language is None:
43 | raise exc.HTTPNotFound()
44 |
45 | translation = annotran.translations.models.Translation.get_translation(page, language, group)
46 |
47 | if translation is None:
48 | raise exc.HTTPNotFound()
49 |
50 | report = annotran.reports.models.Report.get_report(translation, author, reporter)
51 |
52 | # if already in a database, it means it was reported previously
53 | if report:
54 | return exc.HTTPBadRequest()
55 |
56 | report = annotran.reports.models.Report(translation, author, reporter)
57 | request.db.add(report)
58 | request.db.flush()
59 |
60 | reports = request.route_url('admin_reports')
61 |
62 | body_text = u'Hello,\n\nA new abuse report has been filed. ' \
63 | u'Please see {0}.\n\nAnnotran'.format(reports)
64 |
65 | annotran.mailer.send(request, subject=u'A new abuse report has been filed',
66 | recipients=[annotran.views.Shared.support_address],
67 | body=body_text)
68 |
69 | return {}
70 |
71 |
72 | def includeme(config):
73 | """
74 | Pyramid's router configuration
75 | :param config: the configuration object to which to push our routes
76 | :return: None
77 | """
78 | config.add_route('report_add', 'reports/{user_id}/{public_group_id}/{public_language_id}/{page_uri}/addReport')
79 | config.scan(__name__)
80 |
--------------------------------------------------------------------------------
/annotran/resources.py:
--------------------------------------------------------------------------------
1 | from pyramid.security import Allow
2 |
3 |
4 | __acl__ = [
5 | (Allow, 'group:__admin__', 'admin_index'),
6 | (Allow, 'group:__staff__', 'admin_index'),
7 | (Allow, 'group:__admin__', 'admin_features'),
8 | (Allow, 'group:__admin__', 'admin_nipsa'),
9 | (Allow, 'group:__admin__', 'admin_admins'),
10 | (Allow, 'group:__admin__', 'admin_reports'),
11 | (Allow, 'group:__staff__', 'admin_reports'),
12 | (Allow, 'group:__admin__', 'admin_staff'),
13 | (Allow, 'group:__admin__', 'admin_users'),
14 | (Allow, 'group:__staff__', 'admin_users'),
15 | (Allow, 'group:__admin__', 'admin_badge'),
16 | (Allow, 'group:__admin__', 'admin_groups'),
17 | (Allow, 'group:__staff__', 'admin_groups'),
18 | (Allow, 'group:__staff__', 'admin_view_translation'),
19 | (Allow, 'group:__admin__', 'admin_view_translation'),
20 | (Allow, 'group:__staff__', 'admin_delete_translation'),
21 | (Allow, 'group:__admin__', 'admin_delete_translation'),
22 | (Allow, 'group:__staff__', 'admin_delete_block_translation'),
23 | (Allow, 'group:__admin__', 'admin_delete_block_translation'),
24 | (Allow, 'group:__staff__', 'admin_delete_report'),
25 | (Allow, 'group:__admin__', 'admin_delete_report'),
26 | (Allow, 'group:__staff__', 'admin_delete_block_report'),
27 | (Allow, 'group:__admin__', 'admin_delete_block_report'),
28 | ]
29 |
--------------------------------------------------------------------------------
/annotran/static/images/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openlibhums/annotran/42678afaee6d4b57cfaddb402bc6f15b37fdd027/annotran/static/images/home.png
--------------------------------------------------------------------------------
/annotran/static/images/home.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openlibhums/annotran/42678afaee6d4b57cfaddb402bc6f15b37fdd027/annotran/static/images/home.xcf
--------------------------------------------------------------------------------
/annotran/static/images/stars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openlibhums/annotran/42678afaee6d4b57cfaddb402bc6f15b37fdd027/annotran/static/images/stars.png
--------------------------------------------------------------------------------
/annotran/static/scripts/annotator/guest.coffee:
--------------------------------------------------------------------------------
1 | Guest = require('../../../../../h/h/static/scripts/annotator/guest.coffee');
2 | extend = require('extend')
3 | Annotator = require('annotator')
4 | $ = Annotator.$
5 |
6 | class GuestExt extends Guest
7 |
8 | constructor: (element, options) ->
9 | super
10 |
11 |
12 | html: extend {}, Annotator::html,
13 | adder: '''
14 |
15 |
16 |
17 | '''
18 |
19 | anchor: (annotation) ->
20 | # disable anchoring pip display in sidebar
21 | null
22 |
23 | _connectAnnotationSync: (crossframe) =>
24 | super
25 |
26 | crossframe.on 'sentenceSelectionOn', () =>
27 | Annotator = require('annotator')
28 | Annotator._instances[0].plugins.SentenceSelection.toggleOperationOn()
29 |
30 | crossframe.on 'sentenceSelectionOff', () =>
31 | Annotator = require('annotator')
32 | Annotator._instances[0].plugins.SentenceSelection.toggleOperationOff()
33 |
34 | crossframe.on 'hideAdder', () =>
35 | Annotator = require('annotator')
36 | Annotator._instances[0].plugins.CSSModify.hideAdder()
37 |
38 | crossframe.on 'moveToNextSentence', () =>
39 | Annotator = require('annotator')
40 | Annotator._instances[0].plugins.SentenceSelection.moveToNextSentence()
41 |
42 | crossframe.on 'resetDOM', () =>
43 | Annotator = require('annotator')
44 | Annotator._instances[0].plugins.Substitution.clearDOM()
45 |
46 | crossframe.on 'updateAnnotationList', (annotations) =>
47 | Annotator._instances[0].loadedAnnotations = annotations
48 |
49 | crossframe.on 'pushAnnotation', (annot) =>
50 | Annotator._instances[0].loadedAnnotations.push annot
51 |
52 | crossframe.on 'stashAnnotations', (annotations) =>
53 | Annotator._instances[0].loadedAnnotations = annotations
54 |
55 | crossframe.on 'passAnnotations', (annotations) =>
56 | Annotator = require('annotator')
57 | Annotator._instances[0].plugins.Substitution.clearDOM()
58 | Annotator._instances[0].plugins.CSSModify.hideAdder()
59 |
60 | if annotations.length > 0
61 | Annotator._instances[0].plugins.Substitution.multipleSubstitution(annotations)
62 |
63 | onAdderClick: (event) ->
64 | event.preventDefault?()
65 | event.stopPropagation?()
66 | @adder.hide()
67 | switch $(event.target).data('action')
68 | when 'comment'
69 | this.createAnnotation()
70 | Annotator.Util.getGlobal().getSelection().removeAllRanges()
71 |
72 |
73 | module.exports = GuestExt
--------------------------------------------------------------------------------
/annotran/static/scripts/annotator/host.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | ###
24 |
25 | Annotator = require('annotator')
26 | $ = Annotator.$
27 |
28 | Guest = require('./guest')
29 |
30 |
31 | module.exports = class Host extends Guest
32 | constructor: (element, options) ->
33 | if options.firstRun
34 | options.app += (if '?' in options.app then '&' else '?') + 'firstrun'
35 |
36 | # Create the iframe
37 | app = $('')
38 | .attr('name', 'hyp_sidebar_frame')
39 | # enable media in annotations to be shown fullscreen
40 | .attr('allowfullscreen', '')
41 | .attr('seamless', '')
42 | .attr('src', options.app + "?url=" + encodeURIComponent(window.location.href))
43 |
44 | @frame = $('')
45 | .css('display', 'none')
46 | .addClass('annotator-frame annotator-outer')
47 | .appendTo(element)
48 |
49 | super
50 |
51 | app.appendTo(@frame)
52 |
53 | this.on 'panelReady', =>
54 | # Initialize tool state.
55 | if options.showHighlights == undefined
56 | # Highlights are on by default.
57 | options.showHighlights = true
58 | this.setVisibleHighlights(options.showHighlights)
59 |
60 | this.show()
61 |
62 | # Show the UI
63 | @frame.css('display', '')
64 |
65 | destroy: ->
66 | @frame.remove()
67 | super
68 |
--------------------------------------------------------------------------------
/annotran/static/scripts/annotator/main.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | */
24 |
25 | var extend = require('extend');
26 | var Annotator = require('annotator');
27 |
28 | // Polyfills
29 | var g = Annotator.Util.getGlobal();
30 | if (g.wgxpath) {
31 | g.wgxpath.install();
32 | }
33 |
34 | // Applications
35 | Annotator.Guest = require('./guest');
36 | Annotator.Host = require('../../../../../h/h/static/scripts/annotator/host');
37 | Annotator.Sidebar = require('./sidebar');
38 | Annotator.PdfSidebar = require('../../../../../h/h/static/scripts/annotator/pdf-sidebar');
39 |
40 | // UI plugins
41 | Annotator.Plugin.BucketBar = require('../../../../../h/h/static/scripts/annotator/plugin/bucket-bar');
42 | Annotator.Plugin.Toolbar = require('./plugin/toolbar');
43 | Annotator.Plugin.CSSModify = require('./plugin/cssmodify');
44 |
45 | // Document type plugins
46 | Annotator.Plugin.PDF = require('../../../../../h/h/static/scripts/annotator/plugin/pdf');
47 | require('../../../../../h/h/static/scripts/vendor/annotator.document'); // Does not export the plugin :(
48 |
49 | // Selection plugins
50 | Annotator.Plugin.TextSelection = require('../../../../../h/h/static/scripts/annotator/plugin/textselection');
51 | Annotator.Plugin.SentenceSelection = require('./plugin/sentenceselection');
52 | Annotator.Plugin.Substitution = require('./plugin/substitution');
53 |
54 | // Cross-frame communication
55 | Annotator.Plugin.CrossFrame = require('../../../../../h/h/static/scripts/annotator/plugin/cross-frame');
56 | Annotator.Plugin.CrossFrame.AnnotationSync = require('../../../../../h/h/static/scripts/annotation-sync');
57 | Annotator.Plugin.CrossFrame.Bridge = require('../../../../../h/h/static/scripts/bridge');
58 | Annotator.Plugin.CrossFrame.Discovery = require('../../../../../h/h/static/scripts/discovery');
59 |
60 | var docs = 'https://h.readthedocs.org/en/latest/hacking/customized-embedding.html';
61 | var options = {
62 | app: jQuery('link[type="application/annotator+html"]').attr('href')
63 | };
64 |
65 | if (window.hasOwnProperty('hypothesisConfig')) {
66 | if (typeof window.hypothesisConfig === 'function') {
67 | extend(options, window.hypothesisConfig());
68 | } else {
69 | throw new TypeError('hypothesisConfig must be a function, see: ' + docs);
70 | }
71 | }
72 |
73 | Annotator.noConflict().$.noConflict(true)(function() {
74 | 'use strict';
75 | var Klass = window.PDFViewerApplication ?
76 | Annotator.PdfSidebar :
77 | Annotator.Sidebar;
78 | if (options.hasOwnProperty('constructor')) {
79 | Klass = options.constructor;
80 | delete options.constructor;
81 | }
82 | window.annotator = new Klass(document.body, options);
83 | });
84 |
--------------------------------------------------------------------------------
/annotran/static/scripts/annotator/plugin/cssmodify.coffee:
--------------------------------------------------------------------------------
1 | Annotator = require('annotator')
2 | $ = Annotator.$
3 |
4 | # This plugin modifies the host page's CSS according to crossframe calls
5 | module.exports = class CSSModify extends Annotator.Plugin
6 |
7 | pluginInit: ->
8 | # Register the event handlers required for creating a selection
9 | super
10 | null
11 |
12 | hideAdder: () ->
13 | $(".annotator-adder").css({"visibility": "hidden"})
14 |
15 | showAdder: () ->
16 | $(".annotator-adder").css({"visibility": "visible"})
--------------------------------------------------------------------------------
/annotran/static/scripts/annotator/plugin/toolbar.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | ###
24 |
25 | Toolbar = require('../../../../../../h/h/static/scripts/annotator/plugin/toolbar.coffee');
26 | Annotator = require('annotator')
27 | $ = Annotator.$
28 |
29 | makeButton = (item) ->
30 | anchor = $('')
31 | .attr('href', '')
32 | .attr('title', item.title)
33 | .attr('name', item.name)
34 | .on(item.on)
35 | .addClass('annotator-frame-button')
36 | .addClass(item.class)
37 | button = $('').append(anchor)
38 | return button[0]
39 |
40 | class ToolbarExt extends Toolbar
41 | pluginInit: ->
42 | @annotator.toolbar = @toolbar = $(@html)
43 | if @options.container?
44 | $(@options.container).append @toolbar
45 | else
46 | $(@element).append @toolbar
47 |
48 | items = [
49 | "title": "Toggle Sidebar"
50 | "class": "annotator-frame-button--sidebar_toggle h-icon-chevron-left"
51 | "name": "sidebar-toggle"
52 | "on":
53 | "click": (event) =>
54 | event.preventDefault()
55 | event.stopPropagation()
56 | collapsed = @annotator.frame.hasClass('annotator-collapsed')
57 | if collapsed
58 | @annotator.show()
59 | else
60 | @annotator.hide()
61 | ]
62 | @buttons = $(makeButton(item) for item in items)
63 |
64 | list = $('
')
65 | @buttons.appendTo(list)
66 | @toolbar.append(list)
67 |
68 | # Remove focus from the anchors when clicked, this removes the focus
69 | # styles intended only for keyboard navigation. IE/FF apply the focus
70 | # psuedo-class to a clicked element.
71 | @toolbar.on('mouseup', 'a', (event) -> $(event.target).blur())
72 |
73 | module.exports = ToolbarExt
--------------------------------------------------------------------------------
/annotran/static/scripts/app-controller.coffee:
--------------------------------------------------------------------------------
1 |
2 | appcontroller = require('../../../../h/h/static/scripts/app-controller.coffee');
3 | persona = require('../../../../h/h/static/scripts/filter/persona.js')
4 |
5 | class AppControllerExt extends appcontroller
6 | this.$inject = [
7 | '$controller', '$document', '$location', '$rootScope', '$route', '$scope',
8 | '$window', 'annotationUI', 'auth', 'drafts', 'features', 'groups', 'languages'
9 | 'identity', 'session'
10 | ]
11 | constructor: (
12 | $controller, $document, $location, $rootScope, $route, $scope,
13 | $window, annotationUI, auth, drafts, features, groups, languages,
14 | identity, session
15 | ) ->
16 |
17 | super($controller, $document, $location, $rootScope, $route, $scope,
18 | $window, annotationUI, auth, drafts, features, groups,
19 | identity, session)
20 |
21 | $scope.$root.sentencebysentence = "off"
22 | $scope.$root.userListvisible = true
23 | $scope.$root.editOnly = false
24 | $scope.$root.userAnnotations = []
25 | $scope.$root.allPageAnnotations = []
26 | $scope.$root.pageUri = window.location.href
27 | $scope.$root.pageUri = decodeURIComponent($scope.$root.pageUri);
28 | $scope.$root.pageUri = encodeURIComponent(encodeURIComponent(getParameterByName("url", $scope.$root.pageUri)));
29 |
30 |
31 | $scope.$root.updateUserList = (direction) ->
32 | # clear the array
33 | $scope.$root.list_of_users = []
34 |
35 | keys = Object.keys($scope.$root.users_no_scores)
36 | if (keys.length != 0)
37 | for i in [0 .. (keys.length-1)]
38 | delete $scope.$root.users_no_scores[keys[i]]
39 |
40 | keys = Object.keys($scope.$root.users_with_scores)
41 | if (keys.length != 0)
42 | for i in [0 .. (keys.length-1)]
43 | delete $scope.$root.users_with_scores[keys[i]]
44 |
45 | # get all users using annot list
46 | getUsers()
47 |
48 | dupeCheck = []
49 |
50 | if $scope.$root.allPageAnnotations.length > 0
51 | if (session.state.votes != undefined && session.state.votes.length != 0)
52 | for i in [0 .. (session.state.votes.length-1)]
53 | if (groups.focused().id == session.state.votes[i].group_id &&
54 | languages.focused().id == session.state.votes[i].language_id)
55 | author = setUserWithScore(session.state.votes[i].author_id)
56 | if (!author)
57 | continue
58 | score = session.state.votes[i].avg_score
59 | auth_obj = {}
60 | auth_obj["score"] = score
61 | auth_obj["author"] = author
62 | $scope.$root.list_of_users.push auth_obj
63 |
64 | #push unvoted users
65 | keys = Object.keys($scope.$root.users_no_scores)
66 | if (keys.length != 0)
67 | for i in [0 .. (keys.length-1)]
68 | auth_obj = {}
69 | auth_obj["score"] = 0
70 | auth_obj["author"] = $scope.$root.users_no_scores[keys[i]]
71 | $scope.$root.list_of_users.push auth_obj
72 |
73 | if (direction == 1) #reverse the order;
74 | $scope.$root.list_of_users = $scope.$root.list_of_users.reverse()
75 |
76 | return $scope.$root.list_of_users
77 |
78 | $scope.$root.users_no_scores = {} #all users on the page, for all groups and langauges
79 | getUsers = () ->
80 | for entry in $scope.$root.allPageAnnotations
81 | parsed = persona.parseAccountID(entry.user)
82 | if (groups.focused().id == entry.group && languages.focused().id == entry.language)
83 | if Object.keys($scope.$root.users_no_scores).length != 0 && $scope.$root.users_no_scores[parsed.username] != undefined
84 | continue
85 | else
86 | $scope.$root.users_no_scores[parsed.username] = parsed
87 |
88 | $scope.$root.users_with_scores = {}
89 | setUserWithScore = (authorusername) ->
90 | user_info = $scope.$root.users_no_scores[authorusername]
91 | $scope.$root.users_with_scores[authorusername] = user_info
92 | delete $scope.$root.users_no_scores[authorusername]
93 | return user_info
94 |
95 | $scope.$root.allVotes = {}
96 | getAuthorTotalScore = (authorusername) ->
97 | author_score = 0.0
98 | if (session.state.votes != undefined && session.state.votes.length != 0)
99 | if Object.keys($scope.$root.allVotes).length == 0
100 | for i in [0 .. (session.state.votes.length-1)]
101 | $scope.$root.allVotes[session.state.votes[i].author_id] = session.state.votes[i]
102 | if session.state.votes[i].author_id == author.username
103 | author_score = session.state.votes[i].avg_score
104 | else
105 | auth_obj = $scope.$root.allVotes[author.username]
106 | if (auth_obj)
107 | author_score = auth_obj.avg_score
108 | return author_score
109 |
110 | `
111 | function getParameterByName(name, url) {
112 | if (!url) url = pageid;
113 | name = name.replace(/[\[\]]/g, "\\$&");
114 | var regex = new RegExp("[?&]" + name + "(=([^]*)|&|#|$)"),
115 | results = regex.exec(url);
116 | if (!results) return null;
117 | if (!results[2]) return '';
118 | return results[2];
119 | //return decodeURIComponent(results[2].replace(/\+/g, " "));
120 | }
121 | `
122 |
123 | module.exports = AppControllerExt
--------------------------------------------------------------------------------
/annotran/static/scripts/apps.js:
--------------------------------------------------------------------------------
1 |
2 | 'use strict'
3 |
4 | // this assumes that h is stored in the same root directory as annotran
5 | require('../../../../h/h/static/scripts/app.coffee');
6 |
7 | require('./directive/language-list.js');
8 | require('./events.js');
9 | var app = angular.module("h");
10 |
11 | app.controller('AppController', require('./app-controller'))
12 | .controller('WidgetController', require('./widget-controller'))
13 | .directive('languageList', require('./directive/language-list').directive)
14 | .directive('userList', require('./directive/user-list').directive)
15 | .directive('annotation', require('./directive/annotation').directive)
16 | .directive('topBar', require('./directive/top-bar').directive)
17 | .directive('thread', require('./directive/thread'))
18 | .directive('shareDialog', require('./directive/share-dialog'))
19 | .directive('signinControl', require('./directive/signin-control'))
20 | .directive('sidebarTutorial', require('./directive/sidebar-tutorial').directive)
21 | .directive('publishAnnotationBtn', require('./directive/publish-annotation-btn').directive)
22 | .directive('stars', function() {
23 | return {
24 | restrict: 'E',
25 | link: function($scope, elem, attr) {
26 | function starify() {
27 | var val = parseFloat($scope.starscore);
28 | var size = Math.max(0, (Math.min(5, val))) * 16;
29 | var span = angular.element('').css('width', size+'px');
30 | angular.element(elem).html(span[0].outerHTML);
31 | }
32 | starify();
33 | },
34 | scope: {
35 | starscore: '='
36 | },
37 | };
38 | })
39 |
40 | .service('languages', require('./languages'))
41 | .service('pages', require('./pages'))
42 | .service('translations', require('./translations'))
43 | .service('groups', require('./groups'))
44 | .service('session', require('./session'))
45 | .service('votes', require('./votes'))
46 | .service('reports', require('./reports'))
47 |
48 | app.factory('langListFactory', require('./directive/language-service').factory)
49 |
50 |
51 | app.controller("languageController", ['$scope', 'langListFactory',
52 | function ($scope, langListFactory) {
53 | $scope.languages = langListFactory.getLanguages();
54 | $scope.language = null;
55 | }]);
56 |
57 |
58 |
59 |
60 |
61 |
62 | // these decorators override hypothes.is's decorators
63 |
64 | app.decorator(
65 | "annotationDirective",
66 | function annotationDirectiveDecorator( $delegate ) {
67 | return( [ $delegate[1] ] );
68 | }
69 | );
70 |
71 | app.decorator(
72 | "topBarDirective",
73 | function topBarDirectiveDecorator( $delegate ) {
74 | return( [ $delegate[1] ] );
75 | }
76 | );
77 |
78 | app.decorator(
79 | "sidebarTutorialDirective",
80 | function sidebarTutorialDirectiveDecorator( $delegate ) {
81 | return( [ $delegate[1] ] );
82 | }
83 | );
84 |
85 | app.decorator(
86 | "threadDirective",
87 | function threadDirectiveDecorator( $delegate ) {
88 | return( [ $delegate[1] ] );
89 | }
90 | );
91 |
92 | app.decorator(
93 | "shareDialogDirective",
94 | function shareDialogDirectiveDecorator( $delegate ) {
95 | return( [ $delegate[1] ] );
96 | }
97 | );
98 |
99 | app.decorator(
100 | "signinControlDirective",
101 | function signinControlDirectiveDecorator( $delegate ) {
102 | return( [ $delegate[1] ] );
103 | }
104 | );
105 |
106 | app.decorator(
107 | "publishAnnotationBtnDirective",
108 | function publishAnnotationBtnDirectiveDecorator( $delegate ) {
109 | return( [ $delegate[1] ] );
110 | }
111 | );
112 |
113 | /*
114 | app.controller('AppController', ['$scope', '$controller', function ( $controller, $document, $location, $rootScope, $route, $scope,
115 | $window, annotationUI, auth, drafts, features, groups, identity, session) {
116 | // Initialize the super class and extend it.
117 | angular.extend(this, $controller('AppController', { $controller: $controller, $document: $document, $location: $location, $rootScope: $rootScope, $route: $route, $scope: $scope,
118 | $window: $window, annotationUI: annotationUI, auth: auth, drafts: drafts,features: features, groups: groups,
119 | identity: identity, session: session}));
120 | console.log($scope.shareDialog);
121 | }]);
122 | */
--------------------------------------------------------------------------------
/annotran/static/scripts/directive/language-list.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var events = require('../../../../../h/h/static/scripts/events.js');
4 | var eventsa = require('../events');
5 | var Annotator = require('annotator');
6 | var $ = Annotator.$;
7 |
8 | var mouseUpPrevent = true;
9 | var clickPrevent = true;
10 |
11 | // @ngInject
12 | function LanguageListController($scope, $window, languages, groups, pages) {
13 | $scope.addLanguage = function (language) {
14 | clickPrevent = false;
15 | mouseUpPrevent = false;
16 |
17 | // this will fire when the user selects the top entry in the list ("Add a new translation")
18 | if(language == null){ return; }
19 |
20 | if (languages.hasLanguageAlready(language.name) == false) {
21 | var message = 'Are you sure you want to add new translations for the language "' +
22 | language.name + '"?';
23 | if ($window.confirm(message)) {
24 | languages.addLanguage(language.name);
25 | }
26 | } else {
27 | var message = 'Language "' +
28 | language.name + '" has already been added.';
29 | alert(message);
30 | languages.focusByName(language.name);
31 | }
32 | $("#zeroIndex").prop('selected', true);
33 | };
34 |
35 | //This method is used for a list of existing languages - those that are already created for that page.
36 | $scope.focusLanguage = function (languageId) {
37 | $scope.$root.setMode('view');
38 | $scope.$root.selectedUser = undefined;
39 | languages.focus(languageId);
40 | };
41 |
42 |
43 | $scope.showUserList = function () {
44 | $scope.$root.userListvisible = true;
45 | $scope.$root.updateUserList(0);
46 | }
47 |
48 | $scope.handleSubmenuMU = function (event) {
49 | if (mouseUpPrevent == true) {
50 | event.stopPropagation();
51 | event.preventDefault();
52 | }
53 | else{
54 | mouseUpPrevent = true;
55 | }
56 | };
57 |
58 | $scope.handleSubmenuClick = function (event) {
59 | if (clickPrevent == true) {
60 | event.stopPropagation();
61 | event.preventDefault();
62 | }
63 | else{
64 | clickPrevent = true;
65 | }
66 | };
67 |
68 | }
69 |
70 | /**
71 | * @ngdoc directive
72 | * @name languageList
73 | * @restrict AE
74 | * @description Displays a list of available languages
75 | */
76 | // @ngInject
77 | function languageList($window, languages, settings) {
78 | return {
79 | controller: LanguageListController,
80 | link: function ($scope, elem, attrs) {
81 | $scope.languages = languages;
82 | },
83 | restrict: 'E',
84 | scope: {
85 | auth: '=',
86 | userList: '=',
87 | showUserList: '='
88 | },
89 | templateUrl: 'language_list.html'
90 | };
91 | };
92 |
93 | module.exports = {
94 | directive: languageList,
95 | Controller: LanguageListController
96 | };
97 |
--------------------------------------------------------------------------------
/annotran/static/scripts/directive/publish-annotation-btn.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var eventsa = require('../events');
4 |
5 | /**
6 | * @ngdoc directive
7 | * @name publishAnnotationBtn
8 | * @description Displays a combined privacy/selection post button to post
9 | * a new annotation
10 | */
11 | // @ngInject
12 | function publishAnnotationBtnController(crossframe, $rootScope, $scope) {
13 |
14 | this.showDropdown = false;
15 | this.privateLabel = 'Only Me';
16 |
17 | $rootScope.$on('moving_to_sentence', function () {
18 | if($rootScope.sentencebysentence == "on") {
19 | $scope.vm.onCancel();
20 | }
21 | });
22 |
23 | $rootScope.$on('close_open_annotations', function () {
24 | $scope.vm.onCancel();
25 | });
26 |
27 | this.publishDestination = function () {
28 | return this.isShared ? this.group.name : this.privateLabel;
29 | }
30 |
31 | this.moveToNext = function() {
32 | crossframe.call("moveToNextSentence");
33 | }
34 |
35 | this.groupType = function () {
36 | return this.group.public ? 'public' : 'group';
37 | }
38 |
39 | this.setPrivacy = function (level) {
40 | this.onSetPrivacy({level: level});
41 | }
42 | }
43 |
44 | /**
45 | * @ngdoc directive
46 | * @name languageList
47 | * @restrict AE
48 | * @description Displays a list of available languages
49 | */
50 | // @ngInject
51 | function publishAnnotationBtn($window, languages, settings) {
52 | return {
53 | bindToController: true,
54 | controller: publishAnnotationBtnController,
55 | controllerAs: 'vm',
56 | restrict: 'E',
57 | scope: {
58 | group: '=',
59 | canPost: '=',
60 | isShared: '=',
61 | onCancel: '&',
62 | onSave: '&',
63 | onSetPrivacy: '&'
64 | },
65 | templateUrl: 'publish_annotation_btn.html'
66 | };
67 | };
68 |
69 | module.exports = {
70 | directive: publishAnnotationBtn,
71 | Controller: publishAnnotationBtnController
72 | };
73 |
--------------------------------------------------------------------------------
/annotran/static/scripts/directive/share-dialog.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
19 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22 |
23 | This is the override of the directive from h - for the purpose of "via annotran" link usage.
24 | ###
25 |
26 |
27 |
28 | ###*
29 | # @ngdoc directive
30 | # @name share-dialog
31 | # @restrict A
32 | # @description This dialog generates a via link to the page h is currently
33 | # loaded on.
34 | ###
35 | module.exports = ['crossframe', (crossframe) ->
36 | link: (scope, elem, attrs, ctrl) ->
37 | scope.viaPageLink = ''
38 |
39 | # Watch scope.shareDialog.visible: when it changes to true, focus input
40 | # and selection.
41 | scope.$watch (-> scope.shareDialog?.visible), (visible) ->
42 | if visible
43 | scope.$evalAsync(-> elem.find('#via').focus().select())
44 |
45 | scope.$watchCollection (-> crossframe.frames), (frames) ->
46 | if not frames.length
47 | return
48 | # Check to see if we are on a via page. If so, we just return the URI.
49 | re = /https:\/\/via\.annotran\.??/
50 | if re.test(frames[0].uri)
51 | scope.viaPageLink = frames[0].uri
52 | else
53 | scope.viaPageLink = 'https://via.annotran.??/' + frames[0].uri
54 |
55 | restrict: 'E'
56 | templateUrl: 'share_dialog.html'
57 | ]
58 |
--------------------------------------------------------------------------------
/annotran/static/scripts/directive/sidebar-tutorial.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 | '''
24 | **/
25 |
26 | /**
27 | * This is the override of the sidebar-tutorial directive from h. We do not use session preferences,
28 | * but rather hide the entire tutorial once user clicks on "Close" button. The tutorial is always shown on the first load. -!>
29 | **/
30 |
31 | 'use strict';
32 |
33 | // @ngInject
34 | function SidebarTutorialController() {
35 | var vm = this;
36 |
37 | var showSidebarTutorial = true;
38 |
39 | vm.showSidebarTutorial = function () {
40 | return showSidebarTutorial;
41 | };
42 |
43 | vm.dismiss = function () {
44 | showSidebarTutorial = false;
45 | };
46 | }
47 |
48 | /**
49 | * @ngdoc directive
50 | * @name sidebarTutorial
51 | * @description Displays a short tutorial in the sidebar.
52 | */
53 | // @ngInject
54 | module.exports = {
55 | directive: function () {
56 | return {
57 | bindToController: true,
58 | controller: SidebarTutorialController,
59 | controllerAs: 'vm',
60 | restrict: 'E',
61 | scope: {
62 | auth: '=',
63 | },
64 | templateUrl: 'sidebar_tutorial.html'
65 | };
66 | },
67 | Controller: SidebarTutorialController
68 | };
69 |
--------------------------------------------------------------------------------
/annotran/static/scripts/directive/signin-control.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function () {
4 | return {
5 | bindToController: true,
6 | controllerAs: 'vm',
7 | //@ngInject
8 | controller: function (settings) {
9 | this.serviceUrl = settings.serviceUrl;
10 | this.support_address = settings.supportAddress;
11 | },
12 | restrict: 'E',
13 | scope: {
14 | /**
15 | * An object representing the current authentication status.
16 | */
17 | auth: '=',
18 | /**
19 | * Called when the user clicks on the "Sign in" text.
20 | */
21 | onLogin: '&',
22 | /**
23 | * Called when the user clicks on the "Sign out" text.
24 | */
25 | onLogout: '&',
26 | /**
27 | * Whether or not to use the new design for the control.
28 | *
29 | * FIXME: should be removed when the old design is deprecated.
30 | */
31 | newStyle: '=',
32 | },
33 | templateUrl: 'signin_control.html',
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/annotran/static/scripts/directive/top-bar.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | directive: function () {
5 | return {
6 | restrict: 'E',
7 | priority: 0,
8 | scope: {
9 | auth: '=',
10 | isSidebar: '=',
11 | onLogin: '&',
12 | onLogout: '&',
13 | searchController: '=',
14 | accountDialog: '=',
15 | shareDialog: '=',
16 | sortBy: '=',
17 | sortOptions: '=',
18 | userList: '=',
19 | onChangeSortBy: '&',
20 | showUserList: '&'
21 | },
22 | templateUrl: 'top_bar.html',
23 | };
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/annotran/static/scripts/directive/user-list.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var persona = require('../../../../../h/h/static/scripts/filter/persona.js');
3 | var events = require('../../../../../h/h/static/scripts/events.js');
4 | var eventsa = require('../events');
5 |
6 | // @ngInject
7 | function Controller($scope, $window, session, settings, languages, votes, reports, crossframe) {
8 |
9 | this.serviceUrl = settings.serviceUrl;
10 | $scope.$root.sentenceMode = "on";
11 | $scope.$root.mode = "view";
12 |
13 | if ($scope.model == null) {
14 | $scope.model = {};
15 | }
16 |
17 | $scope.hideUserList = function () {
18 | $scope.$root.userListvisible = false;
19 | };
20 |
21 | $scope.setUserForReset = function () {
22 | this.$root.selectedUser = undefined;
23 | this.$root.editOnly = false;
24 |
25 | $scope.$root.$broadcast(eventsa.LANGUAGE_FOCUSED, languages.focused().id);
26 |
27 | };
28 |
29 | $scope.$root.setMode = function(modeValue) {
30 | $scope.$root.mode = modeValue;
31 | $scope.setUserForReset();
32 | if ($scope.$root.mode == 'view') {
33 | if ($scope.$root.sentenceMode == "on") {
34 | crossframe.call("sentenceSelectionOff");
35 | }
36 | crossframe.call("hideAdder");
37 | $scope.$root.$broadcast(eventsa.CLOSE_OPEN_ANNOTATIONS);
38 | }
39 | };
40 |
41 | $scope.setUserForEdit = function () {
42 | this.$root.selectedUser = "self";
43 | this.$root.editOnly = true;
44 |
45 | if (languages.focused().id == undefined) {
46 | var message = 'Select language.';
47 | $window.confirm(message);
48 | } else {
49 | $scope.$root.$broadcast(eventsa.LANGUAGE_FOCUSED, languages.focused().id);
50 | }
51 | };
52 |
53 | $scope.setUser = function (id) {
54 | var selectedUser = "acct:" + id.username + "@" + id.provider;
55 |
56 | if (selectedUser == this.$root.currentUser) {
57 | this.$root.selectedUser = "self";
58 | this.$root.editOnly = true;
59 | } else {
60 | this.$root.selectedUser = id;
61 | this.$root.editOnly = false;
62 | }
63 | $scope.$root.$broadcast(eventsa.LANGUAGE_FOCUSED, languages.focused().id);
64 |
65 | };
66 |
67 | $scope.enableSentence = function () {
68 | $scope.$root.sentenceMode = "on";
69 | $scope.setUserForReset();
70 | this.$root.sentencebysentence = $scope.$root.sentenceMode;
71 |
72 | crossframe.call("sentenceSelectionOn");
73 | };
74 |
75 | $scope.disableSentence = function () {
76 | $scope.$root.sentenceMode = "off";
77 | this.$root.sentencebysentence = $scope.$root.sentenceMode;
78 |
79 | crossframe.call("sentenceSelectionOff");
80 | $scope.$root.$broadcast(eventsa.CLOSE_OPEN_ANNOTATIONS);
81 | };
82 |
83 | $scope.vote = function(author) {
84 | if (author != this.$root.currentUser) {
85 | $scope.author = author;
86 | }
87 | };
88 |
89 | $scope.addVote = function(author, score) {
90 | $scope.author=author;
91 |
92 | var voteRet = votes.addVote(author.username, languages.focused().id, score);
93 |
94 | //set scope.author to an empty dictionary in order to hide the box once the user has voted.
95 | $scope.author={};
96 |
97 | return voteRet;
98 | };
99 |
100 | $scope.addReport = function(author) {
101 | $scope.author=author;
102 |
103 | var message = 'Are you sure you want to report ' + author.username + '\'s translations as abusive?';
104 | if ($window.confirm(message)) {
105 | var reportRet = reports.addReport(author.username, languages.focused().id);
106 | // set scope.author to an empty dictionary in order to hide the box once the user has voted.
107 | $scope.author={};
108 | return reportRet;
109 | }
110 | };
111 |
112 | $scope.showVote = function(author, score) {
113 |
114 | votes.showVote(author, score);
115 |
116 | };
117 |
118 | $scope.voteAuthor = function(user) {
119 | return function(score) {
120 | if ($scope.author == undefined || user == undefined)
121 | return false;
122 | if ($scope.author == user)
123 | return true;
124 | else
125 | return false;
126 | }
127 | };
128 |
129 | $scope.reverseUserList = function(direction) {
130 | $scope.$root.direction = direction;
131 | $scope.$root.updateUserList($scope.$root.direction);
132 | };
133 |
134 | $scope.userList = function () {
135 | return $scope.$root.updateUserList(0)
136 | };
137 |
138 | // for some reason we have to use an array here as NG repeat won't handle it properly otherwise
139 | $scope.list_of_users = $scope.$root.list_of_users;
140 | $scope.$root.updateUserList(0);
141 |
142 | }
143 |
144 | // @ngInject
145 | function userList (languages) {
146 | return {
147 | controller: Controller,
148 | bindToController: true,
149 | controllerAs: 'vm',
150 | restrict: 'E',
151 | link: function ($scope, elem, attrs) {
152 | $scope.languages = languages;
153 | },
154 | scope: {
155 | auth: '=',
156 | session: '=',
157 | onClose: '&',
158 | showUserList: '=',
159 | languages: '&',
160 | },
161 | templateUrl: 'user_list.html'
162 | };
163 | }
164 |
165 | module.exports = {
166 | directive: userList,
167 | Controller: Controller
168 | };
169 |
--------------------------------------------------------------------------------
/annotran/static/scripts/events.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | '''
25 | **/
26 |
27 |
28 | /**
29 | * This module extends the set of global events that are dispatched
30 | * on $rootScope and defined in the same module in h
31 | */
32 | module.exports = {
33 | /** Broadcast when a language is added */
34 | LANGUAGE_ADDED: 'languageAdded',
35 | /** Broadcast when a page is added */
36 | PAGE_ADDED: 'pageAdded',
37 | /** Broadcast when the session is reloaded */
38 | SESSION_RELOADED: 'SessionReloaded',
39 | /** Broadcast when the currently selected language changes */
40 | LANGUAGE_FOCUSED: 'languageFocused',
41 | /** Broadcast when the list of groups changes */
42 | LANGUAGES_CHANGED: 'languagesChanged',
43 | /** Broadcast when the list of users is loaded*/
44 | USERS_LOADED: 'usersLoaded',
45 | /** Broadcast when the currently selected user changes*/
46 | USERS_FOCUSED: 'userFocused',
47 | /** Broadcast when the user deleted an annotation*/
48 | USER_DELETED_ANNOTATION: 'user_deleted_annotation',
49 | /** Broadcast when moving to another sentence in sentence-by-sentence mode*/
50 | MOVING_TO_SENTENCE: 'moving_to_sentence',
51 | /** Broadcast when we want to close all active annotations*/
52 | CLOSE_OPEN_ANNOTATIONS: 'close_open_annotations',
53 | /** Broadcast when the rootscope lists are updated*/
54 | ROOTSCOPE_LISTS_UPDATED: 'rootscope_lists_updated',
55 | };
--------------------------------------------------------------------------------
/annotran/static/scripts/groups.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) 2013-2014 Hypothes.is Project and contributors
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 | '''
25 | **/
26 |
27 | /**
28 | * @ngdoc service
29 | * @name groups
30 | *
31 | * @description Provides access to the list of groups that the user is currently
32 | * a member of and the currently selected group in the UI.
33 | *
34 | * The list of groups is initialized from the session state
35 | * and can then later be updated using the add() and remove()
36 | * methods.
37 | */
38 | 'use strict';
39 |
40 | var STORAGE_KEY = 'hypothesis.groups.focus';
41 |
42 | // this assumes that h is stored in the same root directory as annotran
43 | var events = require('../../../../h/h/static/scripts/events.js');
44 |
45 | // @ngInject
46 | function groups(localStorage, session, settings, $rootScope, $http) {
47 | // The currently focused group. This is the group that's shown as selected in
48 | // the groups dropdown, the annotations displayed are filtered to only ones
49 | // that belong to this group, and any new annotations that the user creates
50 | // will be created in this group.
51 | var focusedGroup;
52 | var eventBroadcasted = false;
53 |
54 | function all() {
55 | return session.state.groups || [];
56 | };
57 |
58 | // Return the full object for the group with the given id.
59 | function get(id) {
60 | var gs = all();
61 | for (var i = 0, max = gs.length; i < max; i++) {
62 | if (gs[i].id === id) {
63 | //$rootScope.$broadcast(events.GROUP_FOCUSED, gs[i].id);
64 | return gs[i];
65 | }
66 | }
67 | };
68 |
69 | /** Leave the group with the given ID.
70 | * Returns a promise which resolves when the action completes.
71 | */
72 | function leave(id) {
73 | var response = $http({
74 | method: 'POST',
75 | url: settings.serviceUrl + 'groups/' + id + '/leave',
76 | });
77 |
78 | // the groups list will be updated in response to a session state
79 | // change notification from the server. We could improve the UX here
80 | // by optimistically updating the session state
81 |
82 | return response;
83 | };
84 |
85 |
86 | /** Return the currently focused group. If no group is explicitly focused we
87 | * will check localStorage to see if we have persisted a focused group from
88 | * a previous session. Lastly, we fall back to the first group available.
89 | */
90 | function focused() {
91 | if (focusedGroup) {
92 | return focusedGroup;
93 | }
94 |
95 | var fromStorage = get(localStorage.getItem(STORAGE_KEY));
96 |
97 | if (fromStorage) {
98 | focusedGroup = fromStorage;
99 |
100 | return focusedGroup.id;
101 | }
102 |
103 | return all()[0].id;
104 | }
105 |
106 | /** Set the group with the passed id as the currently focused group. */
107 | function focus(id) {
108 | var g = get(id);
109 | if (g) {
110 | focusedGroup = g;
111 | localStorage.setItem(STORAGE_KEY, g.id);
112 | $rootScope.$broadcast(events.GROUP_FOCUSED, g.id);
113 | }
114 | }
115 |
116 | // reset the focused group if the user leaves it
117 | $rootScope.$on(events.GROUPS_CHANGED, function () {
118 | $rootScope.setMode('view');
119 | if (focusedGroup) {
120 | focusedGroup = get(focusedGroup.id);
121 | $rootScope.$broadcast(events.GROUP_FOCUSED, focusedGroup);
122 | if (!focusedGroup) {
123 | var focusResult = focused();
124 | $rootScope.$broadcast(events.GROUP_FOCUSED, focusResult);
125 | }
126 | }
127 | else {
128 | var focusResult = focused();
129 | $rootScope.$broadcast(events.GROUP_FOCUSED, focusResult);
130 | }
131 | });
132 |
133 | return {
134 | all: all,
135 | get: get,
136 |
137 | leave: leave,
138 |
139 | focused: focused,
140 | focus: focus,
141 | };
142 | }
143 |
144 | module.exports = groups;
145 |
--------------------------------------------------------------------------------
/annotran/static/scripts/pages.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @ngdoc service
4 | * @name pages
5 | *
6 | * @description Adds a page for which at least one language has been added previously.
7 | *
8 | */
9 | 'use strict';
10 |
11 | var eventsa = require('./events');
12 |
13 | // @ngInject
14 | function pages(settings, session, $rootScope, $http, translations) {
15 |
16 | function addPage(languageName) {
17 | var response = $http({
18 | method: 'POST',
19 | url: settings.serviceUrl + 'pages/' + languageName + '/' + $rootScope.pageUri + '/' + $rootScope.groupPubid + '/addPage',
20 | }).then(function successCallback(response) {
21 | $rootScope.$broadcast(eventsa.PAGE_ADDED, languageName);
22 | });;
23 | return response;
24 | };
25 |
26 | $rootScope.$on(eventsa.LANGUAGE_ADDED, function (event, languageName) {
27 | addPage(languageName);
28 | session.reload(languageName);
29 | });
30 |
31 | return {
32 | addPage: addPage,
33 | };
34 | };
35 |
36 | module.exports = pages;
--------------------------------------------------------------------------------
/annotran/static/scripts/reports.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @ngdoc service
4 | * @name languages
5 | *
6 | * @description Provides access to the list of languages for available translations.
7 | *
8 | * The list of languages is initialized from the session state
9 | * and can then later be updated using the add() method.
10 | */
11 | 'use strict';
12 |
13 | var eventsa = require('./events');
14 |
15 |
16 | // @ngInject
17 | function reports(settings, session, $rootScope, $http, $window) {
18 |
19 |
20 | function addReport(userId, languageId) {
21 |
22 | var pageId = $rootScope.pageUri;
23 | var groupPubid = $rootScope.groupPubid;
24 |
25 | var response = $http({
26 | method: 'POST',
27 | url: settings.serviceUrl + '/reports/' + userId + '/' + groupPubid + '/' + languageId + '/' + pageId + '/' + 'addReport'
28 | }).then(function successCallback(response) {
29 | var message = 'You reported ' +
30 | userId + '\'s translations as abusive.';
31 | alert(message);
32 | }, function errorCallback(response) {
33 | var message = 'You already reported this ' +
34 | userId + '\'s translations as abusive.';
35 | alert(message);
36 | });
37 |
38 | session.reload("");
39 |
40 | return response;
41 | }
42 |
43 |
44 | return {
45 | addReport: addReport
46 | };
47 | }
48 |
49 | module.exports = reports;
--------------------------------------------------------------------------------
/annotran/static/scripts/translations.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @ngdoc service
4 | * @name translations
5 | *
6 | * @description Adds a unique translation record for a page and selected group and language.
7 | *
8 | */
9 | 'use strict';
10 |
11 | var eventsa = require('./events');
12 |
13 | // @ngInject
14 | function translations(settings, session, $rootScope, $http) {
15 |
16 | function addTranslation(languageName) {
17 | var response = $http({
18 | method: 'POST',
19 | url: settings.serviceUrl + 'translations/' + languageName + '/' + $rootScope.groupPubid + '/' + $rootScope.pageUri + '/addTranslation',
20 | });
21 | return response;
22 | };
23 |
24 | $rootScope.$on(eventsa.PAGE_ADDED, function (event, languageName) {
25 | addTranslation(languageName);
26 | session.reload(languageName);
27 | });
28 |
29 | return {
30 | addTranslation: addTranslation,
31 | };
32 | };
33 |
34 | module.exports = translations;
--------------------------------------------------------------------------------
/annotran/static/scripts/vendor/google.translate.lang.list.js:
--------------------------------------------------------------------------------
1 | (function(){function d(b){var a=document.getElementsByTagName("head")[0];a||(a=document.body.parentNode.appendChild(document.createElement("head")));a.appendChild(b)}function _loadJs(b){var a=document.createElement("script");a.type="text/javascript";a.charset="UTF-8";a.src=b;d(a)}function _loadCss(b){var a=document.createElement("link");a.type="text/css";a.rel="stylesheet";a.charset="UTF-8";a.href=b;d(a)}function _isNS(b){b=b.split(".");for(var a=window,c=0;c
2 |
3 |
4 |
5 |
6 |
7 | Server Error
8 |
58 |
59 |
60 |
61 |
62 |
Uh-oh, something went wrong!
63 |
We’re very sorry, our application wasn’t able to load this page. The
64 | team has been notified and we’ll fix it shortly.
65 |
If the problem persists or you'd like more information please contact {{ support_address }}.
8 | To add your own translation in a new language for a selected group, expand a language list above by clicking "Select Language" and adding a new language to a list of available translations.
9 | Then, follow the instructions in the "Translation Options" below.
10 |
11 |
12 |
13 |
14 | To add your own translation into the list of available translations for a selected group and a language above, follow the instructions in the "Translation Options" below.
15 |
16 |
17 |
18 |
19 | To view available translations or edit your own translations select a group and a language above, and follow the instructions in the "Translation Options" below.
20 |
21 |
22 |
23 |
24 | In "Translation Options" there are two modes available: edit and view. In edit mode you can add or change your own translations. In view mode you can view all available translations.
25 |
26 |
27 |
28 |
29 | When viewing a translation, hover over text to see the original version.
30 |
51 | {% include "includes/logo-header.html.jinja2" %}
52 |
53 |
54 |
55 |
Annotran Community Guidelines
56 |
57 |
58 |
Annotran Community Guidelines
59 |
Annotran is an open platform for translating web resources. We believe translations can improve conversations on the web and we hope communities thrive by sharing knowledge and collaborating across linguistic boundaries.
60 |
We have a set of guidelines for participating in public translations through annotran. By Public, we mean translations that are published to the Annotran Public channel.
61 |
62 |
63 |
Things you should do:
64 |
65 |
Be civil. When there’s disagreement, be respectful.
66 |
Make sure your translations are constructive, informative and well-intentioned.
67 |
68 |
69 |
70 |
Things you shouldn’t do:
71 |
72 |
Engage in illegal activity.
73 |
Circumvent copyright or post copyrighted works in translations.
74 |
Post translations of works that are under copyright without an open license.
75 |
Engage in personal attacks, trolling or harassment against other users.
76 |
Engage in marketing or advertising of your own offerings or others’.
77 |
Post explicit content.
78 |
Clutter the public channel with test messages.
79 |
80 |
81 |
82 |
Use of the Annotran service is a privilege, not a right. We reserve the right to limit, suspend or terminate users who violate these guidelines.