├── fiduswriter └── ojs │ ├── __init__.py │ ├── tests │ ├── __init__.py │ └── test_ojs.py │ ├── migrations │ ├── __init__.py │ ├── 0002_remove_submission_file_object.py │ ├── 0005_submission_authors.py │ ├── 0003_journal_templates.py │ ├── 0006_contributors_replaces_authors.py │ ├── 0007_move_contributors_from_submission_to_revision.py │ ├── 0008_reviewer_method.py │ ├── 0004_add_editor.py │ └── 0001_initial.py │ ├── static │ └── js │ │ ├── plugins │ │ └── editor │ │ │ └── ojs.js │ │ ├── modules │ │ └── ojs │ │ │ ├── index.js │ │ │ ├── submit_doc.js │ │ │ ├── contributor_state_plugin.js │ │ │ ├── templates.js │ │ │ ├── admin.js │ │ │ └── editor.js │ │ └── ojs_admin_register_journals.mjs │ ├── locale │ ├── bg │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ ├── djangojs.mo │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── de │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ ├── djangojs.mo │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── es │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ ├── djangojs.mo │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── fr │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ ├── djangojs.mo │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── it │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ ├── djangojs.mo │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── pt_BR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ ├── djangojs.mo │ │ │ ├── django.po │ │ │ └── djangojs.po │ └── BASE │ │ └── LC_MESSAGES │ │ └── django.po │ ├── apps.py │ ├── constants.py │ ├── templates │ └── admin │ │ └── ojs │ │ ├── journal │ │ └── change_list_object_tools.html │ │ └── register_journals.html │ ├── admin.py │ ├── token.py │ ├── urls.py │ ├── helpers.py │ └── models.py ├── requirements.txt ├── ci ├── configuration.py └── .coveragerc ├── dev-requirements.txt ├── MANIFEST.in ├── .gitignore ├── pyproject.toml ├── setup.py ├── .pre-commit-config.yaml ├── stylelint.config.js ├── README.md ├── .github └── workflows │ └── main.yml ├── lint └── django_import_resolver.js ├── biome.json └── LICENSE /fiduswriter/ojs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fiduswriter/ojs/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fiduswriter/ojs/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tenacity==8.2.2 2 | -------------------------------------------------------------------------------- /ci/configuration.py: -------------------------------------------------------------------------------- 1 | INSTALLED_APPS = ["ojs"] 2 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | pre-commit==4.0.1 2 | fiduswriter 3 | -------------------------------------------------------------------------------- /fiduswriter/ojs/static/js/plugins/editor/ojs.js: -------------------------------------------------------------------------------- 1 | export {EditorOJS} from "../../modules/ojs" 2 | -------------------------------------------------------------------------------- /fiduswriter/ojs/static/js/modules/ojs/index.js: -------------------------------------------------------------------------------- 1 | export {EditorOJS} from "./editor" 2 | export {AdminRegisterJournals} from "./admin" 3 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/bg/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiduswriter/fiduswriter-ojs/main/fiduswriter/ojs/locale/bg/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiduswriter/fiduswriter-ojs/main/fiduswriter/ojs/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/es/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiduswriter/fiduswriter-ojs/main/fiduswriter/ojs/locale/es/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiduswriter/fiduswriter-ojs/main/fiduswriter/ojs/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/it/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiduswriter/fiduswriter-ojs/main/fiduswriter/ojs/locale/it/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/bg/LC_MESSAGES/djangojs.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiduswriter/fiduswriter-ojs/main/fiduswriter/ojs/locale/bg/LC_MESSAGES/djangojs.mo -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/de/LC_MESSAGES/djangojs.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiduswriter/fiduswriter-ojs/main/fiduswriter/ojs/locale/de/LC_MESSAGES/djangojs.mo -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/es/LC_MESSAGES/djangojs.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiduswriter/fiduswriter-ojs/main/fiduswriter/ojs/locale/es/LC_MESSAGES/djangojs.mo -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/fr/LC_MESSAGES/djangojs.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiduswriter/fiduswriter-ojs/main/fiduswriter/ojs/locale/fr/LC_MESSAGES/djangojs.mo -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/it/LC_MESSAGES/djangojs.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiduswriter/fiduswriter-ojs/main/fiduswriter/ojs/locale/it/LC_MESSAGES/djangojs.mo -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiduswriter/fiduswriter-ojs/main/fiduswriter/ojs/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/pt_BR/LC_MESSAGES/djangojs.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiduswriter/fiduswriter-ojs/main/fiduswriter/ojs/locale/pt_BR/LC_MESSAGES/djangojs.mo -------------------------------------------------------------------------------- /fiduswriter/ojs/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class Config(AppConfig): 5 | name = "ojs" 6 | default_auto_field = "django.db.models.AutoField" 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | recursive-include fiduswriter/ojs/static * 4 | recursive-include fiduswriter/ojs/locale * 5 | recursive-include fiduswriter/ojs/templates * 6 | -------------------------------------------------------------------------------- /ci/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | parallel = True 3 | concurrency = thread,multiprocessing 4 | sigterm = True 5 | relative_files = True 6 | omit = 7 | *migrations* 8 | venv/* 9 | *virtualenv* 10 | configuration.py 11 | -------------------------------------------------------------------------------- /fiduswriter/ojs/static/js/ojs_admin_register_journals.mjs: -------------------------------------------------------------------------------- 1 | import {AdminRegisterJournals} from "./modules/ojs/admin.js" 2 | 3 | const theJournalRegister = new AdminRegisterJournals() 4 | 5 | theJournalRegister.init() 6 | 7 | window.theJournalRegister = theJournalRegister 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | dist/ 4 | fiduswriter_ojs.egg-info/ 5 | build/ 6 | __pycache__/ 7 | venv/ 8 | .envrc 9 | .direnv/ 10 | fiduswriter/.transpile 11 | fiduswriter/media 12 | fiduswriter/static-libs 13 | fiduswriter/static-transpile 14 | fiduswriter/static-collected 15 | fiduswriter/.coveragerc 16 | fiduswriter/configuration.py 17 | fiduswriter/fiduswriter.sql 18 | fiduswriter/screenshots 19 | .idea/ 20 | -------------------------------------------------------------------------------- /fiduswriter/ojs/migrations/0002_remove_submission_file_object.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 1.11.3 on 2017-09-08 11:23 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("ojs", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="submission", 15 | name="file_object", 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /fiduswriter/ojs/migrations/0005_submission_authors.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-08-08 14:25 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("ojs", "0004_add_editor"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="submission", 14 | name="authors", 15 | field=models.JSONField(default=list), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /fiduswriter/ojs/constants.py: -------------------------------------------------------------------------------- 1 | ROLE_ID_SITE_ADMIN = 1 2 | ROLE_ID_MANAGER = 16 3 | ROLE_ID_SUB_EDITOR = 17 4 | ROLE_ID_ASSISTANT = 4097 5 | EDITOR_ROLES = {1: "editor", 16: "editor", 17: "subeditor", 4097: "assistant"} 6 | EDITOR_ROLE_STAGE_RIGHTS = { 7 | 1: {1: "write", 3: "write", 4: "write", 5: "write"}, 8 | 16: {1: "write", 3: "write", 4: "write", 5: "write"}, 9 | 17: {1: "write", 3: "write", 4: "write", 5: "write"}, 10 | 4097: {1: "comment", 3: "comment", 4: "write-tracked", 5: "comment"}, 11 | } 12 | -------------------------------------------------------------------------------- /fiduswriter/ojs/migrations/0003_journal_templates.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.4 on 2019-09-06 10:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("document", "0019_remove_documenttemplate_definition_hash"), 9 | ("ojs", "0002_remove_submission_file_object"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="journal", 15 | name="templates", 16 | field=models.ManyToManyField(to="document.DocumentTemplate"), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /fiduswriter/ojs/templates/admin/ojs/journal/change_list_object_tools.html: -------------------------------------------------------------------------------- 1 | {% load i18n admin_urls %} 2 | 3 | {% block object-tools-items %} 4 | {% if has_add_permission %} 5 |
  • 6 | {% url cl.opts|admin_urlname:'add' as add_url %} 7 | 8 | {% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %} 9 | 10 |
  • 11 |
  • 12 | 13 | {% blocktrans %}Register journals{% endblocktrans %} 14 | 15 |
  • 16 | {% endif %} 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /fiduswriter/ojs/migrations/0006_contributors_replaces_authors.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-08-09 06:17 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("ojs", "0005_submission_authors"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="submission", 14 | name="authors", 15 | ), 16 | migrations.AddField( 17 | model_name="submission", 18 | name="contributors", 19 | field=models.JSONField(default=dict), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /fiduswriter/ojs/migrations/0007_move_contributors_from_submission_to_revision.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-08-09 09:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("ojs", "0006_contributors_replaces_authors"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="submission", 14 | name="contributors", 15 | ), 16 | migrations.AddField( 17 | model_name="submissionrevision", 18 | name="contributors", 19 | field=models.JSONField(default=dict), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /fiduswriter/ojs/migrations/0008_reviewer_method.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-08-10 18:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("ojs", "0007_move_contributors_from_submission_to_revision"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="reviewer", 14 | name="method", 15 | field=models.CharField( 16 | choices=[ 17 | ("open", "Open"), 18 | ("anonymous", "Anonymous"), 19 | ("doubleanonymous", "Double anonymous"), 20 | ], 21 | default="doubleanonymous", 22 | max_length=15, 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=65.6.3", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "fiduswriter-ojs" 7 | description = "A Fidus Writer plugin to connect to OJS." 8 | version = "4.0.0" 9 | readme = "README.md" 10 | license = {text = "AGPL-3.0-or-later"} 11 | authors = [ 12 | {email = "johannes@fiduswriter.org", name = "Johannes Wilm"}, 13 | ] 14 | classifiers=[ 15 | "Environment :: Web Environment", 16 | "Framework :: Django", 17 | "Framework :: Django :: 5.1", 18 | "Intended Audience :: Developers", 19 | "License :: OSI Approved :: GNU Affero General Public License v3", 20 | "Operating System :: OS Independent", 21 | "Programming Language :: Python", 22 | "Programming Language :: Python :: 3", 23 | "Topic :: Internet :: WWW/HTTP", 24 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content", 25 | ] 26 | dynamic = ["dependencies"] 27 | 28 | [project.urls] 29 | homepage = "https://www.fiduswriter.org/ojs-integration/" 30 | repository = "https://github.com/fiduswriter/fiduswriter-ojs/" 31 | 32 | [tool.setuptools.dynamic] 33 | dependencies = {file = "requirements.txt"} 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | from setuptools import find_namespace_packages, setup 4 | from setuptools.command.build_py import build_py as _build_py 5 | 6 | 7 | # From https://github.com/pypa/setuptools/pull/1574 8 | class build_py(_build_py): 9 | def find_package_modules(self, package, package_dir): 10 | modules = super().find_package_modules(package, package_dir) 11 | patterns = self._get_platform_patterns( 12 | self.exclude_package_data, 13 | package, 14 | package_dir, 15 | ) 16 | 17 | excluded_module_files = [] 18 | for pattern in patterns: 19 | excluded_module_files.extend(glob(pattern)) 20 | 21 | for f in excluded_module_files: 22 | for module in modules: 23 | if module[2] == f: 24 | modules.remove(module) 25 | return modules 26 | 27 | 28 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 29 | 30 | setup( 31 | packages=find_namespace_packages(), 32 | include_package_data=True, 33 | exclude_package_data={ 34 | "": ["configuration.py", "django-admin.py", "build/*"] 35 | }, 36 | cmdclass={"build_py": build_py}, 37 | ) 38 | -------------------------------------------------------------------------------- /fiduswriter/ojs/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.shortcuts import render 3 | from django.urls import path 4 | 5 | from . import models 6 | 7 | 8 | class SubmissionAdmin(admin.ModelAdmin): 9 | pass 10 | 11 | 12 | admin.site.register(models.Submission, SubmissionAdmin) 13 | 14 | 15 | class SubmissionRevisionAdmin(admin.ModelAdmin): 16 | pass 17 | 18 | 19 | admin.site.register(models.SubmissionRevision, SubmissionRevisionAdmin) 20 | 21 | 22 | class AuthorAdmin(admin.ModelAdmin): 23 | pass 24 | 25 | 26 | admin.site.register(models.Author, AuthorAdmin) 27 | 28 | 29 | class ReviewerAdmin(admin.ModelAdmin): 30 | pass 31 | 32 | 33 | admin.site.register(models.Reviewer, ReviewerAdmin) 34 | 35 | 36 | class EditorAdmin(admin.ModelAdmin): 37 | pass 38 | 39 | 40 | admin.site.register(models.Editor, EditorAdmin) 41 | 42 | 43 | class JournalAdmin(admin.ModelAdmin): 44 | def get_urls(self): 45 | urls = super().get_urls() 46 | extra_urls = [ 47 | path( 48 | "register_journal/", 49 | self.admin_site.admin_view(self.register_journal_view), 50 | ) 51 | ] 52 | urls = extra_urls + urls 53 | return urls 54 | 55 | def register_journal_view(self, request): 56 | response = {} 57 | return render(request, "admin/ojs/register_journals.html", response) 58 | 59 | 60 | admin.site.register(models.Journal, JournalAdmin) 61 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black-pre-commit-mirror 3 | rev: 24.10.0 4 | hooks: 5 | - id: black 6 | args: [--line-length=79] 7 | language_version: python3.13 8 | - repo: https://github.com/pycqa/flake8 9 | rev: '7.1.1' 10 | hooks: 11 | - id: flake8 12 | entry: flake8 --extend-ignore E203,E501 13 | - repo: https://github.com/biomejs/pre-commit 14 | rev: "75149f4e3b63c4df805860d7b04186d56dcbc05c" 15 | hooks: 16 | - id: biome-check 17 | #entry: biome check --files-ignore-unknown=true --no-errors-on-unmatched --fix --unsafe 18 | additional_dependencies: ["@biomejs/biome@1.9.2"] 19 | - repo: https://github.com/awebdeveloper/pre-commit-stylelint 20 | rev: "4200758f4cb2f53dd06898dd8dca35e4b8cfb785" 21 | hooks: 22 | - id: stylelint 23 | additional_dependencies: 24 | [ 25 | "stylelint@15.11.0", 26 | "stylelint-config-standard@34.0.0", 27 | "stylelint-value-no-unknown-custom-properties@5.0.0", 28 | "postcss@8.4.32", 29 | ] 30 | - repo: local 31 | hooks: 32 | - id: django-import-resolver 33 | name: Django Import Resolver 34 | entry: node lint/django_import_resolver.js 35 | language: node 36 | files: \.js$ 37 | additional_dependencies: ["acorn@8.10.0"] 38 | -------------------------------------------------------------------------------- /fiduswriter/ojs/token.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from django.utils.http import int_to_base36, base36_to_int 3 | from django.utils.crypto import constant_time_compare, salted_hmac 4 | 5 | # Tokens are used for login from OJS. This is to avoid exposing the api_key to 6 | # the client. 7 | # Partially originating in https://github.com/jpulgarin/django-tokenapi/ 8 | # Copyright 2011 Julian Pulgarin, Apache License 9 | 10 | 11 | def create_token(user, journal_key): 12 | timestamp = int(time()) 13 | return calculate_token(user, journal_key, timestamp) 14 | 15 | 16 | def calculate_token(user, journal_key, timestamp): 17 | ts_b36 = int_to_base36(timestamp) 18 | value = f"{user.pk}{user.password}{timestamp}" 19 | hash = salted_hmac(journal_key, value).hexdigest()[::2] 20 | return "%s-%s-%s" % (user.id, ts_b36, hash) 21 | 22 | 23 | def check_token(user, journal_key, token): 24 | # Check whether token is valid 25 | try: 26 | ts_b36 = token.split("-")[1] 27 | except ValueError: 28 | return False 29 | try: 30 | timestamp = base36_to_int(ts_b36) 31 | except ValueError: 32 | return False 33 | 34 | # Check that the timestamp/uid has not been tampered with 35 | if not constant_time_compare( 36 | calculate_token(user, journal_key, timestamp), token 37 | ): 38 | return False 39 | 40 | # Check timestamp is not older than 2 minutes 41 | new_timestamp = int(time()) 42 | if new_timestamp - timestamp > 120: 43 | return False 44 | 45 | return True 46 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/BASE/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2017-09-13 22:55+0000\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: ojs/templates/ojs/register_journals.html:3 21 | #: ojs/templates/ojs/register_journals.html:23 22 | msgid "Register journals" 23 | msgstr "" 24 | 25 | #: ojs/templates/ojs/register_journals.html:25 26 | msgid "" 27 | "\n" 28 | " Register the journals from a particular OJS instance. Insert " 29 | "first the\n" 30 | " URL and Key of the OJS installation. Decide then for each " 31 | "journal you\n" 32 | " want to connect with Fidus Writer which user should be " 33 | "registered as\n" 34 | " their editor.\n" 35 | " " 36 | msgstr "" 37 | 38 | #: ojs/templates/ojs/register_journals.html:33 39 | msgid "URL" 40 | msgstr "" 41 | 42 | #: ojs/templates/ojs/register_journals.html:34 43 | msgid "Key" 44 | msgstr "" 45 | 46 | #: ojs/templates/ojs/register_journals.html:35 47 | msgid "Query available journals" 48 | msgstr "" 49 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const {execSync} = require("child_process") 3 | 4 | function getFidusWriterPath() { 5 | try { 6 | const fwPath = execSync( 7 | "python -c \"import fiduswriter; print(next(filter(lambda path: '/site-packages/' in path, fiduswriter.__path__), ''))\"" 8 | ) 9 | .toString() 10 | .trim() 11 | if (fwPath) { 12 | return fwPath 13 | } 14 | throw new Error("Fidus Writer not found") 15 | } catch (error) { 16 | console.error( 17 | "Failed to find Fidus Writer installation:", 18 | error.message 19 | ) 20 | process.exit(1) 21 | } 22 | } 23 | 24 | const fidusWriterPath = getFidusWriterPath() 25 | 26 | module.exports = { 27 | extends: "stylelint-config-standard", 28 | plugins: ["stylelint-value-no-unknown-custom-properties"], 29 | rules: { 30 | "color-hex-length": "long", 31 | "max-nesting-depth": 2, 32 | "csstools/value-no-unknown-custom-properties": [ 33 | true, 34 | { 35 | importFrom: [ 36 | path.join(fidusWriterPath, "base/static/css/colors.css") 37 | ] 38 | } 39 | ], 40 | "selector-class-pattern": [ 41 | "^(([a-z][a-z0-9]*)(-[a-z0-9]+)*)|(ProseMirror(-[a-z0-9]+)*)$", 42 | { 43 | message: 44 | "Selector should use lowercase and separate words with hyphens (selector-class-pattern)" 45 | } 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /fiduswriter/ojs/migrations/0004_add_editor.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.4 on 2021-09-02 08:29 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [ 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ("ojs", "0003_journal_templates"), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="Editor", 17 | fields=[ 18 | ( 19 | "id", 20 | models.AutoField( 21 | auto_created=True, 22 | primary_key=True, 23 | serialize=False, 24 | verbose_name="ID", 25 | ), 26 | ), 27 | ("ojs_jid", models.PositiveIntegerField(default=0)), 28 | ("role", models.PositiveIntegerField(default=0)), 29 | ( 30 | "submission", 31 | models.ForeignKey( 32 | on_delete=django.db.models.deletion.CASCADE, 33 | to="ojs.submission", 34 | ), 35 | ), 36 | ( 37 | "user", 38 | models.ForeignKey( 39 | on_delete=django.db.models.deletion.CASCADE, 40 | to=settings.AUTH_USER_MODEL, 41 | ), 42 | ), 43 | ], 44 | options={ 45 | "unique_together": {("submission", "ojs_jid")}, 46 | }, 47 | ), 48 | ] 49 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/es/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Fidus Writer OJS backend\n" 8 | "Language: es\n" 9 | 10 | #: ojs/templates/admin/ojs/journal/change_list_object_tools.html:8 11 | msgid "Add %(name)s" 12 | msgstr "Añadir %(name)s" 13 | 14 | #: ojs/templates/admin/ojs/journal/change_list_object_tools.html:13 15 | #: ojs/templates/admin/ojs/register_journals.html:4 16 | #: ojs/templates/admin/ojs/register_journals.html:24 17 | msgid "Register journals" 18 | msgstr "Registrar revistas" 19 | 20 | #: ojs/templates/admin/ojs/register_journals.html:26 21 | msgid "\n" 22 | " Register the journals from a particular OJS instance. Insert first the\n" 23 | " URL and Key of the OJS installation. Decide then for each journal you\n" 24 | " want to connect with Fidus Writer which user should be registered as\n" 25 | " their editor.\n" 26 | " " 27 | msgstr "\n" 28 | "            Registrar las revistas de una instancia particular de OJS. Insertar primero el\n" 29 | "            URL y la clave de la instalación de OJS. A continuación, decide que revista\n" 30 | "            deseas conectar con Fidus Writer y qué usuario debe estar registrado como\n" 31 | "            su editor.\n" 32 | "             " 33 | 34 | #: ojs/templates/admin/ojs/register_journals.html:34 35 | msgid "URL" 36 | msgstr "URL" 37 | 38 | #: ojs/templates/admin/ojs/register_journals.html:35 39 | msgid "Key" 40 | msgstr "Llave" 41 | 42 | #: ojs/templates/admin/ojs/register_journals.html:36 43 | msgid "Query available journals" 44 | msgstr "Consultar revistas disponibles" 45 | 46 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/fr/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Fidus Writer OJS backend\n" 8 | "Language: fr\n" 9 | 10 | #: ojs/templates/admin/ojs/journal/change_list_object_tools.html:8 11 | msgid "Add %(name)s" 12 | msgstr "Ajouter %(name)s" 13 | 14 | #: ojs/templates/admin/ojs/journal/change_list_object_tools.html:13 15 | #: ojs/templates/admin/ojs/register_journals.html:4 16 | #: ojs/templates/admin/ojs/register_journals.html:24 17 | msgid "Register journals" 18 | msgstr "Enregistrer des revues" 19 | 20 | #: ojs/templates/admin/ojs/register_journals.html:26 21 | msgid "\n" 22 | " Register the journals from a particular OJS instance. Insert first the\n" 23 | " URL and Key of the OJS installation. Decide then for each journal you\n" 24 | " want to connect with Fidus Writer which user should be registered as\n" 25 | " their editor.\n" 26 | " " 27 | msgstr "\n" 28 | "            Enregistrez les journaux d'une instance OJS particulière. Insérez d'abord le\n" 29 | "            URL et clé de l'installation OJS. Décidez ensuite pour chaque journal auquel vous\n" 30 | "            souhaitez connecter Fidus Writer quel utilisateur doit être enregistré en tant qu'éditeur.\n" 31 | "             " 32 | 33 | #: ojs/templates/admin/ojs/register_journals.html:34 34 | msgid "URL" 35 | msgstr "URL" 36 | 37 | #: ojs/templates/admin/ojs/register_journals.html:35 38 | msgid "Key" 39 | msgstr "Clé" 40 | 41 | #: ojs/templates/admin/ojs/register_journals.html:36 42 | msgid "Query available journals" 43 | msgstr "Recherchez les journaux disponibles" 44 | 45 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/it/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Fidus Writer OJS backend\n" 8 | "Language: it\n" 9 | 10 | #: ojs/templates/admin/ojs/journal/change_list_object_tools.html:8 11 | msgid "Add %(name)s" 12 | msgstr "Aggiungi %(name)s" 13 | 14 | #: ojs/templates/admin/ojs/journal/change_list_object_tools.html:13 15 | #: ojs/templates/admin/ojs/register_journals.html:4 16 | #: ojs/templates/admin/ojs/register_journals.html:24 17 | msgid "Register journals" 18 | msgstr "Registrare le riviste" 19 | 20 | #: ojs/templates/admin/ojs/register_journals.html:26 21 | msgid "\n" 22 | " Register the journals from a particular OJS instance. Insert first the\n" 23 | " URL and Key of the OJS installation. Decide then for each journal you\n" 24 | " want to connect with Fidus Writer which user should be registered as\n" 25 | " their editor.\n" 26 | " " 27 | msgstr "\n" 28 | "            Registrare le riviste da una particolare istanza OJS. Inserisci prima il\n" 29 | "            URL e chiave dell'installazione di OJS. Decidi quindi per ogni diario che desideri\n" 30 | "            desidera connettersi con Fidus Writer con quale utente deve essere registrato\n" 31 | "            il loro editore.\n" 32 | "             " 33 | 34 | #: ojs/templates/admin/ojs/register_journals.html:34 35 | msgid "URL" 36 | msgstr "URL" 37 | 38 | #: ojs/templates/admin/ojs/register_journals.html:35 39 | msgid "Key" 40 | msgstr "Chiave" 41 | 42 | #: ojs/templates/admin/ojs/register_journals.html:36 43 | msgid "Query available journals" 44 | msgstr "Cerca riviste disponibili" 45 | 46 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/pt_BR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Fidus Writer OJS backend\n" 8 | "Language: pt-br\n" 9 | 10 | #: ojs/templates/admin/ojs/journal/change_list_object_tools.html:8 11 | msgid "Add %(name)s" 12 | msgstr "Adicionar %(name)s" 13 | 14 | #: ojs/templates/admin/ojs/journal/change_list_object_tools.html:13 15 | #: ojs/templates/admin/ojs/register_journals.html:4 16 | #: ojs/templates/admin/ojs/register_journals.html:24 17 | msgid "Register journals" 18 | msgstr "Registrar revistas" 19 | 20 | #: ojs/templates/admin/ojs/register_journals.html:26 21 | msgid "\n" 22 | " Register the journals from a particular OJS instance. Insert first the\n" 23 | " URL and Key of the OJS installation. Decide then for each journal you\n" 24 | " want to connect with Fidus Writer which user should be registered as\n" 25 | " their editor.\n" 26 | " " 27 | msgstr "\n" 28 | "            Registre as revistas de uma instância específica do OJS. Insira primeiro o\n" 29 | "            URL e chave da instalação do OJS. Decida então para cada revista que você\n" 30 | "            deseja conectar-se ao Fidus Writer, qual usuário deve ser registrado como\n" 31 | "            o editor deles.\n" 32 | "             " 33 | 34 | #: ojs/templates/admin/ojs/register_journals.html:34 35 | msgid "URL" 36 | msgstr "URL" 37 | 38 | #: ojs/templates/admin/ojs/register_journals.html:35 39 | msgid "Key" 40 | msgstr "Chave" 41 | 42 | #: ojs/templates/admin/ojs/register_journals.html:36 43 | msgid "Query available journals" 44 | msgstr "Consultar revistass disponíveis" 45 | 46 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/bg/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Fidus Writer OJS backend\n" 8 | "Language: bg\n" 9 | 10 | #: ojs/templates/admin/ojs/journal/change_list_object_tools.html:8 11 | msgid "Add %(name)s" 12 | msgstr "Добавете %(name)s" 13 | 14 | #: ojs/templates/admin/ojs/journal/change_list_object_tools.html:13 15 | #: ojs/templates/admin/ojs/register_journals.html:4 16 | #: ojs/templates/admin/ojs/register_journals.html:24 17 | msgid "Register journals" 18 | msgstr "Регистрирайте списания" 19 | 20 | #: ojs/templates/admin/ojs/register_journals.html:26 21 | msgid "\n" 22 | " Register the journals from a particular OJS instance. Insert first the\n" 23 | " URL and Key of the OJS installation. Decide then for each journal you\n" 24 | " want to connect with Fidus Writer which user should be registered as\n" 25 | " their editor.\n" 26 | " " 27 | msgstr "\n" 28 | "            Регистрирайте списанията от конкретен случай на OJS. Вмъкнете първо\n" 29 | "            URL адрес и ключ на инсталацията OJS. Вземете решение за всеки журнал, който сте\n" 30 | "            искате да се свържете с Fidus Writer, който потребител трябва да бъде регистриран като\n" 31 | "            техният редактор.\n" 32 | "             " 33 | 34 | #: ojs/templates/admin/ojs/register_journals.html:34 35 | msgid "URL" 36 | msgstr "Адрес" 37 | 38 | #: ojs/templates/admin/ojs/register_journals.html:35 39 | msgid "Key" 40 | msgstr "ключ" 41 | 42 | #: ojs/templates/admin/ojs/register_journals.html:36 43 | msgid "Query available journals" 44 | msgstr "Поискайте наличните списания" 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FidusWriter-OJS 2 | ===== 3 | 4 | FidusWriter-OJS is a Fidus writer plugin to connect a Fidus Writer instance 5 | with Open Journal Systems (OJS). 6 | This plugin has to be combined with the `OJS-FidusWriter plugin `_ for OJS. 7 | 8 | 9 | NOTE 10 | ---- 11 | 12 | These instructions are for developers only. If you are running Fidus Writer as a snap, see [these instructions](https://github.com/fiduswriter/fiduswriter/wiki/Installation-and-configuration-on-Ubuntu-using-snap#activate-plugins) on how to activate plugins. 13 | 14 | Installation 15 | ------------ 16 | 17 | 1. Within a Python virtual environment, install Fidus Writer with the plugin like this: 18 | 19 | pip install fiduswriter[ojs] 20 | 21 | 2. Create a Fidus Writer project in the current folder by running: 22 | 23 | fiduswriter startproject 24 | 25 | 3. There will now be a configuration.py file in the current folder. Add "ojs" to your INSTALLED_APPS setting in the configuration.py file 26 | like this:: 27 | 28 | INSTALLED_APPS += ( 29 | ... 30 | 'ojs', 31 | ) 32 | 33 | 34 | 4. Run Fidus Writer:: 35 | 36 | fiduswriter runserver 37 | 38 | 5. Following the install instructions of the `OJS-FidusWriter plugin `_ to connect 39 | the two plugins with each other. 40 | 41 | 42 | Credits 43 | ----------- 44 | 45 | This plugin has been developed by the `Opening Scholarly Communications in the Social Sciences (OSCOSS) `_ project, financed by the German Research Foundation (DFG) and executed by the University of Bonn and GESIS – Leibniz Institute for the Social Sciences. 46 | 47 | Original Developers: `Fakhri Momeni `_ and `Johannes Wilm `_ 48 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/de/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Fidus Writer OJS backend\n" 8 | "Language: de\n" 9 | 10 | #: ojs/templates/admin/ojs/journal/change_list_object_tools.html:8 11 | msgid "Add %(name)s" 12 | msgstr "Füge %(name)s hinzu" 13 | 14 | #: ojs/templates/admin/ojs/journal/change_list_object_tools.html:13 15 | #: ojs/templates/admin/ojs/register_journals.html:4 16 | #: ojs/templates/admin/ojs/register_journals.html:24 17 | msgid "Register journals" 18 | msgstr "Registriere Zeitschriften" 19 | 20 | #: ojs/templates/admin/ojs/register_journals.html:26 21 | msgid "\n" 22 | " Register the journals from a particular OJS instance. Insert first the\n" 23 | " URL and Key of the OJS installation. Decide then for each journal you\n" 24 | " want to connect with Fidus Writer which user should be registered as\n" 25 | " their editor.\n" 26 | " " 27 | msgstr "\n" 28 | " Registriere die Zeitschriften einer bestimmten OJS Instanz. Setze erst die\n" 29 | " URL und den Key einer bestimmten OJS Instanz in die entsprechenden Felder\n" 30 | " Wähle dann die Zeitschriften aus die du mit Fidus Writer verbinden willst\n" 31 | " und welcher Fidus Writer User als Editor für das Journal registriert\n" 32 | " werden soll." 33 | 34 | #: ojs/templates/admin/ojs/register_journals.html:34 35 | msgid "URL" 36 | msgstr "URL" 37 | 38 | #: ojs/templates/admin/ojs/register_journals.html:35 39 | msgid "Key" 40 | msgstr "Key" 41 | 42 | #: ojs/templates/admin/ojs/register_journals.html:36 43 | msgid "Query available journals" 44 | msgstr "Suche verfügbare Zeitschriften" 45 | 46 | -------------------------------------------------------------------------------- /fiduswriter/ojs/static/js/modules/ojs/submit_doc.js: -------------------------------------------------------------------------------- 1 | import {addAlert, post} from "../common" 2 | import {ShrinkFidus} from "../exporter/native/shrink" 3 | // Send an article submission to FW and OJS servers. 4 | 5 | export class SendDocSubmission { 6 | constructor({ 7 | doc, 8 | imageDB, 9 | bibDB, 10 | journalId, 11 | firstname, 12 | lastname, 13 | affiliation, 14 | authorUrl, 15 | abstract 16 | }) { 17 | this.doc = doc 18 | this.imageDB = imageDB 19 | this.bibDB = bibDB 20 | this.journalId = journalId 21 | this.firstname = firstname 22 | this.lastname = lastname 23 | this.affiliation = affiliation 24 | this.authorUrl = authorUrl 25 | this.abstract = abstract 26 | } 27 | 28 | init() { 29 | const shrinker = new ShrinkFidus(this.doc, this.imageDB, this.bibDB) 30 | 31 | shrinker.init().then(({shrunkImageDB, shrunkBibDB}) => { 32 | const content = JSON.parse(JSON.stringify(this.doc.content)) 33 | this.uploadRevision(content, shrunkBibDB, shrunkImageDB) 34 | }) 35 | } 36 | 37 | uploadRevision(content, bibDB, imageDB) { 38 | post("/api/ojs/author_submit/", { 39 | journal_id: this.journalId, 40 | firstname: this.firstname, 41 | lastname: this.lastname, 42 | affiliation: this.affiliation, 43 | author_url: this.authorUrl, 44 | doc_id: this.doc.id, 45 | title: this.doc.title, 46 | abstract: this.abstract, 47 | content: JSON.stringify(content), 48 | bibliography: JSON.stringify(bibDB), 49 | image_ids: Object.keys(imageDB) 50 | }) 51 | .then(() => addAlert("success", gettext("Article submitted"))) 52 | .catch(error => { 53 | addAlert("error", gettext("Article could not be submitted.")) 54 | throw error 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /fiduswriter/ojs/templates/admin/ojs/register_journals.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base_site.html" %} 2 | {% load i18n %} 3 | {% load transpile %} 4 | {% block title %}{% trans "Register journals" %}{% endblock %} 5 | {% block extrahead %}{{ block.super }} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% endblock %} 25 | {% block content %} 26 |
    27 |

    {% trans "Register journals" %}

    28 |

    29 | {% blocktrans %} 30 | Register the journals from a particular OJS instance. Insert first the 31 | URL and Key of the OJS installation. Decide then for each journal you 32 | want to connect with Fidus Writer which user should be registered as 33 | their editor. 34 | {% endblocktrans %} 35 |

    36 |
    37 |

    {% trans "URL" %}:

    38 |

    {% trans "Key" %}:

    39 | 40 | 41 |
    42 |
    43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /fiduswriter/ojs/static/js/modules/ojs/contributor_state_plugin.js: -------------------------------------------------------------------------------- 1 | import {DOMSerializer} from "prosemirror-model" 2 | import {Plugin, PluginKey} from "prosemirror-state" 3 | 4 | const key = new PluginKey("reviewContributorInput") 5 | 6 | class ReviewContributorsPartView { 7 | constructor(node, view, getPos, contributors) { 8 | this.node = node 9 | this.view = view 10 | this.getPos = getPos 11 | this.dom = document.createElement("div") 12 | this.dom.classList.add("doc-part") 13 | this.dom.classList.add(`doc-${this.node.type.name}`) 14 | this.dom.classList.add(`doc-${this.node.attrs.id}`) 15 | this.dom.classList.add(`doc-${this.node.attrs.id}-readonly`) 16 | this.dom.contentEditable = false 17 | if ( 18 | node.attrs.hidden || 19 | node.attrs.deleted || 20 | !contributors || 21 | !contributors.length 22 | ) { 23 | this.dom.dataset.hidden = true 24 | } else { 25 | // Put contributors content back into place in the display if they are available. 26 | // This allows us to display the contributors to those who should see them and 27 | // not to reviewers. 28 | const schema = this.node.type.schema 29 | const serializer = DOMSerializer.fromSchema(schema) 30 | contributors.forEach(json => 31 | this.dom.append( 32 | serializer.serializeNode(schema.nodeFromJSON(json)) 33 | ) 34 | ) 35 | } 36 | this.contentDOM = document.createElement("span") 37 | this.contentDOM.classList.add("contributors-inner") 38 | this.contentDOM.style.display = "none" 39 | this.dom.appendChild(this.contentDOM) 40 | } 41 | } 42 | 43 | export const reviewContributorPlugin = function (options) { 44 | return new Plugin({ 45 | key, 46 | props: { 47 | nodeViews: { 48 | contributors_part: (node, view, getPos) => 49 | new ReviewContributorsPartView( 50 | node, 51 | view, 52 | getPos, 53 | options.contributors[node.attrs.id] 54 | ) 55 | } 56 | } 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /fiduswriter/ojs/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import re_path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | re_path( 7 | "^add_reviewer/(?P[0-9]+)/(?P[0-9.]+)/$", 8 | views.add_reviewer, 9 | name="add_reviewer", 10 | ), 11 | re_path( 12 | "^accept_reviewer/(?P[0-9]+)/(?P[0-9.]+)/$", 13 | views.accept_reviewer, 14 | name="accept_reviewer", 15 | ), 16 | re_path( 17 | "^remove_reviewer/(?P[0-9]+)/(?P[0-9.]+)/$", 18 | views.remove_reviewer, 19 | name="remove_reviewer", 20 | ), 21 | re_path( 22 | "^check_revision/(?P[0-9]+)/(?P[0-9.]+)/$", 23 | views.check_revision_doc, 24 | name="check_revision_doc", 25 | ), 26 | re_path( 27 | "^revision/(?P[0-9]+)/(?P[0-9.]+)/$", 28 | views.open_revision_doc, 29 | name="open_revision_doc", 30 | ), 31 | re_path( 32 | "^get_login_token/$", views.get_login_token, name="get_login_token" 33 | ), 34 | re_path( 35 | "^create_copy/(?P[0-9]+)/$", 36 | views.create_copy, 37 | name="create_copy", 38 | ), 39 | re_path("^get_user/$", views.get_user, name="get_user"), 40 | re_path("^save_journal/$", views.save_journal, name="save_journal"), 41 | re_path("^get_doc_info/$", views.get_doc_info, name="get_doc_info"), 42 | re_path("^author_submit/$", views.author_submit, name="author_submit"), 43 | re_path( 44 | "^copyedit_draft_submit/$", 45 | views.copyedit_draft_submit, 46 | name="copyedit_draft_submit", 47 | ), 48 | re_path( 49 | "^reviewer_submit/$", views.reviewer_submit, name="reviewer_submit" 50 | ), 51 | re_path("^get_journals/$", views.get_journals, name="get_journals"), 52 | re_path( 53 | "^add_editor/(?P[0-9]+)/$", 54 | views.add_editor, 55 | name="add_editor", 56 | ), 57 | re_path( 58 | "^remove_editor/(?P[0-9]+)/$", 59 | views.remove_editor, 60 | name="remove_editor", 61 | ), 62 | re_path( 63 | "^add_author/(?P[0-9]+)/$", 64 | views.add_author, 65 | name="add_author", 66 | ), 67 | re_path( 68 | "^remove_author/(?P[0-9]+)/$", 69 | views.remove_author, 70 | name="remove_author", 71 | ), 72 | ] 73 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | pull_request: 9 | branches: 10 | - main 11 | - develop 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | jobs: 17 | pre-commit: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: actions/setup-python@v5 22 | with: 23 | python-version: "3.13" 24 | - name: Install dependencies 25 | run: | 26 | pip install pip --upgrade 27 | if grep version pyproject.toml | grep -q "dev"; 28 | then pip install https://github.com/fiduswriter/fiduswriter/archive/develop.zip; 29 | else pip install https://github.com/fiduswriter/fiduswriter/archive/main.zip; 30 | fi 31 | - uses: pre-commit/action@v3.0.1 32 | test: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Set up Python 36 | uses: actions/setup-python@v5 37 | with: 38 | python-version: "3.13" 39 | - name: Set up Node 40 | uses: actions/setup-node@v3 41 | with: 42 | node-version: 22 43 | - uses: actions/checkout@v4 44 | - uses: nanasess/setup-chromedriver@master 45 | - name: Install dependencies 46 | run: | 47 | sudo apt update 48 | sudo apt install libjpeg-dev python3-dev python3-pip gettext zlib1g-dev git nodejs build-essential 49 | pip install -r requirements.txt 50 | if grep version pyproject.toml | grep -q "dev"; 51 | then pip install https://github.com/fiduswriter/fiduswriter/archive/develop.zip; 52 | else pip install https://github.com/fiduswriter/fiduswriter/archive/main.zip; 53 | fi 54 | cd fiduswriter 55 | mv ../ci/configuration.py ./ 56 | mv ../ci/.coveragerc ./ 57 | pip install requests[security] 58 | pip install coverage 59 | pip install coveralls 60 | pip install packaging 61 | pip install webdriver-manager 62 | pip install selenium 63 | pip install wheel 64 | pip install pip --upgrade 65 | coverage run $(which fiduswriter) setup --no-static 66 | - name: Run test ojs 67 | uses: nick-invision/retry@v3 68 | with: 69 | timeout_minutes: 8 70 | max_attempts: 3 71 | retry_on: error 72 | command: | 73 | cd fiduswriter 74 | coverage run $(which fiduswriter) test ojs 75 | - name: Upload failed test screenshots 76 | if: ${{ failure() }} 77 | uses: actions/upload-artifact@v4 78 | with: 79 | name: failure-artifacts 80 | path: ${{ github.workspace }}/fiduswriter/screenshots/ 81 | - name: Coveralls 82 | run: | 83 | cd fiduswriter 84 | coverage combine 85 | coveralls --service=github 86 | env: 87 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 88 | -------------------------------------------------------------------------------- /fiduswriter/ojs/static/js/modules/ojs/templates.js: -------------------------------------------------------------------------------- 1 | import {escapeText} from "../common" 2 | 3 | /** Dialog to add a note to a revision before saving. */ 4 | 5 | export const firstSubmissionDialogTemplate = ({ 6 | journals, 7 | first_name, 8 | last_name, 9 | affiliation, 10 | abstract 11 | }) => 12 | `

    ${gettext("Submission information")}

    13 | 14 | 15 | 16 | 17 | 32 | 33 | 34 | 35 | 38 | 39 | 40 |

    ${gettext("Journal")} *

    18 |
    19 | 29 |
    30 |
    31 |

    ${gettext("Abstract")} *

    36 | 37 |
    41 |

    ${gettext("Corresponding author")}

    42 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |

    ${gettext("First name")} *

    47 | 48 |

    ${gettext("Last name")} *

    53 |

    ${gettext("Affiliation")}

    ${gettext("Webpage")}

    ` 65 | 66 | export const resubmissionDialogTemplate = () => 67 | `

    ${gettext("By pressing the submit button your resubmission will be sent to the journal")}


    68 |

    ${gettext("Be aware that this action cannot be undone!")}

    ` 69 | 70 | export const reviewSubmitDialogTemplate = () => 71 | ` 72 |


    73 | 74 |


    75 | 76 |


    85 |

    ${gettext("Be aware that this action cannot be undone!")}

    ` 86 | -------------------------------------------------------------------------------- /fiduswriter/ojs/helpers.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from httpx import AsyncClient 3 | from tenacity import retry, stop_after_attempt, wait_fixed 4 | 5 | from document.models import Document 6 | from usermedia.models import Image, DocumentImage 7 | from django.conf import settings 8 | from django.core.files import File 9 | 10 | 11 | def create_doc( 12 | owner, 13 | template, 14 | title, 15 | content, 16 | bibliography, 17 | images, 18 | comments, 19 | submission_id, 20 | revision_version, 21 | ): 22 | doc = Document() 23 | doc.owner = owner 24 | doc.template = template 25 | doc.title = title 26 | doc.content = content 27 | doc.bibliography = bibliography 28 | doc.path = f"/Submission {submission_id}/{title.replace('/', '')} ({revision_version})" 29 | doc.comments = comments 30 | doc.save() 31 | 32 | for image in images: 33 | if image is None: 34 | image = Image() 35 | image.uploader = owner 36 | f = open( 37 | path.join(settings.PROJECT_PATH, "base/static/img/error.png") 38 | ) 39 | image.image.save("error.png", File(f)) 40 | image.save() 41 | 42 | DocumentImage.objects.create(document=doc, image=image, title="") 43 | 44 | return doc 45 | 46 | 47 | async def create_doc_async( 48 | owner, 49 | template, 50 | title, 51 | content, 52 | bibliography, 53 | images, 54 | comments, 55 | submission_id, 56 | revision_version, 57 | ): 58 | doc = Document() 59 | doc.owner = owner 60 | doc.template = template 61 | doc.title = title 62 | doc.content = content 63 | doc.bibliography = bibliography 64 | doc.path = f"/Submission {submission_id}/{title.replace('/', '')} ({revision_version})" 65 | doc.comments = comments 66 | await doc.asave() 67 | 68 | for image in images: 69 | if image is None: 70 | image = Image() 71 | image.uploader = owner 72 | f = open( 73 | path.join(settings.PROJECT_PATH, "base/static/img/error.png") 74 | ) 75 | image.image.save("error.png", File(f)) 76 | await image.asave() 77 | 78 | await DocumentImage.objects.acreate( 79 | document=doc, image=image, title="" 80 | ) 81 | 82 | return doc 83 | 84 | 85 | def copy_revision(revision, old_version_stage, new_version_stage, new_version): 86 | images = [] 87 | doc_images = revision.document.documentimage_set.all() 88 | for doc_image in doc_images: 89 | images.append(doc_image.image) 90 | content = revision.document.content 91 | if old_version_stage < 3 and new_version_stage == 3: 92 | # Remove author information at start of review process. 93 | revision.contributors = {} 94 | for part in content["content"]: 95 | if ( 96 | "type" in part 97 | and part["type"] == "contributors_part" 98 | and "content" in part 99 | and "attrs" in part 100 | and "id" in part["attrs"] 101 | ): 102 | revision.contributors[part["attrs"]["id"]] = part["content"] 103 | part["content"] = [] 104 | elif ( 105 | old_version_stage == 3 106 | and new_version_stage > 3 107 | and len(revision.contributors) 108 | ): 109 | # Readd author information after review process. 110 | for part in content["content"]: 111 | if ( 112 | "type" in part 113 | and part["type"] == "contributors_part" 114 | and "attrs" in part 115 | and part["attrs"]["id"] in revision.contributors 116 | ): 117 | part["content"] = revision.contributors[part["attrs"]["id"]] 118 | revision.contributors = {} 119 | document = create_doc( 120 | revision.submission.journal.editor, 121 | revision.document.template, 122 | revision.document.title, 123 | content, 124 | revision.document.bibliography, 125 | images, 126 | revision.document.comments, 127 | revision.submission.id, 128 | new_version, 129 | ) 130 | 131 | # Copy revision 132 | revision.pk = None 133 | revision.document = document 134 | revision.version = new_version 135 | revision.save() 136 | 137 | return revision 138 | 139 | 140 | @retry(reraise=True, stop=stop_after_attempt(10), wait=wait_fixed(3)) 141 | async def send_async(request, timeout=40): 142 | async with AsyncClient(timeout=timeout) as client: 143 | response = await client.send(request) 144 | response.raise_for_status() 145 | return response 146 | -------------------------------------------------------------------------------- /fiduswriter/ojs/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models.deletion import CASCADE 3 | from django.conf import settings 4 | 5 | from document.models import Document, DocumentTemplate 6 | 7 | 8 | # A Journal registered with a particular OJS installation 9 | class Journal(models.Model): 10 | ojs_url = models.CharField(max_length=512) 11 | ojs_key = models.CharField(max_length=512) 12 | ojs_jid = models.PositiveIntegerField() # _jid as _id is foreign key 13 | templates = models.ManyToManyField(DocumentTemplate) 14 | name = models.CharField(max_length=512) 15 | editor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=CASCADE) 16 | 17 | class Meta(object): 18 | unique_together = (("ojs_url", "ojs_jid"),) 19 | 20 | def __str__(self): 21 | return self.name 22 | 23 | 24 | # A submission registered with OJS 25 | class Submission(models.Model): 26 | submitter = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=CASCADE) 27 | journal = models.ForeignKey(Journal, on_delete=CASCADE) 28 | ojs_jid = models.PositiveIntegerField(default=0) # ID in OJS 29 | 30 | def __str__(self): 31 | return "{ojs_jid} in {journal} by {submitter}".format( 32 | ojs_jid=self.ojs_jid, 33 | journal=self.journal.name, 34 | submitter=self.submitter.username, 35 | ) 36 | 37 | 38 | # An author registered with OJS and also registered here 39 | # Authors are the same for an entire submission. 40 | class Author(models.Model): 41 | user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=CASCADE) 42 | submission = models.ForeignKey(Submission, on_delete=CASCADE) 43 | ojs_jid = models.PositiveIntegerField(default=0) # ID in OJS 44 | 45 | class Meta(object): 46 | unique_together = ("submission", "ojs_jid") 47 | 48 | def __str__(self): 49 | return "{username} ({ojs_jid})".format( 50 | username=self.user.username, ojs_jid=self.ojs_jid 51 | ) 52 | 53 | 54 | # Within each submission, there is a new revision for each revision 55 | class SubmissionRevision(models.Model): 56 | submission = models.ForeignKey(Submission, on_delete=CASCADE) 57 | # version = stage ID + "." + round + "." + (0 for reviewer or 5 for author) 58 | # version) 59 | # For example: 60 | # submission version: "1.0.0" 61 | # Author version of 5th external review (stage ID=3): "3.5.5" 62 | # The version should increase like a computer version number. Not all 63 | # numbers are included. 64 | version = models.CharField(max_length=8, default="1.0.0") 65 | document = models.ForeignKey(Document, on_delete=CASCADE) 66 | # Contributor information if it has been removed from the document 67 | contributors = models.JSONField(default=dict) 68 | 69 | def __str__(self): 70 | return "{ojs_jid} (v{version}) in {journal} by {submitter}".format( 71 | ojs_jid=self.submission.ojs_jid, 72 | version=self.version, 73 | journal=self.submission.journal.name, 74 | submitter=self.submission.submitter.username, 75 | ) 76 | 77 | 78 | REVIEW_METHODS = [ 79 | ("open", "Open"), # Author and reviewer can see one-another's names. 80 | ( 81 | "anonymous", 82 | "Anonymous", 83 | ), # Reviewer can see name of author, but author cannot see name of reviewer. 84 | ( 85 | "doubleanonymous", 86 | "Double anonymous", 87 | ), # Author and reviewer cannot see one-another's names. 88 | ] 89 | 90 | 91 | # A reviewer registered with OJS and also registered here 92 | # Reviewers can differ from revision to revision. 93 | class Reviewer(models.Model): 94 | user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=CASCADE) 95 | revision = models.ForeignKey(SubmissionRevision, on_delete=CASCADE) 96 | ojs_jid = models.PositiveIntegerField(default=0) # ID in OJS 97 | method = models.CharField( 98 | max_length=15, default="doubleanonymous", choices=REVIEW_METHODS 99 | ) 100 | 101 | class Meta(object): 102 | unique_together = ("revision", "ojs_jid") 103 | 104 | def __str__(self): 105 | return "{username} ({ojs_jid})".format( 106 | username=self.user.username, ojs_jid=self.ojs_jid 107 | ) 108 | 109 | 110 | # An editor/partial editor registered with OJS and also registered here 111 | # Editors are the same for an entire submission. 112 | class Editor(models.Model): 113 | user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=CASCADE) 114 | submission = models.ForeignKey(Submission, on_delete=CASCADE) 115 | ojs_jid = models.PositiveIntegerField(default=0) # ID in OJS 116 | role = models.PositiveIntegerField( 117 | default=0 118 | ) # role (SITE_ADMIN/MANAGER/SUB_EDITOR/ASSISTANT) in OJS 119 | 120 | class Meta(object): 121 | unique_together = ("submission", "ojs_jid") 122 | 123 | def __str__(self): 124 | return "User: {username} (OJS User-ID: {user_id}), Submission: {ojs_jid} in {journal} by {submitter}".format( 125 | username=self.user.username, 126 | user_id=self.ojs_jid, 127 | ojs_jid=self.submission.ojs_jid, 128 | journal=self.submission.journal.name, 129 | submitter=self.submission.submitter.username, 130 | ) 131 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/pt_BR/LC_MESSAGES/djangojs.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Fidus Writer OJS\n" 8 | "Language: pt-br\n" 9 | 10 | #: ojs/static/js/modules/ojs/admin.js:57 11 | msgid "Provide a URL for the OJS server and the key to access it." 12 | msgstr "Forneça um URL para o servidor OJS e a chave para acessá-lo." 13 | 14 | #: ojs/static/js/modules/ojs/admin.js:77 15 | msgid "Cannot find Fidus Writer user corresponding to email: " 16 | msgstr "Não é possível encontrar o usuário do Fidus Writer correspondente ao email: " 17 | 18 | #: ojs/static/js/modules/ojs/admin.js:82 19 | msgid "Editor" 20 | msgstr "Editor" 21 | 22 | #: ojs/static/js/modules/ojs/admin.js:82 23 | msgid "Register" 24 | msgstr "Cadastre-se" 25 | 26 | #: ojs/static/js/modules/ojs/admin.js:86 27 | msgid "Could not connect to OJS server." 28 | msgstr "Não foi possível conectar ao servidor OJS." 29 | 30 | #: ojs/static/js/modules/ojs/admin.js:106 31 | msgid "Editor and journal name need to be filled out." 32 | msgstr "O nome do editor e da revista precisa ser preenchido." 33 | 34 | #: ojs/static/js/modules/ojs/admin.js:113 35 | msgid "Editor needs to be the ID number of the editor user." 36 | msgstr "O editor precisa ser o número ID do usuário do editor." 37 | 38 | #: ojs/static/js/modules/ojs/admin.js:125 39 | msgid "Journal saved." 40 | msgstr "Revista salva." 41 | 42 | #: ojs/static/js/modules/ojs/admin.js:127 43 | msgid "Journal already present on server." 44 | msgstr "A revista já está presente no servidor" 45 | 46 | #: ojs/static/js/modules/ojs/admin.js:133 47 | msgid "Could not save journal. Please check form." 48 | msgstr "Não foi possível salvar a revista. Por favor, verifique o formulário." 49 | 50 | #: ojs/static/js/modules/ojs/editor.js:26 51 | msgid "Could not obtain submission info." 52 | msgstr "Não foi possível obter as informações do envio." 53 | 54 | #: ojs/static/js/modules/ojs/editor.js:39 55 | #: ojs/static/js/modules/ojs/editor.js:41 56 | msgid "Submit to journal" 57 | msgstr "Enviar para a revista" 58 | 59 | #: ojs/static/js/modules/ojs/editor.js:67 60 | msgid "Submit" 61 | msgstr "Enviar" 62 | 63 | #: ojs/static/js/modules/ojs/editor.js:78 64 | msgid "Firstname, lastname and abstract are obligatory fields!" 65 | msgstr "Nome, sobrenome e resumo são campos obrigatórios!" 66 | 67 | #: ojs/static/js/modules/ojs/editor.js:102 68 | msgid "Complete missing information and choose journal" 69 | msgstr "Preencha as informações ausentes e escolha a revista" 70 | 71 | #: ojs/static/js/modules/ojs/editor.js:118 72 | #: ojs/static/js/modules/ojs/editor.js:173 73 | msgid "Send" 74 | msgstr "Enviar" 75 | 76 | #: ojs/static/js/modules/ojs/editor.js:130 77 | msgid "Submit revision" 78 | msgstr "Reenviar" 79 | 80 | #: ojs/static/js/modules/ojs/editor.js:140 81 | msgid "Resubmission successful" 82 | msgstr "Reenvio com êxito" 83 | 84 | #: ojs/static/js/modules/ojs/editor.js:143 85 | #: ojs/static/js/modules/ojs/editor.js:222 86 | msgid "Review could not be submitted." 87 | msgstr "Não foi possível enviar a revisão." 88 | 89 | #: ojs/static/js/modules/ojs/editor.js:188 90 | msgid "Leave your messages for editor and authors" 91 | msgstr "Deixe suas mensagens para o editor e os autores" 92 | 93 | #: ojs/static/js/modules/ojs/editor.js:207 94 | msgid "Fill out all fields before submitting!" 95 | msgstr "Preencha todos os campos antes de enviar!" 96 | 97 | #: ojs/static/js/modules/ojs/editor.js:219 98 | msgid "Review submitted" 99 | msgstr "Revisão enviada" 100 | 101 | #: ojs/static/js/modules/ojs/submit_doc.js:60 102 | msgid "Article submitted" 103 | msgstr "Artigo enviado" 104 | 105 | #: ojs/static/js/modules/ojs/submit_doc.js:61 106 | msgid "Article could not be submitted." 107 | msgstr "Não foi possível enviar o artigo." 108 | 109 | #: ojs/static/js/modules/ojs/templates.js:10 110 | msgid "Submission information" 111 | msgstr "Informações de envio" 112 | 113 | #: ojs/static/js/modules/ojs/templates.js:10 114 | msgid "Journal" 115 | msgstr "Revista" 116 | 117 | #: ojs/static/js/modules/ojs/templates.js:10 118 | msgid "Abstract" 119 | msgstr "Resumo" 120 | 121 | #: ojs/static/js/modules/ojs/templates.js:10 122 | msgid "Corresponding author" 123 | msgstr "Autor correspondente" 124 | 125 | #: ojs/static/js/modules/ojs/templates.js:10 126 | msgid "First name" 127 | msgstr "Nome" 128 | 129 | #: ojs/static/js/modules/ojs/templates.js:10 130 | msgid "Last name" 131 | msgstr "Sobrenome" 132 | 133 | #: ojs/static/js/modules/ojs/templates.js:10 134 | msgid "Affiliation" 135 | msgstr "Afiliação" 136 | 137 | #: ojs/static/js/modules/ojs/templates.js:10 138 | msgid "Webpage" 139 | msgstr "Página web" 140 | 141 | #: ojs/static/js/modules/ojs/templates.js:11 142 | msgid "By pressing the submit button your resubmission will be sent to the journal" 143 | msgstr "Ao pressionar o botão enviar seu será enviado para a revista" 144 | 145 | #: ojs/static/js/modules/ojs/templates.js:11 146 | #: ojs/static/js/modules/ojs/templates.js:12 147 | msgid "Be aware that this action cannot be undone!" 148 | msgstr "Esteja ciente de que esta ação não pode ser desfeita!" 149 | 150 | #: ojs/static/js/modules/ojs/templates.js:12 151 | msgid "Message for editor" 152 | msgstr "Mensagem para o editor" 153 | 154 | #: ojs/static/js/modules/ojs/templates.js:12 155 | msgid "Message for editor and authors" 156 | msgstr "Mensagem para o editor e os autores" 157 | 158 | #: ojs/static/js/modules/ojs/templates.js:12 159 | msgid "Recommendation" 160 | msgstr "Recomendação" 161 | 162 | #: ojs/static/js/modules/ojs/templates.js:12 163 | msgid "Choose One" 164 | msgstr "Escolha um" 165 | 166 | #: ojs/static/js/modules/ojs/templates.js:12 167 | msgid "Accept Submission" 168 | msgstr "Aceitar envio" 169 | 170 | #: ojs/static/js/modules/ojs/templates.js:12 171 | msgid "Revisions Required" 172 | msgstr "Revisões necessárias" 173 | 174 | #: ojs/static/js/modules/ojs/templates.js:12 175 | msgid "Resubmit for Review" 176 | msgstr "Reenviar para revisão" 177 | 178 | #: ojs/static/js/modules/ojs/templates.js:12 179 | msgid "Resubmit Elsewhere" 180 | msgstr "Reenviar em outro lugar" 181 | 182 | #: ojs/static/js/modules/ojs/templates.js:12 183 | msgid "Decline Submission" 184 | msgstr "Declinar envio" 185 | 186 | #: ojs/static/js/modules/ojs/templates.js:12 187 | msgid "See Comments" 188 | msgstr "Ver comentários" 189 | 190 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/es/LC_MESSAGES/djangojs.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Fidus Writer OJS\n" 8 | "Language: es\n" 9 | 10 | #: ojs/static/js/modules/ojs/admin.js:57 11 | msgid "Provide a URL for the OJS server and the key to access it." 12 | msgstr "Proporcione una URL para el servidor OJS y la llave para acceder a él." 13 | 14 | #: ojs/static/js/modules/ojs/admin.js:77 15 | msgid "Cannot find Fidus Writer user corresponding to email: " 16 | msgstr "No se puede encontrar el usuario de Fidus Writer correspondiente al correo electrónico: " 17 | 18 | #: ojs/static/js/modules/ojs/admin.js:82 19 | msgid "Editor" 20 | msgstr "Editor" 21 | 22 | #: ojs/static/js/modules/ojs/admin.js:82 23 | msgid "Register" 24 | msgstr "Registrar" 25 | 26 | #: ojs/static/js/modules/ojs/admin.js:86 27 | msgid "Could not connect to OJS server." 28 | msgstr "No se pudo conectar con el servidor OJS." 29 | 30 | #: ojs/static/js/modules/ojs/admin.js:106 31 | msgid "Editor and journal name need to be filled out." 32 | msgstr "El nombre del editor y la revista deben completarse." 33 | 34 | #: ojs/static/js/modules/ojs/admin.js:113 35 | msgid "Editor needs to be the ID number of the editor user." 36 | msgstr "El editor debe ser el número ID del usuario del editor." 37 | 38 | #: ojs/static/js/modules/ojs/admin.js:125 39 | msgid "Journal saved." 40 | msgstr "Revista guardada." 41 | 42 | #: ojs/static/js/modules/ojs/admin.js:127 43 | msgid "Journal already present on server." 44 | msgstr "La revista ya existe en el servidor." 45 | 46 | #: ojs/static/js/modules/ojs/admin.js:133 47 | msgid "Could not save journal. Please check form." 48 | msgstr "No se pudo guardar la revista. Por favor revise el formulario." 49 | 50 | #: ojs/static/js/modules/ojs/editor.js:26 51 | msgid "Could not obtain submission info." 52 | msgstr "No se pudo obtener la información de envío." 53 | 54 | #: ojs/static/js/modules/ojs/editor.js:39 55 | #: ojs/static/js/modules/ojs/editor.js:41 56 | msgid "Submit to journal" 57 | msgstr "Enviar a la revista" 58 | 59 | #: ojs/static/js/modules/ojs/editor.js:67 60 | msgid "Submit" 61 | msgstr "Enviar" 62 | 63 | #: ojs/static/js/modules/ojs/editor.js:78 64 | msgid "Firstname, lastname and abstract are obligatory fields!" 65 | msgstr "¡Nombre, apellido y resumen son campos obligatorios!" 66 | 67 | #: ojs/static/js/modules/ojs/editor.js:102 68 | msgid "Complete missing information and choose journal" 69 | msgstr "Complete la información faltante y elija la revista" 70 | 71 | #: ojs/static/js/modules/ojs/editor.js:118 72 | #: ojs/static/js/modules/ojs/editor.js:173 73 | msgid "Send" 74 | msgstr "Enviar" 75 | 76 | #: ojs/static/js/modules/ojs/editor.js:130 77 | msgid "Submit revision" 78 | msgstr "Reenviar" 79 | 80 | #: ojs/static/js/modules/ojs/editor.js:140 81 | msgid "Resubmission successful" 82 | msgstr "Reenvío exitoso" 83 | 84 | #: ojs/static/js/modules/ojs/editor.js:143 85 | #: ojs/static/js/modules/ojs/editor.js:222 86 | msgid "Review could not be submitted." 87 | msgstr "No se pudo enviar la revisión." 88 | 89 | #: ojs/static/js/modules/ojs/editor.js:188 90 | msgid "Leave your messages for editor and authors" 91 | msgstr "Deja tus mensajes para el editor y los autores" 92 | 93 | #: ojs/static/js/modules/ojs/editor.js:207 94 | msgid "Fill out all fields before submitting!" 95 | msgstr "¡Complete todos los campos antes de enviar!" 96 | 97 | #: ojs/static/js/modules/ojs/editor.js:219 98 | msgid "Review submitted" 99 | msgstr "Revisión enviada" 100 | 101 | #: ojs/static/js/modules/ojs/submit_doc.js:60 102 | msgid "Article submitted" 103 | msgstr "Artículo enviado" 104 | 105 | #: ojs/static/js/modules/ojs/submit_doc.js:61 106 | msgid "Article could not be submitted." 107 | msgstr "No se pudo enviar el artículo." 108 | 109 | #: ojs/static/js/modules/ojs/templates.js:10 110 | msgid "Submission information" 111 | msgstr "Información de envío" 112 | 113 | #: ojs/static/js/modules/ojs/templates.js:10 114 | msgid "Journal" 115 | msgstr "Revista" 116 | 117 | #: ojs/static/js/modules/ojs/templates.js:10 118 | msgid "Abstract" 119 | msgstr "Resumen" 120 | 121 | #: ojs/static/js/modules/ojs/templates.js:10 122 | msgid "Corresponding author" 123 | msgstr "Autor correspondiente" 124 | 125 | #: ojs/static/js/modules/ojs/templates.js:10 126 | msgid "First name" 127 | msgstr "Nombre" 128 | 129 | #: ojs/static/js/modules/ojs/templates.js:10 130 | msgid "Last name" 131 | msgstr "Apellido" 132 | 133 | #: ojs/static/js/modules/ojs/templates.js:10 134 | msgid "Affiliation" 135 | msgstr "Afiliación" 136 | 137 | #: ojs/static/js/modules/ojs/templates.js:10 138 | msgid "Webpage" 139 | msgstr "Página web" 140 | 141 | #: ojs/static/js/modules/ojs/templates.js:11 142 | msgid "By pressing the submit button your resubmission will be sent to the journal" 143 | msgstr "Al presionar el botón de enviar su reenvío será enviado a la revista" 144 | 145 | #: ojs/static/js/modules/ojs/templates.js:11 146 | #: ojs/static/js/modules/ojs/templates.js:12 147 | msgid "Be aware that this action cannot be undone!" 148 | msgstr "¡Tenga en cuenta que esta acción no se puede deshacer!" 149 | 150 | #: ojs/static/js/modules/ojs/templates.js:12 151 | msgid "Message for editor" 152 | msgstr "Mensaje para el editor" 153 | 154 | #: ojs/static/js/modules/ojs/templates.js:12 155 | msgid "Message for editor and authors" 156 | msgstr "Mensaje para el editor y los autores" 157 | 158 | #: ojs/static/js/modules/ojs/templates.js:12 159 | msgid "Recommendation" 160 | msgstr "Recomendación" 161 | 162 | #: ojs/static/js/modules/ojs/templates.js:12 163 | msgid "Choose One" 164 | msgstr "Elige uno" 165 | 166 | #: ojs/static/js/modules/ojs/templates.js:12 167 | msgid "Accept Submission" 168 | msgstr "Aceptar envío" 169 | 170 | #: ojs/static/js/modules/ojs/templates.js:12 171 | msgid "Revisions Required" 172 | msgstr "Revisiones requeridas" 173 | 174 | #: ojs/static/js/modules/ojs/templates.js:12 175 | msgid "Resubmit for Review" 176 | msgstr "Reenviar para revisión" 177 | 178 | #: ojs/static/js/modules/ojs/templates.js:12 179 | msgid "Resubmit Elsewhere" 180 | msgstr "Reenviar a otro lugar" 181 | 182 | #: ojs/static/js/modules/ojs/templates.js:12 183 | msgid "Decline Submission" 184 | msgstr "Rechazar envío" 185 | 186 | #: ojs/static/js/modules/ojs/templates.js:12 187 | msgid "See Comments" 188 | msgstr "Ver comentarios" 189 | 190 | -------------------------------------------------------------------------------- /lint/django_import_resolver.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const fs = require("fs") 3 | const acorn = require("acorn") 4 | const {execSync} = require("child_process") 5 | 6 | const EXCEPTIONS = ["../../../mathlive/opf_includes"] 7 | 8 | function getFidusWriterPath() { 9 | try { 10 | const fwPath = execSync( 11 | "python -c \"import fiduswriter; print(next(filter(lambda path: '/site-packages/' in path, fiduswriter.__path__), ''))\"" 12 | ) 13 | .toString() 14 | .trim() 15 | if (fwPath) { 16 | return fwPath 17 | } 18 | throw new Error("Fidus Writer not found") 19 | } catch (error) { 20 | console.error( 21 | "Failed to find Fidus Writer installation:", 22 | error.message 23 | ) 24 | process.exit(1) 25 | } 26 | } 27 | 28 | function isFile(file) { 29 | let stat 30 | try { 31 | stat = fs.statSync(file) 32 | } catch (e) { 33 | if (e && (e.code === "ENOENT" || e.code === "ENOTDIR")) { 34 | return false 35 | } 36 | throw e 37 | } 38 | return stat.isFile() || stat.isFIFO() 39 | } 40 | 41 | function getAppsPaths(rootDir) { 42 | const appsPaths = [] 43 | const subdirs = fs.readdirSync(rootDir, {withFileTypes: true}) 44 | subdirs.forEach(subdir => { 45 | if (subdir.isDirectory()) { 46 | const staticPath = path.join(rootDir, subdir.name, "static") 47 | if ( 48 | fs.existsSync(staticPath) && 49 | fs.lstatSync(staticPath).isDirectory() 50 | ) { 51 | appsPaths.push(path.join(rootDir, subdir.name)) 52 | } 53 | } 54 | }) 55 | return appsPaths 56 | } 57 | 58 | function resolveFilelocation(source, file, appsPaths) { 59 | const returnValue = {found: false, path: null} 60 | const fullPath = path.resolve(path.dirname(file), source) 61 | 62 | if (fullPath.includes("/plugins/")) { 63 | returnValue.found = true 64 | returnValue.path = null 65 | return returnValue 66 | } 67 | 68 | // Check in plugin and fidus writer apps 69 | appsPaths.find(appPath => { 70 | const resolvedPath = fullPath.replace( 71 | /.*\/static\/js\//g, 72 | `${appPath}/static/js/` 73 | ) 74 | if (isFile(`${resolvedPath}.js`)) { 75 | returnValue.path = `${resolvedPath}.js` 76 | returnValue.found = true 77 | return true 78 | } 79 | if (isFile(`${resolvedPath}/index.js`)) { 80 | returnValue.path = `${resolvedPath}/index.js` 81 | returnValue.found = true 82 | return true 83 | } 84 | 85 | return false 86 | }) 87 | 88 | return returnValue 89 | } 90 | 91 | function checkExports(filePath, importedNames, sourcePath) { 92 | const content = fs.readFileSync(filePath, "utf-8") 93 | const ast = acorn.parse(content, { 94 | sourceType: "module", 95 | ecmaVersion: "latest" 96 | }) 97 | 98 | const exportedNames = new Set() 99 | 100 | ast.body.forEach(node => { 101 | if (node.type === "ExportNamedDeclaration") { 102 | if (node.declaration) { 103 | if (node.declaration.id) { 104 | exportedNames.add(node.declaration.id.name) 105 | } else if (node.declaration.declarations) { 106 | node.declaration.declarations.forEach(decl => { 107 | if (decl.id && decl.id.name) { 108 | exportedNames.add(decl.id.name) 109 | } 110 | }) 111 | } 112 | } 113 | if (node.specifiers) { 114 | node.specifiers.forEach(spec => { 115 | exportedNames.add(spec.exported.name) 116 | }) 117 | } 118 | } else if (node.type === "ExportDefaultDeclaration") { 119 | exportedNames.add("default") 120 | } 121 | }) 122 | 123 | importedNames.forEach(name => { 124 | if (!exportedNames.has(name)) { 125 | console.error( 126 | `Unresolved export: ${name} not found in ${filePath}, imported in ${sourcePath}` 127 | ) 128 | process.exit(1) 129 | } 130 | }) 131 | } 132 | 133 | function checkImports(file, appsPaths) { 134 | const content = fs.readFileSync(file, "utf-8") 135 | const importRegex = 136 | /import\s+(?:(\*\s+as\s+\w+)|(\w+)|(\{[^}]+\}))\s+from\s+['"](.*)['"]/g 137 | let match 138 | while ((match = importRegex.exec(content)) !== null) { 139 | const source = match[4] 140 | // Skip non-relative imports 141 | if (!source.startsWith(".") && !source.startsWith("..")) { 142 | continue 143 | } 144 | if (EXCEPTIONS.includes(source)) { 145 | continue 146 | } 147 | const result = resolveFilelocation(source, file, appsPaths) 148 | if (!result.found) { 149 | console.error(`Unresolved import: ${source} in file ${file}`) 150 | process.exit(1) 151 | } 152 | 153 | const importedNames = [] 154 | if (match[1]) { 155 | // import * as name 156 | importedNames.push("*") 157 | } else if (match[2]) { 158 | // import name 159 | importedNames.push("default") 160 | } else if (match[3]) { 161 | // import { name1, name2 } 162 | const names = match[3] 163 | .replace(/[{}]/g, "") 164 | .split(",") 165 | .map(name => name.trim()) 166 | importedNames.push(...names) 167 | } 168 | if (!result.path) { 169 | // Plugin - final path cannot be checked yet. 170 | return 171 | } 172 | checkExports(result.path, importedNames, file) 173 | } 174 | } 175 | 176 | const pluginPath = path.resolve(__dirname, "../fiduswriter") 177 | const pluginAppsPaths = getAppsPaths(pluginPath) 178 | 179 | const fidusWriterPath = getFidusWriterPath() 180 | const fidusWriterAppsPaths = getAppsPaths(fidusWriterPath) 181 | 182 | const appsPaths = pluginAppsPaths.concat(fidusWriterAppsPaths) 183 | 184 | const files = process.argv.slice(2) 185 | files.forEach(file => checkImports(file, appsPaths)) 186 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/it/LC_MESSAGES/djangojs.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Fidus Writer OJS\n" 8 | "Language: it\n" 9 | 10 | #: ojs/static/js/modules/ojs/admin.js:57 11 | msgid "Provide a URL for the OJS server and the key to access it." 12 | msgstr "Fornire un URL per il server OJS e la chiave per accedervi." 13 | 14 | #: ojs/static/js/modules/ojs/admin.js:77 15 | msgid "Cannot find Fidus Writer user corresponding to email: " 16 | msgstr "Impossibile trovare l'utente di Fidus Writer corrispondente all'email: " 17 | 18 | #: ojs/static/js/modules/ojs/admin.js:82 19 | msgid "Editor" 20 | msgstr "editore" 21 | 22 | #: ojs/static/js/modules/ojs/admin.js:82 23 | msgid "Register" 24 | msgstr "Registrare" 25 | 26 | #: ojs/static/js/modules/ojs/admin.js:86 27 | msgid "Could not connect to OJS server." 28 | msgstr "Impossibile connettersi al server OJS." 29 | 30 | #: ojs/static/js/modules/ojs/admin.js:106 31 | msgid "Editor and journal name need to be filled out." 32 | msgstr "L'editore e il nome del giornale devono essere compilati." 33 | 34 | #: ojs/static/js/modules/ojs/admin.js:113 35 | msgid "Editor needs to be the ID number of the editor user." 36 | msgstr "L'editor deve essere il numero ID dell'utente dell'editor." 37 | 38 | #: ojs/static/js/modules/ojs/admin.js:125 39 | msgid "Journal saved." 40 | msgstr "Diario salvato." 41 | 42 | #: ojs/static/js/modules/ojs/admin.js:127 43 | msgid "Journal already present on server." 44 | msgstr "Diario già presente sul server." 45 | 46 | #: ojs/static/js/modules/ojs/admin.js:133 47 | msgid "Could not save journal. Please check form." 48 | msgstr "Impossibile salvare il diario. Si prega di controllare il modulo." 49 | 50 | #: ojs/static/js/modules/ojs/editor.js:26 51 | msgid "Could not obtain submission info." 52 | msgstr "Impossibile ottenere le informazioni di invio." 53 | 54 | #: ojs/static/js/modules/ojs/editor.js:39 55 | #: ojs/static/js/modules/ojs/editor.js:41 56 | msgid "Submit to journal" 57 | msgstr "Invia al diario" 58 | 59 | #: ojs/static/js/modules/ojs/editor.js:67 60 | msgid "Submit" 61 | msgstr "Sottoscrivi" 62 | 63 | #: ojs/static/js/modules/ojs/editor.js:78 64 | msgid "Firstname, lastname and abstract are obligatory fields!" 65 | msgstr "Nome, cognome e abstract sono campi obbligatori!" 66 | 67 | #: ojs/static/js/modules/ojs/editor.js:102 68 | msgid "Complete missing information and choose journal" 69 | msgstr "Completa le informazioni mancanti e scegli il diario" 70 | 71 | #: ojs/static/js/modules/ojs/editor.js:118 72 | #: ojs/static/js/modules/ojs/editor.js:173 73 | msgid "Send" 74 | msgstr "Inviare" 75 | 76 | #: ojs/static/js/modules/ojs/editor.js:130 77 | msgid "Submit revision" 78 | msgstr "Invia di nuovo" 79 | 80 | #: ojs/static/js/modules/ojs/editor.js:140 81 | msgid "Resubmission successful" 82 | msgstr "Rinvio riuscito" 83 | 84 | #: ojs/static/js/modules/ojs/editor.js:143 85 | #: ojs/static/js/modules/ojs/editor.js:222 86 | msgid "Review could not be submitted." 87 | msgstr "Non è stato possibile inviare la recensione." 88 | 89 | #: ojs/static/js/modules/ojs/editor.js:188 90 | msgid "Leave your messages for editor and authors" 91 | msgstr "Lascia i tuoi messaggi per editore e autori" 92 | 93 | #: ojs/static/js/modules/ojs/editor.js:207 94 | msgid "Fill out all fields before submitting!" 95 | msgstr "Compila tutti i campi prima di inviare!" 96 | 97 | #: ojs/static/js/modules/ojs/editor.js:219 98 | msgid "Review submitted" 99 | msgstr "Recensione inviata" 100 | 101 | #: ojs/static/js/modules/ojs/submit_doc.js:60 102 | msgid "Article submitted" 103 | msgstr "Articolo inviato" 104 | 105 | #: ojs/static/js/modules/ojs/submit_doc.js:61 106 | msgid "Article could not be submitted." 107 | msgstr "Non è stato possibile inviare l'articolo." 108 | 109 | #: ojs/static/js/modules/ojs/templates.js:10 110 | msgid "Submission information" 111 | msgstr "Informazioni sulla presentazione" 112 | 113 | #: ojs/static/js/modules/ojs/templates.js:10 114 | msgid "Journal" 115 | msgstr "rivista" 116 | 117 | #: ojs/static/js/modules/ojs/templates.js:10 118 | msgid "Abstract" 119 | msgstr "Astratto" 120 | 121 | #: ojs/static/js/modules/ojs/templates.js:10 122 | msgid "Corresponding author" 123 | msgstr "autore corrispondente" 124 | 125 | #: ojs/static/js/modules/ojs/templates.js:10 126 | msgid "First name" 127 | msgstr "nome di battesimo" 128 | 129 | #: ojs/static/js/modules/ojs/templates.js:10 130 | msgid "Last name" 131 | msgstr "Cognome" 132 | 133 | #: ojs/static/js/modules/ojs/templates.js:10 134 | msgid "Affiliation" 135 | msgstr "Affiliazione" 136 | 137 | #: ojs/static/js/modules/ojs/templates.js:10 138 | msgid "Webpage" 139 | msgstr "Pagina web" 140 | 141 | #: ojs/static/js/modules/ojs/templates.js:11 142 | msgid "By pressing the submit button your resubmission will be sent to the journal" 143 | msgstr "Premendo il pulsante di invio il tuo reinvio verrà inviato al giornale" 144 | 145 | #: ojs/static/js/modules/ojs/templates.js:11 146 | #: ojs/static/js/modules/ojs/templates.js:12 147 | msgid "Be aware that this action cannot be undone!" 148 | msgstr "Ricorda che questa azione non può essere annullata!" 149 | 150 | #: ojs/static/js/modules/ojs/templates.js:12 151 | msgid "Message for editor" 152 | msgstr "Messaggio per l'editor" 153 | 154 | #: ojs/static/js/modules/ojs/templates.js:12 155 | msgid "Message for editor and authors" 156 | msgstr "Messaggio per editore e autori" 157 | 158 | #: ojs/static/js/modules/ojs/templates.js:12 159 | msgid "Recommendation" 160 | msgstr "Raccomandazione" 161 | 162 | #: ojs/static/js/modules/ojs/templates.js:12 163 | msgid "Choose One" 164 | msgstr "Scegline uno" 165 | 166 | #: ojs/static/js/modules/ojs/templates.js:12 167 | msgid "Accept Submission" 168 | msgstr "Accetta l'invio" 169 | 170 | #: ojs/static/js/modules/ojs/templates.js:12 171 | msgid "Revisions Required" 172 | msgstr "Revisioni richieste" 173 | 174 | #: ojs/static/js/modules/ojs/templates.js:12 175 | msgid "Resubmit for Review" 176 | msgstr "Reinvia per la revisione" 177 | 178 | #: ojs/static/js/modules/ojs/templates.js:12 179 | msgid "Resubmit Elsewhere" 180 | msgstr "Reinvia altrove" 181 | 182 | #: ojs/static/js/modules/ojs/templates.js:12 183 | msgid "Decline Submission" 184 | msgstr "Rifiuta presentazione" 185 | 186 | #: ojs/static/js/modules/ojs/templates.js:12 187 | msgid "See Comments" 188 | msgstr "Vedi commenti" 189 | 190 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/bg/LC_MESSAGES/djangojs.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Fidus Writer OJS\n" 8 | "Language: bg\n" 9 | 10 | #: ojs/static/js/modules/ojs/admin.js:57 11 | msgid "Provide a URL for the OJS server and the key to access it." 12 | msgstr "Въведете URL за OJS сървъра и ключа за достъп до него." 13 | 14 | #: ojs/static/js/modules/ojs/admin.js:77 15 | msgid "Cannot find Fidus Writer user corresponding to email: " 16 | msgstr "Не мога да намеря потребител на Fidus Writer, отговарящ на имейла: " 17 | 18 | #: ojs/static/js/modules/ojs/admin.js:82 19 | msgid "Editor" 20 | msgstr "Редактор" 21 | 22 | #: ojs/static/js/modules/ojs/admin.js:82 23 | msgid "Register" 24 | msgstr "Регистрирам" 25 | 26 | #: ojs/static/js/modules/ojs/admin.js:86 27 | msgid "Could not connect to OJS server." 28 | msgstr "Не можа да се свърже със OJS сървъра." 29 | 30 | #: ojs/static/js/modules/ojs/admin.js:106 31 | msgid "Editor and journal name need to be filled out." 32 | msgstr "Името на редактора и списанието трябва да бъде попълнено." 33 | 34 | #: ojs/static/js/modules/ojs/admin.js:113 35 | msgid "Editor needs to be the ID number of the editor user." 36 | msgstr "Редакторът трябва да бъде идентификационният номер на потребителя на редактора." 37 | 38 | #: ojs/static/js/modules/ojs/admin.js:125 39 | msgid "Journal saved." 40 | msgstr "Журналът е запазен." 41 | 42 | #: ojs/static/js/modules/ojs/admin.js:127 43 | msgid "Journal already present on server." 44 | msgstr "Журнал вече присъства на сървъра." 45 | 46 | #: ojs/static/js/modules/ojs/admin.js:133 47 | msgid "Could not save journal. Please check form." 48 | msgstr "Журналът не можа да бъде запазен. Моля, проверете формата." 49 | 50 | #: ojs/static/js/modules/ojs/editor.js:26 51 | msgid "Could not obtain submission info." 52 | msgstr "Не можа да се получи информация за изпращането." 53 | 54 | #: ojs/static/js/modules/ojs/editor.js:39 55 | #: ojs/static/js/modules/ojs/editor.js:41 56 | msgid "Submit to journal" 57 | msgstr "Изпращане в дневника" 58 | 59 | #: ojs/static/js/modules/ojs/editor.js:67 60 | msgid "Submit" 61 | msgstr "Изпращане" 62 | 63 | #: ojs/static/js/modules/ojs/editor.js:78 64 | msgid "Firstname, lastname and abstract are obligatory fields!" 65 | msgstr "Име, фамилия и резюме са задължителни полета!" 66 | 67 | #: ojs/static/js/modules/ojs/editor.js:102 68 | msgid "Complete missing information and choose journal" 69 | msgstr "Попълнете липсващата информация и изберете дневник" 70 | 71 | #: ojs/static/js/modules/ojs/editor.js:118 72 | #: ojs/static/js/modules/ojs/editor.js:173 73 | msgid "Send" 74 | msgstr "Изпрати" 75 | 76 | #: ojs/static/js/modules/ojs/editor.js:130 77 | msgid "Submit revision" 78 | msgstr "Повторно изпращане на" 79 | 80 | #: ojs/static/js/modules/ojs/editor.js:140 81 | msgid "Resubmission successful" 82 | msgstr "Повторното подаване е успешно" 83 | 84 | #: ojs/static/js/modules/ojs/editor.js:143 85 | #: ojs/static/js/modules/ojs/editor.js:222 86 | msgid "Review could not be submitted." 87 | msgstr "Прегледът не можа да бъде изпратен." 88 | 89 | #: ojs/static/js/modules/ojs/editor.js:188 90 | msgid "Leave your messages for editor and authors" 91 | msgstr "Оставете съобщенията си за редактор и автори" 92 | 93 | #: ojs/static/js/modules/ojs/editor.js:207 94 | msgid "Fill out all fields before submitting!" 95 | msgstr "Попълнете всички полета преди да изпратите!" 96 | 97 | #: ojs/static/js/modules/ojs/editor.js:219 98 | msgid "Review submitted" 99 | msgstr "Прегледът бе изпратен" 100 | 101 | #: ojs/static/js/modules/ojs/submit_doc.js:60 102 | msgid "Article submitted" 103 | msgstr "Изпратена статия" 104 | 105 | #: ojs/static/js/modules/ojs/submit_doc.js:61 106 | msgid "Article could not be submitted." 107 | msgstr "Статия не може да бъде изпратена." 108 | 109 | #: ojs/static/js/modules/ojs/templates.js:10 110 | msgid "Submission information" 111 | msgstr "Информация за подаване" 112 | 113 | #: ojs/static/js/modules/ojs/templates.js:10 114 | msgid "Journal" 115 | msgstr "списание" 116 | 117 | #: ojs/static/js/modules/ojs/templates.js:10 118 | msgid "Abstract" 119 | msgstr "абстрактен" 120 | 121 | #: ojs/static/js/modules/ojs/templates.js:10 122 | msgid "Corresponding author" 123 | msgstr "Автора за кореспонденция" 124 | 125 | #: ojs/static/js/modules/ojs/templates.js:10 126 | msgid "First name" 127 | msgstr "Първо име" 128 | 129 | #: ojs/static/js/modules/ojs/templates.js:10 130 | msgid "Last name" 131 | msgstr "Фамилия" 132 | 133 | #: ojs/static/js/modules/ojs/templates.js:10 134 | msgid "Affiliation" 135 | msgstr "присъединяване" 136 | 137 | #: ojs/static/js/modules/ojs/templates.js:10 138 | msgid "Webpage" 139 | msgstr "Уеб страница" 140 | 141 | #: ojs/static/js/modules/ojs/templates.js:11 142 | msgid "By pressing the submit button your resubmission will be sent to the journal" 143 | msgstr "С натискане на бутона за изпращане изпратете повторно подаване до дневника" 144 | 145 | #: ojs/static/js/modules/ojs/templates.js:11 146 | #: ojs/static/js/modules/ojs/templates.js:12 147 | msgid "Be aware that this action cannot be undone!" 148 | msgstr "Имайте предвид, че това действие не може да бъде отменено!" 149 | 150 | #: ojs/static/js/modules/ojs/templates.js:12 151 | msgid "Message for editor" 152 | msgstr "Съобщение за редактор" 153 | 154 | #: ojs/static/js/modules/ojs/templates.js:12 155 | msgid "Message for editor and authors" 156 | msgstr "Съобщение за редактор и автори" 157 | 158 | #: ojs/static/js/modules/ojs/templates.js:12 159 | msgid "Recommendation" 160 | msgstr "Препоръка" 161 | 162 | #: ojs/static/js/modules/ojs/templates.js:12 163 | msgid "Choose One" 164 | msgstr "Избери едно" 165 | 166 | #: ojs/static/js/modules/ojs/templates.js:12 167 | msgid "Accept Submission" 168 | msgstr "Приемете Изпращането" 169 | 170 | #: ojs/static/js/modules/ojs/templates.js:12 171 | msgid "Revisions Required" 172 | msgstr "Необходими ревизии" 173 | 174 | #: ojs/static/js/modules/ojs/templates.js:12 175 | msgid "Resubmit for Review" 176 | msgstr "Изпратете отново за преглед" 177 | 178 | #: ojs/static/js/modules/ojs/templates.js:12 179 | msgid "Resubmit Elsewhere" 180 | msgstr "Подайте отново на друго място" 181 | 182 | #: ojs/static/js/modules/ojs/templates.js:12 183 | msgid "Decline Submission" 184 | msgstr "Отклоняване на подаването" 185 | 186 | #: ojs/static/js/modules/ojs/templates.js:12 187 | msgid "See Comments" 188 | msgstr "Вижте коментари" 189 | 190 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/fr/LC_MESSAGES/djangojs.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Fidus Writer OJS\n" 8 | "Language: fr\n" 9 | 10 | #: ojs/static/js/modules/ojs/admin.js:57 11 | msgid "Provide a URL for the OJS server and the key to access it." 12 | msgstr "Fournissez une URL pour le serveur OJS et la clé pour y accéder." 13 | 14 | #: ojs/static/js/modules/ojs/admin.js:77 15 | msgid "Cannot find Fidus Writer user corresponding to email: " 16 | msgstr "Impossible de trouver l'utilisateur Fidus Writer correspondant à l'e-mail: " 17 | 18 | #: ojs/static/js/modules/ojs/admin.js:82 19 | msgid "Editor" 20 | msgstr "Éditeur" 21 | 22 | #: ojs/static/js/modules/ojs/admin.js:82 23 | msgid "Register" 24 | msgstr "registre" 25 | 26 | #: ojs/static/js/modules/ojs/admin.js:86 27 | msgid "Could not connect to OJS server." 28 | msgstr "Impossible de se connecter au serveur OJS." 29 | 30 | #: ojs/static/js/modules/ojs/admin.js:106 31 | msgid "Editor and journal name need to be filled out." 32 | msgstr "Le nom de l'éditeur et du journal doivent être renseignés." 33 | 34 | #: ojs/static/js/modules/ojs/admin.js:113 35 | msgid "Editor needs to be the ID number of the editor user." 36 | msgstr "L'éditeur doit être le numéro d'identification de l'utilisateur de l'éditeur." 37 | 38 | #: ojs/static/js/modules/ojs/admin.js:125 39 | msgid "Journal saved." 40 | msgstr "Journal enregistré." 41 | 42 | #: ojs/static/js/modules/ojs/admin.js:127 43 | msgid "Journal already present on server." 44 | msgstr "Journal déjà présent sur le serveur." 45 | 46 | #: ojs/static/js/modules/ojs/admin.js:133 47 | msgid "Could not save journal. Please check form." 48 | msgstr "Impossible d'enregistrer le journal. Veuillez vérifier le formulaire." 49 | 50 | #: ojs/static/js/modules/ojs/editor.js:26 51 | msgid "Could not obtain submission info." 52 | msgstr "Impossible d'obtenir les informations de soumission." 53 | 54 | #: ojs/static/js/modules/ojs/editor.js:39 55 | #: ojs/static/js/modules/ojs/editor.js:41 56 | msgid "Submit to journal" 57 | msgstr "Soumettre au journal" 58 | 59 | #: ojs/static/js/modules/ojs/editor.js:67 60 | msgid "Submit" 61 | msgstr "Soumettre" 62 | 63 | #: ojs/static/js/modules/ojs/editor.js:78 64 | msgid "Firstname, lastname and abstract are obligatory fields!" 65 | msgstr "Le prénom, le nom et l'abrégé sont des champs obligatoires!" 66 | 67 | #: ojs/static/js/modules/ojs/editor.js:102 68 | msgid "Complete missing information and choose journal" 69 | msgstr "Complétez les informations manquantes et choisissez le journal" 70 | 71 | #: ojs/static/js/modules/ojs/editor.js:118 72 | #: ojs/static/js/modules/ojs/editor.js:173 73 | msgid "Send" 74 | msgstr "Envoyer" 75 | 76 | #: ojs/static/js/modules/ojs/editor.js:130 77 | msgid "Submit revision" 78 | msgstr "Soumettre à nouveau" 79 | 80 | #: ojs/static/js/modules/ojs/editor.js:140 81 | msgid "Resubmission successful" 82 | msgstr "Nouvelle soumission réussie" 83 | 84 | #: ojs/static/js/modules/ojs/editor.js:143 85 | #: ojs/static/js/modules/ojs/editor.js:222 86 | msgid "Review could not be submitted." 87 | msgstr "La révision n'a pas pu être soumise." 88 | 89 | #: ojs/static/js/modules/ojs/editor.js:188 90 | msgid "Leave your messages for editor and authors" 91 | msgstr "Laissez vos messages à l'éditeur et aux auteurs" 92 | 93 | #: ojs/static/js/modules/ojs/editor.js:207 94 | msgid "Fill out all fields before submitting!" 95 | msgstr "Remplissez tous les champs avant de soumettre!" 96 | 97 | #: ojs/static/js/modules/ojs/editor.js:219 98 | msgid "Review submitted" 99 | msgstr "Examen soumis" 100 | 101 | #: ojs/static/js/modules/ojs/submit_doc.js:60 102 | msgid "Article submitted" 103 | msgstr "Article soumis" 104 | 105 | #: ojs/static/js/modules/ojs/submit_doc.js:61 106 | msgid "Article could not be submitted." 107 | msgstr "L'article n'a pas pu être soumis." 108 | 109 | #: ojs/static/js/modules/ojs/templates.js:10 110 | msgid "Submission information" 111 | msgstr "Informations sur la soumission" 112 | 113 | #: ojs/static/js/modules/ojs/templates.js:10 114 | msgid "Journal" 115 | msgstr "Journal" 116 | 117 | #: ojs/static/js/modules/ojs/templates.js:10 118 | msgid "Abstract" 119 | msgstr "Abstrait" 120 | 121 | #: ojs/static/js/modules/ojs/templates.js:10 122 | msgid "Corresponding author" 123 | msgstr "auteur correspondant" 124 | 125 | #: ojs/static/js/modules/ojs/templates.js:10 126 | msgid "First name" 127 | msgstr "Prénom" 128 | 129 | #: ojs/static/js/modules/ojs/templates.js:10 130 | msgid "Last name" 131 | msgstr "Nom de famille" 132 | 133 | #: ojs/static/js/modules/ojs/templates.js:10 134 | msgid "Affiliation" 135 | msgstr "Affiliation" 136 | 137 | #: ojs/static/js/modules/ojs/templates.js:10 138 | msgid "Webpage" 139 | msgstr "Page Web" 140 | 141 | #: ojs/static/js/modules/ojs/templates.js:11 142 | msgid "By pressing the submit button your resubmission will be sent to the journal" 143 | msgstr "En appuyant sur le bouton Soumettre, votre nouvelle soumission sera envoyée au journal" 144 | 145 | #: ojs/static/js/modules/ojs/templates.js:11 146 | #: ojs/static/js/modules/ojs/templates.js:12 147 | msgid "Be aware that this action cannot be undone!" 148 | msgstr "Soyez conscient que cette action ne peut pas être annulée!" 149 | 150 | #: ojs/static/js/modules/ojs/templates.js:12 151 | msgid "Message for editor" 152 | msgstr "Message pour l'éditeur" 153 | 154 | #: ojs/static/js/modules/ojs/templates.js:12 155 | msgid "Message for editor and authors" 156 | msgstr "Message pour l'éditeur et les auteurs" 157 | 158 | #: ojs/static/js/modules/ojs/templates.js:12 159 | msgid "Recommendation" 160 | msgstr "Recommandation" 161 | 162 | #: ojs/static/js/modules/ojs/templates.js:12 163 | msgid "Choose One" 164 | msgstr "Choisissez-en un" 165 | 166 | #: ojs/static/js/modules/ojs/templates.js:12 167 | msgid "Accept Submission" 168 | msgstr "Accepter la soumission" 169 | 170 | #: ojs/static/js/modules/ojs/templates.js:12 171 | msgid "Revisions Required" 172 | msgstr "Révisions requises" 173 | 174 | #: ojs/static/js/modules/ojs/templates.js:12 175 | msgid "Resubmit for Review" 176 | msgstr "Renvoyer pour examen" 177 | 178 | #: ojs/static/js/modules/ojs/templates.js:12 179 | msgid "Resubmit Elsewhere" 180 | msgstr "Soumettre à nouveau ailleurs" 181 | 182 | #: ojs/static/js/modules/ojs/templates.js:12 183 | msgid "Decline Submission" 184 | msgstr "Refuser la soumission" 185 | 186 | #: ojs/static/js/modules/ojs/templates.js:12 187 | msgid "See Comments" 188 | msgstr "Voir les commentaires" 189 | 190 | -------------------------------------------------------------------------------- /fiduswriter/ojs/locale/de/LC_MESSAGES/djangojs.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Fidus Writer OJS\n" 8 | "Language: de\n" 9 | 10 | #: ojs/static/js/modules/ojs/admin.js:57 11 | msgid "Provide a URL for the OJS server and the key to access it." 12 | msgstr "Gebe eine URL für einen OJS Server und den entsprechenden Key an." 13 | 14 | #: ojs/static/js/modules/ojs/admin.js:77 15 | msgid "Cannot find Fidus Writer user corresponding to email: " 16 | msgstr "Fidus Writer-Benutzer, der der E-Mail entspricht, kann nicht gefunden werden: " 17 | 18 | #: ojs/static/js/modules/ojs/admin.js:82 19 | msgid "Editor" 20 | msgstr "Editor" 21 | 22 | #: ojs/static/js/modules/ojs/admin.js:82 23 | msgid "Register" 24 | msgstr "Registrieren" 25 | 26 | #: ojs/static/js/modules/ojs/admin.js:86 27 | msgid "Could not connect to OJS server." 28 | msgstr "Verbindung zum OJS-Server konnte nicht hergestellt werden." 29 | 30 | #: ojs/static/js/modules/ojs/admin.js:106 31 | msgid "Editor and journal name need to be filled out." 32 | msgstr "Editor und Journal Name sind Pflichtfelder." 33 | 34 | #: ojs/static/js/modules/ojs/admin.js:113 35 | msgid "Editor needs to be the ID number of the editor user." 36 | msgstr "Editor muss die ID Nummer des Editor Users sein." 37 | 38 | #: ojs/static/js/modules/ojs/admin.js:125 39 | msgid "Journal saved." 40 | msgstr "Zeitschrift gespeichert." 41 | 42 | #: ojs/static/js/modules/ojs/admin.js:127 43 | msgid "Journal already present on server." 44 | msgstr "Zeitschrift schon auf dem Server vorhanden." 45 | 46 | #: ojs/static/js/modules/ojs/admin.js:133 47 | msgid "Could not save journal. Please check form." 48 | msgstr "Konnte die Zeitschrift nicht abspeichern. Bitte untersuche das Formular." 49 | 50 | #: ojs/static/js/modules/ojs/editor.js:26 51 | msgid "Could not obtain submission info." 52 | msgstr "Übermittlungsinformationen konnten nicht abgerufen werden." 53 | 54 | #: ojs/static/js/modules/ojs/editor.js:39 55 | #: ojs/static/js/modules/ojs/editor.js:41 56 | msgid "Submit to journal" 57 | msgstr "Sende an Journal" 58 | 59 | #: ojs/static/js/modules/ojs/editor.js:67 60 | msgid "Submit" 61 | msgstr "Einschicken" 62 | 63 | #: ojs/static/js/modules/ojs/editor.js:78 64 | msgid "Firstname, lastname and abstract are obligatory fields!" 65 | msgstr "Vorname, Nachname und Zusammenfassung sind Pflichtfelder!" 66 | 67 | #: ojs/static/js/modules/ojs/editor.js:102 68 | msgid "Complete missing information and choose journal" 69 | msgstr "Vervollständige die Detalien und wähle Journal aus" 70 | 71 | #: ojs/static/js/modules/ojs/editor.js:118 72 | #: ojs/static/js/modules/ojs/editor.js:173 73 | msgid "Send" 74 | msgstr "Abschicken" 75 | 76 | #: ojs/static/js/modules/ojs/editor.js:130 77 | msgid "Submit revision" 78 | msgstr "Überarbeitung einreichen" 79 | 80 | #: ojs/static/js/modules/ojs/editor.js:140 81 | msgid "Resubmission successful" 82 | msgstr "Erneute Einreichung erfolgreich" 83 | 84 | #: ojs/static/js/modules/ojs/editor.js:143 85 | #: ojs/static/js/modules/ojs/editor.js:222 86 | msgid "Review could not be submitted." 87 | msgstr "Review konnte nicht eingereicht werden." 88 | 89 | #: ojs/static/js/modules/ojs/editor.js:188 90 | msgid "Leave your messages for editor and authors" 91 | msgstr "Hinterlasse deine Nachricht für den Editor und die Autoren" 92 | 93 | #: ojs/static/js/modules/ojs/editor.js:207 94 | msgid "Fill out all fields before submitting!" 95 | msgstr "Fülle alle Felder vor dem einschicken aus!" 96 | 97 | #: ojs/static/js/modules/ojs/editor.js:219 98 | msgid "Review submitted" 99 | msgstr "Review eingeschickt" 100 | 101 | #: ojs/static/js/modules/ojs/submit_doc.js:60 102 | msgid "Article submitted" 103 | msgstr "Artikel eingeschickt" 104 | 105 | #: ojs/static/js/modules/ojs/submit_doc.js:61 106 | msgid "Article could not be submitted." 107 | msgstr "Artikel konnte nicht eingeschickt werden." 108 | 109 | #: ojs/static/js/modules/ojs/templates.js:10 110 | msgid "Submission information" 111 | msgstr "Einreichungsinformationen" 112 | 113 | #: ojs/static/js/modules/ojs/templates.js:10 114 | msgid "Journal" 115 | msgstr "Journal" 116 | 117 | #: ojs/static/js/modules/ojs/templates.js:10 118 | msgid "Abstract" 119 | msgstr "Zusammenfassung" 120 | 121 | #: ojs/static/js/modules/ojs/templates.js:10 122 | msgid "Corresponding author" 123 | msgstr "Korrespondierender Autor" 124 | 125 | #: ojs/static/js/modules/ojs/templates.js:10 126 | msgid "First name" 127 | msgstr "Vorname" 128 | 129 | #: ojs/static/js/modules/ojs/templates.js:10 130 | msgid "Last name" 131 | msgstr "Nachname" 132 | 133 | #: ojs/static/js/modules/ojs/templates.js:10 134 | msgid "Affiliation" 135 | msgstr "Institution" 136 | 137 | #: ojs/static/js/modules/ojs/templates.js:10 138 | msgid "Webpage" 139 | msgstr "Webseite" 140 | 141 | #: ojs/static/js/modules/ojs/templates.js:11 142 | msgid "By pressing the submit button your resubmission will be sent to the journal" 143 | msgstr "Durch das Betätigen der „Abschicken“-Schaltfläche wird der überarbeitete Beitrag gesendet." 144 | 145 | #: ojs/static/js/modules/ojs/templates.js:11 146 | #: ojs/static/js/modules/ojs/templates.js:12 147 | msgid "Be aware that this action cannot be undone!" 148 | msgstr "Achtung, diese Handlung kann nicht rückgängig gemacht werden!" 149 | 150 | #: ojs/static/js/modules/ojs/templates.js:12 151 | msgid "Message for editor" 152 | msgstr "Nachricht an den Editor" 153 | 154 | #: ojs/static/js/modules/ojs/templates.js:12 155 | msgid "Message for editor and authors" 156 | msgstr "Nachricht an den Editor und die Autoren" 157 | 158 | #: ojs/static/js/modules/ojs/templates.js:12 159 | msgid "Recommendation" 160 | msgstr "Empfehlung" 161 | 162 | #: ojs/static/js/modules/ojs/templates.js:12 163 | msgid "Choose One" 164 | msgstr "Wähle Eine" 165 | 166 | #: ojs/static/js/modules/ojs/templates.js:12 167 | msgid "Accept Submission" 168 | msgstr "Akzeptiere Einreichung" 169 | 170 | #: ojs/static/js/modules/ojs/templates.js:12 171 | msgid "Revisions Required" 172 | msgstr "Überarbeitung nötig" 173 | 174 | #: ojs/static/js/modules/ojs/templates.js:12 175 | msgid "Resubmit for Review" 176 | msgstr "Neueinreichung für Review" 177 | 178 | #: ojs/static/js/modules/ojs/templates.js:12 179 | msgid "Resubmit Elsewhere" 180 | msgstr "Woanders Einreichen" 181 | 182 | #: ojs/static/js/modules/ojs/templates.js:12 183 | msgid "Decline Submission" 184 | msgstr "Einreichung Ablehnen" 185 | 186 | #: ojs/static/js/modules/ojs/templates.js:12 187 | msgid "See Comments" 188 | msgstr "Siehe Kommentare" 189 | 190 | -------------------------------------------------------------------------------- /fiduswriter/ojs/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 1.11 on 2017-05-03 09:06 2 | from django.conf import settings 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | def submission_filename(instance, filename): 8 | return "/".join( 9 | [ 10 | "submission", 11 | str(instance.journal.id), 12 | str(instance.submitter.id), 13 | filename, 14 | ] 15 | ) 16 | 17 | 18 | class Migration(migrations.Migration): 19 | initial = True 20 | 21 | dependencies = [ 22 | ("document", "0001_squashed_20200219"), 23 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 24 | ] 25 | 26 | operations = [ 27 | migrations.CreateModel( 28 | name="Author", 29 | fields=[ 30 | ( 31 | "id", 32 | models.AutoField( 33 | auto_created=True, 34 | primary_key=True, 35 | serialize=False, 36 | verbose_name="ID", 37 | ), 38 | ), 39 | ("ojs_jid", models.PositiveIntegerField(default=0)), 40 | ], 41 | ), 42 | migrations.CreateModel( 43 | name="Journal", 44 | fields=[ 45 | ( 46 | "id", 47 | models.AutoField( 48 | auto_created=True, 49 | primary_key=True, 50 | serialize=False, 51 | verbose_name="ID", 52 | ), 53 | ), 54 | ("ojs_url", models.CharField(max_length=512)), 55 | ("ojs_key", models.CharField(max_length=512)), 56 | ("ojs_jid", models.PositiveIntegerField()), 57 | ("name", models.CharField(max_length=512)), 58 | ( 59 | "editor", 60 | models.ForeignKey( 61 | on_delete=django.db.models.deletion.CASCADE, 62 | to=settings.AUTH_USER_MODEL, 63 | ), 64 | ), 65 | ], 66 | ), 67 | migrations.CreateModel( 68 | name="Reviewer", 69 | fields=[ 70 | ( 71 | "id", 72 | models.AutoField( 73 | auto_created=True, 74 | primary_key=True, 75 | serialize=False, 76 | verbose_name="ID", 77 | ), 78 | ), 79 | ("ojs_jid", models.PositiveIntegerField(default=0)), 80 | ], 81 | ), 82 | migrations.CreateModel( 83 | name="Submission", 84 | fields=[ 85 | ( 86 | "id", 87 | models.AutoField( 88 | auto_created=True, 89 | primary_key=True, 90 | serialize=False, 91 | verbose_name="ID", 92 | ), 93 | ), 94 | ("ojs_jid", models.PositiveIntegerField(default=0)), 95 | ( 96 | "file_object", 97 | models.FileField(upload_to=submission_filename), 98 | ), 99 | ( 100 | "journal", 101 | models.ForeignKey( 102 | on_delete=django.db.models.deletion.CASCADE, 103 | to="ojs.Journal", 104 | ), 105 | ), 106 | ( 107 | "submitter", 108 | models.ForeignKey( 109 | on_delete=django.db.models.deletion.CASCADE, 110 | to=settings.AUTH_USER_MODEL, 111 | ), 112 | ), 113 | ], 114 | ), 115 | migrations.CreateModel( 116 | name="SubmissionRevision", 117 | fields=[ 118 | ( 119 | "id", 120 | models.AutoField( 121 | auto_created=True, 122 | primary_key=True, 123 | serialize=False, 124 | verbose_name="ID", 125 | ), 126 | ), 127 | ("version", models.CharField(default="1.0.0", max_length=8)), 128 | ( 129 | "document", 130 | models.ForeignKey( 131 | on_delete=django.db.models.deletion.CASCADE, 132 | to="document.Document", 133 | ), 134 | ), 135 | ( 136 | "submission", 137 | models.ForeignKey( 138 | on_delete=django.db.models.deletion.CASCADE, 139 | to="ojs.Submission", 140 | ), 141 | ), 142 | ], 143 | ), 144 | migrations.AddField( 145 | model_name="reviewer", 146 | name="revision", 147 | field=models.ForeignKey( 148 | on_delete=django.db.models.deletion.CASCADE, 149 | to="ojs.SubmissionRevision", 150 | ), 151 | ), 152 | migrations.AddField( 153 | model_name="reviewer", 154 | name="user", 155 | field=models.ForeignKey( 156 | on_delete=django.db.models.deletion.CASCADE, 157 | to=settings.AUTH_USER_MODEL, 158 | ), 159 | ), 160 | migrations.AddField( 161 | model_name="author", 162 | name="submission", 163 | field=models.ForeignKey( 164 | on_delete=django.db.models.deletion.CASCADE, 165 | to="ojs.Submission", 166 | ), 167 | ), 168 | migrations.AddField( 169 | model_name="author", 170 | name="user", 171 | field=models.ForeignKey( 172 | on_delete=django.db.models.deletion.CASCADE, 173 | to=settings.AUTH_USER_MODEL, 174 | ), 175 | ), 176 | migrations.AlterUniqueTogether( 177 | name="reviewer", 178 | unique_together=set([("revision", "ojs_jid")]), 179 | ), 180 | migrations.AlterUniqueTogether( 181 | name="journal", 182 | unique_together=set([("ojs_url", "ojs_jid")]), 183 | ), 184 | migrations.AlterUniqueTogether( 185 | name="author", 186 | unique_together=set([("submission", "ojs_jid")]), 187 | ), 188 | ] 189 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatter": { 3 | "enabled": true, 4 | "useEditorconfig": true, 5 | "formatWithErrors": false, 6 | "indentStyle": "space", 7 | "indentWidth": 4, 8 | "lineEnding": "lf", 9 | "lineWidth": 80, 10 | "attributePosition": "auto", 11 | "bracketSpacing": true, 12 | "ignore": [ 13 | "**/.transpile-cache/", 14 | "**/venv/", 15 | "**/static-transpile/", 16 | "**/static-libs/", 17 | "**/static-collected/", 18 | "**/node-modules/", 19 | "**/testing/", 20 | "**/.eslintrc.mjs", 21 | "**/*.json", 22 | "**/*.html", 23 | "**/.direnv", 24 | "**/venv", 25 | "**/.transpile", 26 | "**/.babelrc", 27 | "**/sw-template.js" 28 | ] 29 | }, 30 | "linter": { 31 | "enabled": true, 32 | "rules": { 33 | "recommended": false, 34 | "complexity": { 35 | "noExtraBooleanCast": "error", 36 | "noMultipleSpacesInRegularExpressionLiterals": "error", 37 | "noUselessCatch": "off", 38 | "noUselessConstructor": "off", 39 | "noUselessLabel": "error", 40 | "noUselessLoneBlockStatements": "error", 41 | "noUselessRename": "error", 42 | "noUselessStringConcat": "error", 43 | "noUselessTernary": "off", 44 | "noUselessUndefinedInitialization": "error", 45 | "noVoid": "error", 46 | "noWith": "error", 47 | "useLiteralKeys": "off" 48 | }, 49 | "correctness": { 50 | "noConstAssign": "error", 51 | "noConstantCondition": "error", 52 | "noEmptyCharacterClassInRegex": "error", 53 | "noEmptyPattern": "error", 54 | "noGlobalObjectCalls": "error", 55 | "noInnerDeclarations": "error", 56 | "noInvalidConstructorSuper": "error", 57 | "noInvalidUseBeforeDeclaration": "off", 58 | "noNewSymbol": "error", 59 | "noNonoctalDecimalEscape": "error", 60 | "noPrecisionLoss": "error", 61 | "noSelfAssign": "error", 62 | "noSetterReturn": "error", 63 | "noSwitchDeclarations": "error", 64 | "noUndeclaredVariables": "error", 65 | "noUnreachable": "error", 66 | "noUnreachableSuper": "error", 67 | "noUnsafeFinally": "error", 68 | "noUnsafeOptionalChaining": "error", 69 | "noUnusedLabels": "error", 70 | "noUnusedVariables": "error", 71 | "useArrayLiterals": "error", 72 | "useIsNan": "error", 73 | "useValidForDirection": "error", 74 | "useYield": "error" 75 | }, 76 | "security": { "noGlobalEval": "error" }, 77 | "style": { 78 | "noArguments": "off", 79 | "noCommaOperator": "error", 80 | "noNegationElse": "off", 81 | "noParameterAssign": "off", 82 | "noRestrictedGlobals": { "level": "error", "options": {} }, 83 | "noVar": "error", 84 | "noYodaExpression": "off", 85 | "useBlockStatements": "error", 86 | "useCollapsedElseIf": "off", 87 | "useConsistentBuiltinInstantiation": "error", 88 | "useConst": "warn", 89 | "useDefaultSwitchClause": "off", 90 | "useNumericLiterals": "error", 91 | "useShorthandAssign": "off", 92 | "useSingleVarDeclarator": "off", 93 | "useTemplate": "off" 94 | }, 95 | "suspicious": { 96 | "noAsyncPromiseExecutor": "error", 97 | "noCatchAssign": "error", 98 | "noClassAssign": "error", 99 | "noCompareNegZero": "error", 100 | "noControlCharactersInRegex": "off", 101 | "noDebugger": "error", 102 | "noDoubleEquals": "off", 103 | "noDuplicateCase": "error", 104 | "noDuplicateClassMembers": "error", 105 | "noDuplicateObjectKeys": "error", 106 | "noDuplicateParameters": "error", 107 | "noEmptyBlockStatements": "off", 108 | "noFallthroughSwitchClause": "error", 109 | "noFunctionAssign": "error", 110 | "noGlobalAssign": "error", 111 | "noImportAssign": "error", 112 | "noLabelVar": "error", 113 | "noMisleadingCharacterClass": "error", 114 | "noPrototypeBuiltins": "off", 115 | "noRedeclare": "error", 116 | "noSelfCompare": "error", 117 | "noShadowRestrictedNames": "error", 118 | "noSparseArray": "error", 119 | "noUnsafeNegation": "error", 120 | "useAwait": "error", 121 | "useGetterReturn": "error", 122 | "useValidTypeof": "error" 123 | } 124 | }, 125 | "ignore": [ 126 | "**/.transpile-cache/", 127 | "**/venv/", 128 | "**/static-transpile/", 129 | "**/static-libs/", 130 | "**/static-collected/", 131 | "**/testing/", 132 | "**/manifest.json", 133 | "**/sw-template.js" 134 | ] 135 | }, 136 | "javascript": { 137 | "formatter": { 138 | "jsxQuoteStyle": "double", 139 | "quoteProperties": "asNeeded", 140 | "trailingCommas": "none", 141 | "semicolons": "asNeeded", 142 | "indentWidth": 4, 143 | "arrowParentheses": "asNeeded", 144 | "bracketSameLine": true, 145 | "quoteStyle": "double", 146 | "attributePosition": "auto", 147 | "bracketSpacing": false 148 | }, 149 | "globals": [ 150 | "settings_MEDIA_MAX_SIZE", 151 | "settings_SOCIALACCOUNT_OPEN", 152 | "settings_REGISTRATION_OPEN", 153 | "gettext", 154 | "interpolate", 155 | "settings_USE_SERVICE_WORKER", 156 | "transpile_VERSION", 157 | "settings_TEST_SERVER", 158 | "settings_DEBUG", 159 | "settings_SOURCE_MAPS", 160 | "settings_STATIC_URL", 161 | "settings_PASSWORD_LOGIN", 162 | "process", 163 | "staticUrl", 164 | "settings_CONTACT_EMAIL", 165 | "settings_IS_FREE", 166 | "settings_FOOTER_LINKS", 167 | "settings_BRANDING_LOGO" 168 | ] 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /fiduswriter/ojs/static/js/modules/ojs/admin.js: -------------------------------------------------------------------------------- 1 | import { 2 | addAlert, 3 | findTarget, 4 | getJson, 5 | noSpaceTmp, 6 | post, 7 | postJson 8 | } from "../common" 9 | // Adds capabilities for admins to register journals 10 | 11 | export class AdminRegisterJournals { 12 | constructor() { 13 | this.journals = [] 14 | this.ojsUrl = "" 15 | this.ojsKey = "" 16 | } 17 | 18 | init() { 19 | this.bind() 20 | } 21 | 22 | bind() { 23 | document.addEventListener("click", event => { 24 | const el = {} 25 | switch (true) { 26 | case findTarget(event, "#get_journals", el): 27 | this.getJournals() 28 | break 29 | case findTarget(event, ".related-lookup", el): { 30 | // The following is slightly modified from the binding function in the 31 | // admin interface to allow for lookups in fields that are added to the 32 | // DOM at a later stage. 33 | const nEvent = window.django.jQuery.Event( 34 | "django:lookup-related" 35 | ) // using django's builtin jQuery as required 36 | window.django.jQuery(el.target).trigger(nEvent) // using django's builtin jQuery as required 37 | break 38 | } 39 | case findTarget(event, ".register-submit", el): { 40 | const journalId = el.target.dataset.id 41 | this.saveJournal(journalId) 42 | break 43 | } 44 | default: 45 | break 46 | } 47 | }) 48 | } 49 | 50 | getJournals() { 51 | this.ojsUrl = document.getElementById("ojs_url").value 52 | this.ojsKey = document.getElementById("ojs_key").value 53 | if (this.ojsUrl.length === 0 || this.ojsKey.length === 0) { 54 | addAlert( 55 | "error", 56 | gettext( 57 | "Provide a URL for the OJS server and the key to access it." 58 | ) 59 | ) 60 | return 61 | } 62 | getJson("/api/ojs/get_journals/", {url: this.ojsUrl, key: this.ojsKey}) 63 | .then(json => { 64 | const journals = json["journals"].sort( 65 | (a, b) => parseInt(a.id) - parseInt(b.id) 66 | ) 67 | const emailLookups = [] 68 | journals.forEach(journal => { 69 | if (!journal.contact_email) { 70 | return 71 | } 72 | const emailLookup = this.getUser(journal.contact_email) 73 | .then(user => { 74 | if (user) { 75 | Object.assign(journal, user) 76 | } 77 | }) 78 | .catch(_error => { 79 | addAlert( 80 | "info", 81 | gettext( 82 | `Cannot find Fidus Writer user corresponding to email: ${journal.contact_email}` 83 | ) 84 | ) 85 | }) 86 | emailLookups.push(emailLookup) 87 | }) 88 | return Promise.all(emailLookups).then(() => { 89 | const journalHTML = journals 90 | .map( 91 | journal => 92 | noSpaceTmp` 93 |
    94 | ${journal.id}  95 |   96 | ${gettext("Editor")} : 97 | 98 |   99 | ${journal.user_name ? journal.user_name : ""} 100 | 101 |
    ` 102 | ) 103 | .join("") 104 | document.getElementById("journal_form").innerHTML = 105 | journalHTML 106 | }) 107 | }) 108 | .catch(error => { 109 | addAlert("error", gettext("Could not connect to OJS server.")) 110 | throw error 111 | }) 112 | } 113 | 114 | getUser(email) { 115 | return postJson("/api/ojs/get_user/", {email}).then(({json}) => { 116 | return json 117 | }) 118 | } 119 | 120 | saveJournal(ojs_jid) { 121 | const name = document.getElementById(`journal_name_${ojs_jid}`).value 122 | const editor = document.getElementById(`editor_${ojs_jid}`).value 123 | if (name.length === 0 || editor.length === 0) { 124 | addAlert( 125 | "error", 126 | gettext("Editor and journal name need to be filled out.") 127 | ) 128 | return 129 | } 130 | const editor_id = parseInt(editor) 131 | if (isNaN(editor_id)) { 132 | addAlert( 133 | "error", 134 | gettext("Editor needs to be the ID number of the editor user.") 135 | ) 136 | return 137 | } 138 | 139 | post("/api/ojs/save_journal/", { 140 | editor_id, 141 | name, 142 | ojs_jid, 143 | ojs_key: this.ojsKey, 144 | ojs_url: this.ojsUrl 145 | }) 146 | .then(response => { 147 | if (response.status === 201) { 148 | addAlert("info", gettext("Journal saved.")) 149 | } else { 150 | addAlert( 151 | "warning", 152 | gettext("Journal already present on server.") 153 | ) 154 | } 155 | const journalEl = document.getElementById(`journal_${ojs_jid}`) 156 | journalEl.parentElement.removeChild(journalEl) 157 | }) 158 | .catch(error => { 159 | addAlert( 160 | "error", 161 | gettext("Could not save journal. Please check form.") 162 | ) 163 | throw error 164 | }) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /fiduswriter/ojs/static/js/modules/ojs/editor.js: -------------------------------------------------------------------------------- 1 | import { 2 | Dialog, 3 | activateWait, 4 | addAlert, 5 | deactivateWait, 6 | post, 7 | postJson 8 | } from "../common" 9 | import {COMMENT_ONLY_ROLES, READ_ONLY_ROLES} from "../editor" 10 | import {contributorInputPlugin} from "../editor/state_plugins" 11 | import {reviewContributorPlugin} from "./contributor_state_plugin" 12 | import {SendDocSubmission} from "./submit_doc" 13 | import { 14 | firstSubmissionDialogTemplate, 15 | resubmissionDialogTemplate, 16 | reviewSubmitDialogTemplate 17 | } from "./templates" 18 | 19 | // Adds functions for OJS to the editor 20 | export class EditorOJS { 21 | constructor(editor) { 22 | this.editor = editor 23 | this.submission = { 24 | status: "unknown" 25 | } 26 | this.journals = false 27 | } 28 | 29 | init() { 30 | const docData = { 31 | doc_id: this.editor.docInfo.id 32 | } 33 | return postJson("/api/ojs/get_doc_info/", docData) 34 | .then(({json}) => { 35 | this.submission = json["submission"] 36 | this.journals = json["journals"] 37 | return this.setupUI() 38 | }) 39 | .catch(error => { 40 | addAlert("error", gettext("Could not obtain submission info.")) 41 | throw error 42 | }) 43 | } 44 | 45 | setupUI() { 46 | if (this.journals.length === 0) { 47 | // This installation does not have any journals setup. Abort. 48 | return Promise.resolve() 49 | } 50 | if ( 51 | this.submission.status === "submitted" && 52 | this.submission.version.split(".")[0] === "3" 53 | ) { 54 | // We are in the peer review stage. 55 | // replace contributorInputPlugin. 56 | this.editor.statePlugins = this.editor.statePlugins.filter( 57 | plugin => plugin[0] !== contributorInputPlugin 58 | ) 59 | this.editor.statePlugins.push([ 60 | reviewContributorPlugin, 61 | () => ({ 62 | editor: this, 63 | contributors: this.submission.contributors 64 | }) 65 | ]) 66 | } 67 | 68 | const fileMenu = this.editor.menu.headerbarModel.content.find( 69 | menu => menu.id === "file" 70 | ) 71 | fileMenu.content.push({ 72 | title: gettext("Submit to journal"), 73 | type: "action", 74 | tooltip: gettext("Submit to journal"), 75 | action: editor => { 76 | if (this.submission.status === "submitted") { 77 | if ( 78 | COMMENT_ONLY_ROLES.includes( 79 | editor.docInfo.access_rights 80 | ) 81 | ) { 82 | this.reviewerDialog() 83 | } else if ("4.0.0" === this.submission.version) { 84 | this.updateCopyeditDraftDialog() 85 | } else { 86 | this.resubmissionDialog() 87 | } 88 | } else { 89 | this.firstSubmissionDialog() 90 | } 91 | }, 92 | disabled: editor => { 93 | if ("sub-author" === this.submission.user_role) { 94 | // No submission allowed to the sub-authors 95 | return true 96 | } 97 | 98 | if (READ_ONLY_ROLES.includes(editor.docInfo.access_rights)) { 99 | // Not allowed to submit the doc for review without the rights to write 100 | return true 101 | } else { 102 | if (this.submission.status === "submitted") { 103 | const role = this.submission.user_role 104 | if ("editor" === role || "subeditor" === role) { 105 | // Editors and Sub-Editors have no need to submit 106 | return true 107 | } else if ("assistant" === role) { 108 | const submissionStep = parseInt( 109 | this.submission.version.slice(0, 1) 110 | ) 111 | if (4 !== submissionStep) { 112 | // Assistants can only submit on copyediting revisions 113 | return true 114 | } 115 | } 116 | } else if ( 117 | COMMENT_ONLY_ROLES.includes( 118 | editor.docInfo.access_rights 119 | ) 120 | ) { 121 | // Not allowed to submit the doc for review without the rights to write 122 | return true 123 | } 124 | } 125 | 126 | return false 127 | } 128 | }) 129 | return Promise.resolve() 130 | } 131 | 132 | // Dialog for an article that has no submisison status. Includes selection of journal. 133 | firstSubmissionDialog() { 134 | const buttons = [ 135 | { 136 | text: gettext("Submit"), 137 | classes: "fw-dark", 138 | click: () => { 139 | const journalId = parseInt( 140 | document.getElementById("submission-journal").value 141 | ) 142 | const firstname = document 143 | .getElementById("submission-firstname") 144 | .value.trim() 145 | const lastname = document 146 | .getElementById("submission-lastname") 147 | .value.trim() 148 | const affiliation = document 149 | .getElementById("submission-affiliation") 150 | .value.trim() 151 | const authorUrl = document 152 | .getElementById("submission-author-url") 153 | .value.trim() 154 | const abstract = document 155 | .getElementById("submission-abstract") 156 | .value.trim() 157 | if ( 158 | firstname === "" || 159 | lastname === "" || 160 | abstract === "" 161 | ) { 162 | addAlert( 163 | "error", 164 | gettext( 165 | "Firstname, lastname and abstract are obligatory fields!" 166 | ) 167 | ) 168 | return 169 | } 170 | this.submitDoc({ 171 | journalId, 172 | firstname, 173 | lastname, 174 | affiliation, 175 | authorUrl, 176 | abstract 177 | }) 178 | dialog.close() 179 | } 180 | }, 181 | { 182 | type: "cancel" 183 | } 184 | ] 185 | 186 | const abstractNode = 187 | this.editor.docInfo.confirmedDoc.content.content.find( 188 | node => node.attrs && node.attrs.metadata === "abstract" 189 | ) 190 | const authorsNode = 191 | this.editor.docInfo.confirmedDoc.content.content.find( 192 | node => node.attrs && node.attrs.metadata === "authors" 193 | ) 194 | const authorNode = 195 | authorsNode && authorsNode.childCount 196 | ? authorsNode.firstChild 197 | : false 198 | const dialog = new Dialog({ 199 | height: 460, 200 | width: 800, 201 | buttons, 202 | title: gettext("Complete missing information and choose journal"), 203 | body: firstSubmissionDialogTemplate({ 204 | journals: this.journals, 205 | first_name: authorNode 206 | ? authorNode.attrs.firstname 207 | : this.editor.user.first_name, 208 | last_name: authorNode 209 | ? authorNode.attrs.lastname 210 | : this.editor.user.last_name, 211 | affiliation: authorNode ? authorNode.attrs.institution : "", 212 | abstract: 213 | !abstractNode || abstractNode.attrs.hidden 214 | ? "" 215 | : abstractNode.textContent 216 | }) 217 | }) 218 | dialog.open() 219 | } 220 | 221 | /* Dialog for submitting changes on the copyediting draft revision */ 222 | updateCopyeditDraftDialog() { 223 | const buttons = [ 224 | { 225 | text: gettext("Send"), 226 | click: () => { 227 | this.submitCopyeditDraftUpdate() 228 | dialog.close() 229 | }, 230 | classes: "fw-dark" 231 | }, 232 | { 233 | type: "cancel" 234 | } 235 | ], 236 | dialog = new Dialog({ 237 | width: 300, 238 | buttons, 239 | title: gettext("Submit revision"), 240 | body: resubmissionDialogTemplate() 241 | }) 242 | dialog.open() 243 | } 244 | 245 | /* Dialog for submission of all subsequent revisions */ 246 | resubmissionDialog() { 247 | const buttons = [ 248 | { 249 | text: gettext("Send"), 250 | click: () => { 251 | this.submitResubmission() 252 | dialog.close() 253 | }, 254 | classes: "fw-dark" 255 | }, 256 | { 257 | type: "cancel" 258 | } 259 | ], 260 | dialog = new Dialog({ 261 | width: 300, 262 | buttons, 263 | title: gettext("Submit revision"), 264 | body: resubmissionDialogTemplate() 265 | }) 266 | dialog.open() 267 | } 268 | 269 | submitCopyeditDraftUpdate() { 270 | post("/api/ojs/copyedit_draft_submit/", { 271 | doc_id: this.editor.docInfo.id 272 | }) 273 | .then(() => { 274 | addAlert("success", gettext("Editors are informed.")) 275 | window.setTimeout(() => window.location.reload(), 2000) 276 | }) 277 | .catch(error => { 278 | addAlert("error", gettext("Updates could not be submitted.")) 279 | throw error 280 | }) 281 | } 282 | 283 | submitResubmission() { 284 | post("/api/ojs/author_submit/", { 285 | doc_id: this.editor.docInfo.id 286 | }) 287 | .then(() => { 288 | addAlert("success", gettext("Resubmission successful")) 289 | window.setTimeout(() => window.location.reload(), 2000) 290 | }) 291 | .catch(error => { 292 | addAlert("error", gettext("Review could not be submitted.")) 293 | throw error 294 | }) 295 | } 296 | 297 | submitDoc({ 298 | journalId, 299 | firstname, 300 | lastname, 301 | affiliation, 302 | authorUrl, 303 | abstract 304 | }) { 305 | const submitter = new SendDocSubmission({ 306 | doc: this.editor.getDoc(), 307 | imageDB: this.editor.mod.db.imageDB, 308 | bibDB: this.editor.mod.db.bibDB, 309 | journalId, 310 | firstname, 311 | lastname, 312 | affiliation, 313 | authorUrl, 314 | abstract 315 | }) 316 | return submitter.init() 317 | } 318 | 319 | // The dialog for a document reviewer. 320 | reviewerDialog() { 321 | const buttons = [ 322 | { 323 | text: gettext("Send"), 324 | click: () => { 325 | if (this.submitReview()) { 326 | dialog.close() 327 | } 328 | }, 329 | classes: "fw-dark" 330 | }, 331 | { 332 | type: "cancel" 333 | } 334 | ], 335 | reviewMessageEl = document.getElementById("review-message"), 336 | dialog = new Dialog({ 337 | height: 400, 338 | width: 380, 339 | id: "review-message", 340 | title: gettext("Leave your messages for editor and authors"), 341 | body: reviewSubmitDialogTemplate(), 342 | buttons 343 | }) 344 | if (reviewMessageEl) { 345 | reviewMessageEl.parentElement.removeChild(reviewMessageEl) 346 | } 347 | 348 | dialog.open() 349 | } 350 | 351 | // Send the opinion of the reviewer to OJS. 352 | submitReview() { 353 | const editor_message = document.getElementById("message-editor").value, 354 | editor_author_message = document.getElementById( 355 | "message-editor-author" 356 | ).value, 357 | recommendation = document.getElementById("recommendation").value 358 | if ( 359 | editor_message === "" || 360 | editor_author_message === "" || 361 | recommendation === "" 362 | ) { 363 | addAlert("error", gettext("Fill out all fields before submitting!")) 364 | return false 365 | } 366 | activateWait() 367 | post("/api/ojs/reviewer_submit/", { 368 | doc_id: this.editor.docInfo.id, 369 | editor_message, 370 | editor_author_message, 371 | recommendation 372 | }) 373 | .then(() => { 374 | deactivateWait() 375 | addAlert("success", gettext("Review submitted")) 376 | window.setTimeout(() => window.location.reload(), 2000) 377 | }) 378 | .catch(error => { 379 | addAlert("error", gettext("Review could not be submitted.")) 380 | throw error 381 | }) 382 | return true 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /fiduswriter/ojs/tests/test_ojs.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | import multiprocessing 4 | from http.server import BaseHTTPRequestHandler, HTTPServer 5 | import socket 6 | import requests 7 | from urllib.parse import urljoin 8 | from urllib.parse import parse_qs 9 | 10 | from selenium.webdriver.common.by import By 11 | from selenium.webdriver.common.keys import Keys 12 | from selenium.webdriver.support import expected_conditions as EC 13 | from selenium.webdriver.common.action_chains import ActionChains 14 | from selenium.webdriver.support.wait import WebDriverWait 15 | from selenium.common.exceptions import StaleElementReferenceException 16 | from channels.testing import ChannelsLiveServerTestCase 17 | from testing.selenium_helper import SeleniumHelper 18 | 19 | from ojs import models 20 | 21 | 22 | # From https://realpython.com/testing-third-party-apis-with-mock-servers/ 23 | class MockServerRequestHandler(BaseHTTPRequestHandler): 24 | submission_id = 15 25 | author_id = 8979 26 | ojs_key = "OJS_KEY" 27 | journals = [ 28 | { 29 | "id": 4, 30 | "name": "Journal of Progress", 31 | "contact_email": "contact@progress.com", 32 | "contact_name": "John B. Future", 33 | "url_relative_path": "future/", 34 | "description": "A future journal", 35 | }, 36 | { 37 | "id": 5, 38 | "name": "Journal of the Past", 39 | "contact_email": "contact@goodolddays.com", 40 | "contact_name": "Remember Falls", 41 | "url_relative_path": "past/", 42 | "description": "A historic journal", 43 | }, 44 | ] 45 | 46 | def do_POST(self): 47 | if ( 48 | not ( 49 | "/index.php/index/gateway" "/plugin/FidusWriterGatewayPlugin/" 50 | ) 51 | in self.path 52 | ): 53 | self.send_response(requests.codes.not_found) 54 | self.end_headers() 55 | return 56 | url_parts = self.path.split("/FidusWriterGatewayPlugin/")[1].split("?") 57 | relative_url = url_parts[0] 58 | query = url_parts[1] 59 | if ("key=" + self.ojs_key) not in query: 60 | self.send_response(requests.codes.not_found) 61 | self.end_headers() 62 | return 63 | content_length = int(self.headers["Content-Length"]) 64 | form = parse_qs(self.rfile.read(content_length).decode("utf-8")) 65 | if relative_url == "authorSubmit": 66 | if all( 67 | i in form 68 | for i in ( 69 | "username", 70 | "title", 71 | "abstract", 72 | "first_name", 73 | "last_name", 74 | "email", 75 | "journal_id", 76 | "fidus_url", 77 | "version", 78 | ) 79 | ): 80 | self.send_response(requests.codes.ok) 81 | self.send_header("Content-Type", "application/json") 82 | self.end_headers() 83 | self.wfile.write( 84 | json.dumps( 85 | { 86 | "submission_id": self.submission_id, 87 | "user_id": self.author_id, 88 | } 89 | ).encode(encoding="utf_8") 90 | ) 91 | return 92 | elif all(i in form for i in ("submission_id", "version")): 93 | self.send_response(requests.codes.ok) 94 | self.end_headers() 95 | return 96 | elif relative_url == "reviewerSubmit": 97 | if all( 98 | i in form 99 | for i in ( 100 | "submission_id", 101 | "version", 102 | "user_id", 103 | "editor_message", 104 | "editor_author_message", 105 | "recommendation", 106 | ) 107 | ): 108 | self.send_response(requests.codes.ok) 109 | self.end_headers() 110 | return 111 | self.send_response(requests.codes.not_found) 112 | self.end_headers() 113 | return 114 | 115 | def do_GET(self): 116 | if ( 117 | "/index.php/index/gateway" "/plugin/FidusWriterGatewayPlugin/" 118 | ) not in self.path: 119 | self.send_response(requests.codes.not_found) 120 | self.end_headers() 121 | return 122 | url_parts = self.path.split("/FidusWriterGatewayPlugin/")[1].split("?") 123 | relative_url = url_parts[0] 124 | query = url_parts[1] 125 | if ("key=" + self.ojs_key) not in query: 126 | self.send_response(requests.codes.not_found) 127 | self.end_headers() 128 | return 129 | if relative_url == "journals": 130 | self.send_response(requests.codes.ok) 131 | self.send_header("Content-Type", "application/json") 132 | self.end_headers() 133 | self.wfile.write( 134 | json.dumps( 135 | { 136 | "journals": self.journals, 137 | } 138 | ).encode(encoding="utf_8") 139 | ) 140 | else: 141 | self.send_response(requests.codes.not_found) 142 | self.end_headers() 143 | return 144 | 145 | 146 | def get_free_port(): 147 | s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM) 148 | s.bind(("localhost", 0)) 149 | address, port = s.getsockname() 150 | s.close() 151 | return port 152 | 153 | 154 | class OJSDummyTest(SeleniumHelper, ChannelsLiveServerTestCase): 155 | fixtures = ["initial_documenttemplates.json", "initial_styles.json"] 156 | 157 | @classmethod 158 | def start_server(cls, port): 159 | httpd = HTTPServer(("", port), MockServerRequestHandler) 160 | httpd.serve_forever() 161 | 162 | @classmethod 163 | def setUpClass(cls): 164 | super().setUpClass() 165 | cls.base_url = cls.live_server_url 166 | driver_data = cls.get_drivers(1) 167 | cls.driver = driver_data["drivers"][0] 168 | cls.client = driver_data["clients"][0] 169 | cls.driver.implicitly_wait(driver_data["wait_time"]) 170 | cls.wait_time = driver_data["wait_time"] 171 | cls.server_port = get_free_port() 172 | cls.server = multiprocessing.Process( 173 | target=cls.start_server, args=(cls.server_port,) 174 | ) 175 | cls.server.daemon = True 176 | cls.server.start() 177 | 178 | @classmethod 179 | def tearDownClass(cls): 180 | cls.driver.quit() 181 | cls.server.terminate() 182 | super().tearDownClass() 183 | 184 | def setUp(self): 185 | self.admin = self.create_user( 186 | username="Admin", email="admin@admin.com", passtext="password" 187 | ) 188 | self.admin.is_superuser = True 189 | self.admin.is_staff = True 190 | self.admin.save() 191 | self.user1 = self.create_user( 192 | username="User1", email="user1@user.com", passtext="password" 193 | ) 194 | self.editor1 = self.create_user( 195 | username="Editor1", email="editor1@user.com", passtext="password" 196 | ) 197 | self.editor2 = self.create_user( 198 | username="Editor2", email="editor2@user.com", passtext="password" 199 | ) 200 | 201 | def assertSuccessAlert(self, message): 202 | i = 0 203 | message_found = False 204 | while i < 100: 205 | i = i + 1 206 | try: 207 | if ( 208 | self.driver.find_element( 209 | By.CSS_SELECTOR, 210 | "body #alerts-outer-wrapper .alerts-success", 211 | ).text 212 | == message 213 | ): 214 | message_found = True 215 | break 216 | else: 217 | time.sleep(0.1) 218 | continue 219 | except StaleElementReferenceException: 220 | time.sleep(0.1) 221 | continue 222 | self.assertTrue(message_found) 223 | 224 | def test_ojs_dummy(self): 225 | # Register journals 226 | self.login_user(self.admin, self.driver, self.client) 227 | self.driver.get(urljoin(self.base_url, "/admin/")) 228 | self.driver.find_element( 229 | By.CSS_SELECTOR, 'a[href="/admin/ojs/journal/"]' 230 | ).click() 231 | self.driver.find_element( 232 | By.CSS_SELECTOR, 'a[href="register_journal/"]' 233 | ).click() 234 | self.driver.find_element(By.ID, "ojs_url").send_keys( 235 | "http://localhost:{}/".format(self.server_port) 236 | ) 237 | self.driver.find_element(By.ID, "ojs_key").send_keys("OJS_KEY") 238 | self.driver.find_element(By.ID, "get_journals").click() 239 | self.driver.find_element(By.ID, "editor_4").send_keys(self.editor1.id) 240 | self.driver.find_element( 241 | By.CSS_SELECTOR, 'button[data-id="4"]' 242 | ).click() 243 | self.driver.find_element(By.ID, "editor_5").send_keys(self.editor2.id) 244 | self.driver.find_element( 245 | By.CSS_SELECTOR, 'button[data-id="5"]' 246 | ).click() 247 | time.sleep(1) 248 | # Log in as user to submit an article 249 | self.login_user(self.user1, self.driver, self.client) 250 | self.driver.get(urljoin(self.base_url, "/")) 251 | WebDriverWait(self.driver, self.wait_time).until( 252 | EC.element_to_be_clickable( 253 | (By.CSS_SELECTOR, ".new_document button") 254 | ) 255 | ).click() 256 | WebDriverWait(self.driver, self.wait_time).until( 257 | EC.presence_of_element_located((By.CLASS_NAME, "editor-toolbar")) 258 | ) 259 | self.driver.find_element(By.CSS_SELECTOR, ".doc-title").click() 260 | self.driver.find_element(By.CSS_SELECTOR, ".doc-title").send_keys( 261 | "Test" 262 | ) 263 | # We enable the abstract 264 | self.driver.find_element( 265 | By.CSS_SELECTOR, "#header-navigation > div:nth-child(3) > span" 266 | ).click() 267 | self.driver.find_element( 268 | By.CSS_SELECTOR, 269 | ( 270 | "#header-navigation > div:nth-child(3) > div " 271 | "> ul > li:nth-child(1) > span" 272 | ), 273 | ).click() 274 | self.driver.find_element( 275 | By.CSS_SELECTOR, 276 | ( 277 | "#header-navigation > div:nth-child(3) > div " 278 | "> ul > li:nth-child(1) > div > ul > li:nth-child(3) > span" 279 | ), 280 | ).click() 281 | self.driver.find_element(By.CSS_SELECTOR, ".doc-body").click() 282 | ActionChains(self.driver).send_keys(Keys.LEFT).send_keys( 283 | "An abstract" 284 | ).perform() 285 | time.sleep(1) 286 | # Enable authors 287 | self.driver.find_element( 288 | By.XPATH, '//*[@id="header-navigation"]/div[3]/span' 289 | ).click() 290 | self.driver.find_element( 291 | By.XPATH, '//*[normalize-space()="Optional sections"]' 292 | ).click() 293 | self.driver.find_element( 294 | By.XPATH, '//*[normalize-space()="Authors"]' 295 | ).click() 296 | # Close menu 297 | self.driver.find_element(By.CSS_SELECTOR, "#document-top").click() 298 | # Add author 299 | self.driver.find_element( 300 | By.CSS_SELECTOR, ".doc-authors button" 301 | ).click() 302 | self.driver.find_element( 303 | By.CSS_SELECTOR, "input[name=firstname]" 304 | ).send_keys("Wendy") 305 | self.driver.find_element( 306 | By.CSS_SELECTOR, "input[name=lastname]" 307 | ).send_keys("Pox") 308 | self.driver.find_element( 309 | By.CSS_SELECTOR, "input[name=email]" 310 | ).send_keys("wendy@pox.com") 311 | self.driver.find_element( 312 | By.CSS_SELECTOR, "input[name=institution]" 313 | ).send_keys("Sunny University") 314 | 315 | self.driver.find_element( 316 | By.CSS_SELECTOR, ".ui-dialog-buttonset .fw-dark" 317 | ).click() 318 | time.sleep(1) 319 | # Check that there is now one author 320 | self.assertEqual( 321 | len( 322 | self.driver.find_elements(By.CSS_SELECTOR, "span.contributor") 323 | ), 324 | 1, 325 | ) 326 | # We submit the article to journal 327 | self.driver.find_element( 328 | By.XPATH, '//*[@id="header-navigation"]/div[1]/span' 329 | ).click() 330 | self.driver.find_element( 331 | By.XPATH, '//*[normalize-space()="Submit to journal"]' 332 | ).click() 333 | self.driver.find_element(By.ID, "submission-journal").click() 334 | self.driver.find_element( 335 | By.XPATH, '//*[normalize-space()="Journal of the Past"]' 336 | ).click() 337 | self.assertEqual( 338 | self.driver.find_element( 339 | By.ID, "submission-abstract" 340 | ).get_attribute("value"), 341 | "An abstract", 342 | ) 343 | self.assertEqual( 344 | self.driver.find_element( 345 | By.ID, "submission-firstname" 346 | ).get_attribute("value"), 347 | "Wendy", 348 | ) 349 | self.assertEqual( 350 | self.driver.find_element( 351 | By.ID, "submission-lastname" 352 | ).get_attribute("value"), 353 | "Pox", 354 | ) 355 | # Modify the lastname 356 | self.driver.find_element(By.ID, "submission-lastname").send_keys( 357 | " Hansen" 358 | ) 359 | self.driver.find_element( 360 | By.XPATH, '//*[normalize-space()="Submit"]' 361 | ).click() 362 | self.assertSuccessAlert("Article submitted") 363 | # Let OJS create a copy of the document (invisible to the original 364 | # author). 365 | submission_id = models.Submission.objects.last().id 366 | response = self.client.post( 367 | "/api/ojs/create_copy/{}/".format(submission_id), 368 | { 369 | "key": "OJS_KEY", 370 | "old_version": "1.0.0", 371 | "new_version": "3.0.0", 372 | "granted_users": "1", 373 | }, 374 | ) 375 | self.assertEqual(response.status_code, 201) 376 | # Let OJS assign a new double-blind reviewer to the submitted article 377 | # and give access. 378 | response = self.client.post( 379 | "/api/ojs/add_reviewer/{}/3.0.0/".format(submission_id), 380 | { 381 | "key": "OJS_KEY", 382 | "user_id": 9263, 383 | "email": "reviewer1@reviews.com", 384 | "username": "Reviewer1", 385 | }, 386 | ) 387 | self.assertEqual(response.status_code, 201) 388 | # Reviewer accepts review 389 | response = self.client.post( 390 | "/api/ojs/accept_reviewer/{}/3.0.0/".format(submission_id), 391 | { 392 | "key": "OJS_KEY", 393 | "user_id": 9263, 394 | "review_method": "doubleanonymous", 395 | }, 396 | ) 397 | self.assertEqual(response.status_code, 200) 398 | # Get login token and log in 399 | response = self.client.get( 400 | "/api/ojs/get_login_token/", 401 | { 402 | "key": "OJS_KEY", 403 | "fidus_id": str(submission_id), 404 | "user_id": 9263, 405 | "version": "3.0.0", 406 | "is_editor": False, 407 | }, 408 | ) 409 | self.assertEqual(response.status_code, 200) 410 | login_token = json.loads(response.content)["token"] 411 | self.driver.get( 412 | urljoin( 413 | self.base_url, 414 | "/api/ojs/revision/{}/3.0.0/?token={}".format( 415 | submission_id, login_token 416 | ), 417 | ) 418 | ) 419 | WebDriverWait(self.driver, self.wait_time).until( 420 | EC.presence_of_element_located((By.CLASS_NAME, "editor-toolbar")) 421 | ) 422 | # Check that no authors are listed 423 | self.assertEqual( 424 | len( 425 | self.driver.find_elements(By.CSS_SELECTOR, "span.contributor") 426 | ), 427 | 0, 428 | ) 429 | time.sleep(1) 430 | # Check that we cannot change the title 431 | self.driver.find_element(By.CSS_SELECTOR, ".doc-title").click() 432 | self.driver.find_element(By.CSS_SELECTOR, ".doc-title").send_keys( 433 | "ARGH" 434 | ) 435 | self.assertEqual( 436 | self.driver.find_element(By.CSS_SELECTOR, ".doc-title").text, 437 | "Test", 438 | ) 439 | ActionChains(self.driver).double_click( 440 | self.driver.find_element(By.CSS_SELECTOR, ".doc-title") 441 | ).perform() 442 | self.driver.find_element( 443 | By.CSS_SELECTOR, 'button[title="Comment"]' 444 | ).click() 445 | self.driver.find_element( 446 | By.CSS_SELECTOR, "#comment-editor .ProseMirror" 447 | ).send_keys("Reviewer comment") 448 | self.driver.find_element(By.CSS_SELECTOR, "button.fw-dark").click() 449 | # Reviewer submits response to journal 450 | self.driver.find_element( 451 | By.XPATH, '//*[@id="header-navigation"]/div[1]/span' 452 | ).click() 453 | self.driver.find_element( 454 | By.XPATH, '//*[normalize-space()="Submit to journal"]' 455 | ).click() 456 | self.driver.find_element(By.ID, "message-editor").send_keys( 457 | "A message just for the editor" 458 | ) 459 | self.driver.find_element(By.ID, "message-editor-author").send_keys( 460 | "A message for the editor and author" 461 | ) 462 | self.driver.find_element(By.ID, "recommendation").click() 463 | ActionChains(self.driver).send_keys(Keys.DOWN).send_keys( 464 | Keys.DOWN 465 | ).send_keys(Keys.DOWN).send_keys(Keys.ENTER).perform() 466 | self.driver.find_element(By.CSS_SELECTOR, "button.fw-dark").click() 467 | self.assertSuccessAlert("Review submitted") 468 | # Let OJS assign a new anonymous reviewer to the submitted article 469 | # and give access. 470 | response = self.client.post( 471 | "/api/ojs/add_reviewer/{}/3.0.0/".format(submission_id), 472 | { 473 | "key": "OJS_KEY", 474 | "user_id": 18364, 475 | "email": "reviewer2@reviews.com", 476 | "username": "Reviewer2", 477 | }, 478 | ) 479 | self.assertEqual(response.status_code, 201) 480 | # Reviewer accepts review 481 | response = self.client.post( 482 | "/api/ojs/accept_reviewer/{}/3.0.0/".format(submission_id), 483 | { 484 | "key": "OJS_KEY", 485 | "user_id": 18364, 486 | "review_method": "anonymous", 487 | }, 488 | ) 489 | self.assertEqual(response.status_code, 200) 490 | # Get login token and log in 491 | response = self.client.get( 492 | "/api/ojs/get_login_token/", 493 | { 494 | "key": "OJS_KEY", 495 | "fidus_id": str(submission_id), 496 | "user_id": 18364, 497 | "version": "3.0.0", 498 | "is_editor": False, 499 | }, 500 | ) 501 | self.assertEqual(response.status_code, 200) 502 | login_token = json.loads(response.content)["token"] 503 | self.driver.get( 504 | urljoin( 505 | self.base_url, 506 | "/api/ojs/revision/{}/3.0.0/?token={}".format( 507 | submission_id, login_token 508 | ), 509 | ) 510 | ) 511 | WebDriverWait(self.driver, self.wait_time).until( 512 | EC.presence_of_element_located((By.CLASS_NAME, "editor-toolbar")) 513 | ) 514 | # Check that authors are listed 515 | self.assertEqual( 516 | len( 517 | self.driver.find_elements(By.CSS_SELECTOR, "span.contributor") 518 | ), 519 | 1, 520 | ) 521 | time.sleep(1) 522 | # Check that we cannot change the title 523 | self.driver.find_element(By.CSS_SELECTOR, ".doc-title").click() 524 | self.driver.find_element(By.CSS_SELECTOR, ".doc-title").send_keys( 525 | "ARGH" 526 | ) 527 | self.assertEqual( 528 | self.driver.find_element(By.CSS_SELECTOR, ".doc-title").text, 529 | "Test", 530 | ) 531 | ActionChains(self.driver).double_click( 532 | self.driver.find_element(By.CSS_SELECTOR, ".doc-title") 533 | ).perform() 534 | self.driver.find_element( 535 | By.CSS_SELECTOR, 'button[title="Comment"]' 536 | ).click() 537 | self.driver.find_element( 538 | By.CSS_SELECTOR, "#comment-editor .ProseMirror" 539 | ).send_keys("Reviewer comment") 540 | self.driver.find_element(By.CSS_SELECTOR, "button.fw-dark").click() 541 | # Reviewer submits response to journal 542 | self.driver.find_element( 543 | By.XPATH, '//*[@id="header-navigation"]/div[1]/span' 544 | ).click() 545 | self.driver.find_element( 546 | By.XPATH, '//*[normalize-space()="Submit to journal"]' 547 | ).click() 548 | self.driver.find_element(By.ID, "message-editor").send_keys( 549 | "Another message just for the editor" 550 | ) 551 | self.driver.find_element(By.ID, "message-editor-author").send_keys( 552 | "Another message for the editor and author" 553 | ) 554 | self.driver.find_element(By.ID, "recommendation").click() 555 | ActionChains(self.driver).send_keys(Keys.DOWN).send_keys( 556 | Keys.DOWN 557 | ).send_keys(Keys.DOWN).send_keys(Keys.ENTER).perform() 558 | self.driver.find_element(By.CSS_SELECTOR, "button.fw-dark").click() 559 | self.assertSuccessAlert("Review submitted") 560 | # Make another copy to give the original author access to the reviewed 561 | # version 562 | response = self.client.post( 563 | "/api/ojs/create_copy/{}/".format(submission_id), 564 | { 565 | "key": "OJS_KEY", 566 | "old_version": "3.0.0", 567 | "new_version": "3.0.5", 568 | "granted_users": "1", 569 | }, 570 | ) 571 | self.assertEqual(response.status_code, 201) 572 | # Log in as author and verify that there are now three documents 573 | self.login_user(self.user1, self.driver, self.client) 574 | self.driver.get(urljoin(self.base_url, "/")) 575 | time.sleep(1) 576 | self.assertEqual( 577 | len( 578 | self.driver.find_elements( 579 | By.CSS_SELECTOR, "table.fw-data-table tbody tr" 580 | ) 581 | ), 582 | 2, 583 | ) 584 | self.driver.find_elements(By.CSS_SELECTOR, "a.fw-data-table-title")[ 585 | 0 586 | ].click() 587 | self.assertEqual( 588 | len( 589 | self.driver.find_elements( 590 | By.CSS_SELECTOR, "table.fw-data-table tbody tr" 591 | ) 592 | ), 593 | 3, 594 | ) 595 | WebDriverWait(self.driver, self.wait_time).until( 596 | EC.presence_of_element_located( 597 | (By.XPATH, "//h1[contains(text(),'/Submission 1/')]") 598 | ) 599 | ) 600 | # Enter the latest version 601 | self.driver.find_elements(By.CSS_SELECTOR, "a.fw-data-table-title")[ 602 | 2 603 | ].click() 604 | WebDriverWait(self.driver, self.wait_time).until( 605 | EC.presence_of_element_located((By.CLASS_NAME, "editor-toolbar")) 606 | ) 607 | # Check that authors are listed 608 | self.assertEqual( 609 | len( 610 | self.driver.find_elements(By.CSS_SELECTOR, "span.contributor") 611 | ), 612 | 1, 613 | ) 614 | # Check that authors are listed read only 615 | self.assertEqual( 616 | len( 617 | self.driver.find_elements( 618 | By.CSS_SELECTOR, "div.doc-authors-readonly" 619 | ) 620 | ), 621 | 1, 622 | ) 623 | # Check that we can change the body 624 | self.driver.find_element(By.CSS_SELECTOR, ".doc-body").click() 625 | self.driver.find_element(By.CSS_SELECTOR, ".doc-body").send_keys( 626 | "An updated body" 627 | ) 628 | time.sleep(1) 629 | self.assertEqual( 630 | self.driver.find_element(By.CSS_SELECTOR, ".doc-body").text, 631 | "An updated body", 632 | ) 633 | # Resubmit 634 | self.driver.find_element( 635 | By.XPATH, '//*[@id="header-navigation"]/div[1]/span' 636 | ).click() 637 | self.driver.find_element( 638 | By.XPATH, '//*[normalize-space()="Submit to journal"]' 639 | ).click() 640 | self.driver.find_element(By.CSS_SELECTOR, "button.fw-dark").click() 641 | self.assertSuccessAlert("Resubmission successful") 642 | # Journal accepts. Make another copy moving from review into 643 | # copy-editing 644 | response = self.client.post( 645 | "/api/ojs/create_copy/{}/".format(submission_id), 646 | { 647 | "key": "OJS_KEY", 648 | "old_version": "3.0.5", 649 | "new_version": "4.0.0", 650 | "granted_users": "1", 651 | }, 652 | ) 653 | # Go back to overview. 654 | self.driver.find_element( 655 | By.CSS_SELECTOR, "#close-document-top" 656 | ).click() 657 | time.sleep(1) 658 | # Check that there is now another document. 659 | self.assertEqual( 660 | len( 661 | self.driver.find_elements( 662 | By.CSS_SELECTOR, "table.fw-data-table tbody tr" 663 | ) 664 | ), 665 | 4, 666 | ) 667 | # Enter the latest version 668 | self.driver.find_elements(By.CSS_SELECTOR, "a.fw-data-table-title")[ 669 | 3 670 | ].click() 671 | # Check that the author information is listed. 672 | self.assertEqual( 673 | len( 674 | self.driver.find_elements(By.CSS_SELECTOR, "span.contributor") 675 | ), 676 | 1, 677 | ) 678 | # Check that authors are not listed read only 679 | self.assertEqual( 680 | len( 681 | self.driver.find_elements( 682 | By.CSS_SELECTOR, "div.doc-authors-readonly" 683 | ) 684 | ), 685 | 0, 686 | ) 687 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | --------------------------------------------------------------------------------