diff --git a/HypothesisPlugin.php b/HypothesisPlugin.php
index 0e41043..e2478de 100644
--- a/HypothesisPlugin.php
+++ b/HypothesisPlugin.php
@@ -16,15 +16,31 @@
use PKP\plugins\GenericPlugin;
use PKP\plugins\Hook;
+use APP\core\Application;
+use PKP\facades\Locale;
+use PKP\db\DAORegistry;
+use APP\template\TemplateManager;
class HypothesisPlugin extends GenericPlugin {
+ private const NMI_TYPE_ANNOTATIONS = 'NMI_TYPE_ANNOTATIONS';
+
/**
* @copydoc Plugin::register()
*/
function register($category, $path, $mainContextId = null) {
if (parent::register($category, $path, $mainContextId)) {
- Hook::add('ArticleHandler::download',array(&$this, 'callback'));
+ Hook::add('ArticleHandler::download', array(&$this, 'callback'));
Hook::add('TemplateManager::display', array(&$this, 'callbackTemplateDisplay'));
+ Hook::add('TemplateManager::display', [$this, 'addAnnotationNumberViewers']);
+ Hook::add('LoadHandler', array($this, 'addAnnotationsHandler'));
+ Hook::add('LoadComponentHandler', array($this, 'setupHypothesisHandler'));
+ Hook::add('AcronPlugin::parseCronTab', [$this, 'addTasksToCrontab']);
+
+ Hook::add('NavigationMenus::itemTypes', [$this, 'addNavigationMenuItemType']);
+ Hook::add('NavigationMenus::displaySettings', [$this, 'setNavigationMenuItemUrl']);
+
+ $this->addHandlerURLToJavaScript();
+
return true;
}
return false;
@@ -64,6 +80,7 @@ function callbackTemplateDisplay($hookName, $args) {
// template path contains the plugin path, and ends with the tpl file
if ( (strpos($template, $plugin) !== false) && ( (strpos($template, ':'.$submissiontpl, -1 - strlen($submissiontpl)) !== false) || (strpos($template, ':'.$issuetpl, -1 - strlen($issuetpl)) !== false))) {
$templateMgr->registerFilter("output", array($this, 'changePdfjsPath'));
+ $templateMgr->registerFilter("output", array($this, 'addHypothesisConfig'));
}
return false;
}
@@ -79,6 +96,110 @@ function changePdfjsPath($output, $templateMgr) {
return $newOutput;
}
+ /**
+ * Adds Hypothesis tab configuration so sidebar opens automatically when PDF has annotations
+ * @param $output string
+ * @param $templateMgr TemplateManager
+ * @return $string
+ */
+ public function addHypothesisConfig($output, $templateMgr) {
+ if (preg_match('/
]+id="pdfCanvasContainer/', $output, $matches, PREG_OFFSET_CAPTURE)) {
+ $posMatch = $matches[0][1];
+ $config = $templateMgr->fetch($this->getTemplateResource('hypothesisConfig.tpl'));
+
+ $output = substr_replace($output, $config, $posMatch, 0);
+ $templateMgr->unregisterFilter('output', array($this, 'addHypothesisConfig'));
+ }
+ return $output;
+ }
+
+ public function addAnnotationNumberViewers($hookName, $args) {
+ $templateMgr = $args[0];
+ $template = $args[1];
+ $pagesToInsert = [
+ 'frontend/pages/indexServer.tpl',
+ 'frontend/pages/preprint.tpl',
+ 'frontend/pages/preprints.tpl',
+ 'frontend/pages/sections.tpl',
+ 'frontend/pages/indexJournal.tpl',
+ 'frontend/pages/article.tpl',
+ 'frontend/pages/issue.tpl'
+ ];
+
+ if (in_array($template, $pagesToInsert)) {
+ $request = Application::get()->getRequest();
+
+ $jsUrl = $request->getBaseUrl() . '/' . $this->getPluginPath() . '/js/addAnnotationViewers.js';
+ $styleUrl = $request->getBaseUrl() . '/' . $this->getPluginPath() . '/styles/annotationViewer.css';
+
+ $templateMgr->addJavascript('AddAnnotationViewers', $jsUrl, ['contexts' => 'frontend']);
+ $templateMgr->addStyleSheet('AnnotationViewerStyleSheet', $styleUrl, ['contexts' => 'frontend']);
+ }
+
+ return false;
+ }
+
+ public function addAnnotationsHandler($hookName, $args) {
+ $page = $args[0];
+ if ($page == 'annotations') {
+ define('HANDLER_CLASS', 'APP\plugins\generic\hypothesis\pages\annotations\AnnotationsHandler');
+ return true;
+ }
+ return false;
+ }
+
+ public function setupHypothesisHandler($hookName, $args) {
+ $component = &$args[0];
+ if ($component == 'plugins.generic.hypothesis.controllers.HypothesisHandler') {
+ return true;
+ }
+ return false;
+ }
+
+ public function addTasksToCrontab($hookName, $args) {
+ $taskFilesPath = &$args[0];
+ $taskFilesPath[] = $this->getPluginPath() . DIRECTORY_SEPARATOR . 'scheduledTasks.xml';
+ return false;
+ }
+
+ public function addHandlerURLToJavaScript()
+ {
+ $request = Application::get()->getRequest();
+ $templateMgr = TemplateManager::getManager($request);
+ $handlerUrl = $request->getDispatcher()->url($request, Application::ROUTE_COMPONENT, null, 'plugins.generic.hypothesis.controllers.HypothesisHandler');
+ $data = ['hypothesisHandlerUrl' => $handlerUrl];
+
+ $templateMgr->addJavaScript('HypothesisHandler', 'app = ' . json_encode($data) . ';', ['contexts' => 'frontend', 'inline' => true]);
+ }
+
+ public function addNavigationMenuItemType($hookName, $args) {
+ $itemTypes = &$args[0];
+
+ $itemTypes[self::NMI_TYPE_ANNOTATIONS] = [
+ 'title' => __('plugins.generic.hypothesis.annotationsMenuItem.title'),
+ 'description' => __('plugins.generic.hypothesis.annotationsMenuItem.description')
+ ];
+
+ return Hook::CONTINUE;
+ }
+
+ public function setNavigationMenuItemUrl($hookName, $args) {
+ $menuItem = &$args[0];
+
+ if ($menuItem->getType() === self::NMI_TYPE_ANNOTATIONS) {
+ $request = Application::get()->getRequest();
+ $dispatcher = $request->getDispatcher();
+ $menuItem->setUrl($dispatcher->url(
+ $request,
+ ROUTE_PAGE,
+ null,
+ 'annotations',
+ null,
+ null
+ ));
+ }
+ }
+
/**
* Get the display name of this plugin
* @return string
@@ -94,5 +215,23 @@ function getDisplayName() {
function getDescription() {
return __('plugins.generic.hypothesis.description');
}
+
+ public function setEnabled($enabled){
+ parent::setEnabled($enabled);
+
+ $contextId = $this->getCurrentContextId();
+ if ($enabled && $contextId != Application::CONTEXT_SITE) {
+ $navigationMenuItemDao = DAORegistry::getDAO('NavigationMenuItemDAO');
+ $menuItems = $navigationMenuItemDao->getByType(self::NMI_TYPE_ANNOTATIONS, $contextId)->toArray();
+ if(empty($menuItems)) {
+ $locale = Locale::getLocale();
+ $menuItem = $navigationMenuItemDao->newDataObject();
+ $menuItem->setTitle(__('plugins.generic.hypothesis.annotationsMenuItem.title'), $locale);
+ $menuItem->setContextId($contextId);
+ $menuItem->setType(self::NMI_TYPE_ANNOTATIONS);
+ $navigationMenuItemDao->insertObject($menuItem);
+ }
+ }
+ }
}
diff --git a/classes/Annotation.php b/classes/Annotation.php
new file mode 100644
index 0000000..b72e58b
--- /dev/null
+++ b/classes/Annotation.php
@@ -0,0 +1,22 @@
+user = $user;
+ $this->dateCreated = $dateCreated;
+ $this->target = $target;
+ $this->content = $content;
+ }
+
+ public static function __set_state($dump) {
+ $obj = new Annotation($dump['user'], $dump['dateCreated'], $dump['target'], $dump['content']);
+ return $obj;
+ }
+}
diff --git a/classes/HypothesisDAO.php b/classes/HypothesisDAO.php
new file mode 100644
index 0000000..0773626
--- /dev/null
+++ b/classes/HypothesisDAO.php
@@ -0,0 +1,23 @@
+where('submission_id', $submissionId)
+ ->select('current_publication_id')
+ ->first();
+ $currentPublicationId = get_object_vars($result)['current_publication_id'];
+
+ $result = DB::table('publications')
+ ->where('publication_id', $currentPublicationId)
+ ->select('date_published')
+ ->first();
+
+ return get_object_vars($result)['date_published'];
+ }
+}
\ No newline at end of file
diff --git a/classes/HypothesisHelper.php b/classes/HypothesisHelper.php
new file mode 100644
index 0000000..4187427
--- /dev/null
+++ b/classes/HypothesisHelper.php
@@ -0,0 +1,142 @@
+getCollector()
+ ->filterByContextIds([$contextId])
+ ->filterByStatus([Submission::STATUS_PUBLISHED])
+ ->getMany();
+
+ $groupsRequests = $this->getSubmissionsGroupsRequests($submissions, $contextId);
+ $submissionsAnnotations = [];
+ foreach ($groupsRequests as $groupRequest) {
+ $groupResponse = $this->getRequestAnnotations($groupRequest);
+ if (!is_null($groupResponse) && $groupResponse['total'] > 0) {
+ $submissionsAnnotations = array_merge(
+ $submissionsAnnotations,
+ $this->groupSubmissionsAnnotations($groupResponse, $contextId)
+ );
+ }
+ }
+
+ return $submissionsAnnotations;
+ }
+
+ private function getSubmissionsGroupsRequests($submissions, $contextId) {
+ $requests = [];
+ $requestPrefix = $currentRequest = "https://hypothes.is/api/search?limit=200&group=__world__";
+ $maxRequestLength = 4094;
+
+ foreach ($submissions as $submission) {
+ $submissionRequestParams = $this->getSubmissionRequestParams($submission, $contextId);
+
+ if(!is_null($submissionRequestParams)) {
+ if(strlen($currentRequest.$submissionRequestParams) < $maxRequestLength) {
+ $currentRequest .= $submissionRequestParams;
+ }
+ else {
+ $requests[] = $currentRequest;
+ $currentRequest = $requestPrefix . $submissionRequestParams;
+ }
+ }
+ }
+
+ if ($currentRequest != $requestPrefix) {
+ $requests[] = $currentRequest;
+ }
+
+ return $requests;
+ }
+
+ private function getSubmissionRequestParams($submission, $contextId) {
+ $submissionRequestParams = "";
+ $publication = $submission->getCurrentPublication();
+
+ if(is_null($publication))
+ return null;
+
+ $galleys = $publication->getData('galleys');
+ foreach ($galleys as $galley) {
+ if ($galley->getFileType() == 'application/pdf') {
+ $galleyDownloadURL = $this->getGalleyDownloadURL($contextId, $submission, $galley);
+ if (!is_null($galleyDownloadURL)) {
+ $submissionRequestParams .= "&uri={$galleyDownloadURL}";
+ }
+ }
+ }
+
+ return $submissionRequestParams;
+ }
+
+ private function getRequestAnnotations($requestURL) {
+ $ch = curl_init($requestURL);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ $output = curl_exec($ch);
+ if(!$output || substr($output, 1, 8) != '"total":') return null;
+
+ return json_decode($output, true);
+ }
+
+ private function groupSubmissionsAnnotations($groupResponse, $contextId) {
+ $submissionsAnnotations = [];
+
+ foreach ($groupResponse['rows'] as $annotationResponse) {
+ $urlBySlash = explode("/", $annotationResponse['links']['incontext']);
+ $submissionBestId = $urlBySlash[count($urlBySlash) - 3];
+ $submissionId = Repo::submission()->getByBestId($submissionBestId, $contextId)->getId();
+
+ if(!array_key_exists($submissionId, $submissionsAnnotations)) {
+ $submissionsAnnotations[$submissionId] = new SubmissionAnnotations($submissionId);
+ }
+
+ $annotation = $this->getAnnotation($annotationResponse);
+ $submissionsAnnotations[$submissionId]->addAnnotation($annotation);
+ }
+
+ return $submissionsAnnotations;
+ }
+
+ private function getAnnotation($annotationResponse): Annotation {
+ $user = substr($annotationResponse['user'], 5, strlen($annotationResponse['user']) - 17);
+ $dateCreated = $annotationResponse['created'];
+ $content = $annotationResponse['text'];
+
+ $target = "";
+ if(isset($annotationResponse['target'][0]['selector'])) {
+ foreach ($annotationResponse['target'][0]['selector'] as $selector) {
+ if($selector['type'] == 'TextQuoteSelector') {
+ $target = $selector['exact'];
+ break;
+ }
+ }
+ }
+
+ return new Annotation($user, $dateCreated, $target, $content);
+ }
+
+ public function getGalleyDownloadURL($contextId, $submission, $galley) {
+ $request = Application::get()->getRequest();
+ $indexUrl = $request->getIndexUrl();
+ $context = Application::getContextDAO()->getById($contextId);
+ $contextPath = $context->getPath();
+ $submissionType = (Application::getName() == 'ojs2' ? 'article' : 'preprint');
+
+ $submissionFile = $galley->getFile();
+ if(is_null($submissionFile))
+ return null;
+
+ $submissionBestId = $submission->getBestId();
+ $galleyBestId = $galley->getBestGalleyId();
+ $fileId = $submissionFile->getId();
+
+ return $indexUrl . "/$contextPath/$submissionType/download/$submissionBestId/$galleyBestId/$fileId";
+ }
+}
\ No newline at end of file
diff --git a/classes/SubmissionAnnotations.php b/classes/SubmissionAnnotations.php
new file mode 100644
index 0000000..edb70aa
--- /dev/null
+++ b/classes/SubmissionAnnotations.php
@@ -0,0 +1,26 @@
+submissionId = $submissionId;
+ $this->annotations = [];
+ }
+
+ public static function __set_state($dump) {
+ $obj = new SubmissionAnnotations($dump['submissionId']);
+ $obj->annotations = $dump['annotations'];
+ return $obj;
+ }
+
+ public function addAnnotation(Annotation $annotation) {
+ $this->annotations[] = $annotation;
+ }
+}
\ No newline at end of file
diff --git a/classes/tasks/UpdateAnnotationsCache.php b/classes/tasks/UpdateAnnotationsCache.php
new file mode 100644
index 0000000..c37bfec
--- /dev/null
+++ b/classes/tasks/UpdateAnnotationsCache.php
@@ -0,0 +1,36 @@
+getIds([
+ 'isEnabled' => true,
+ ]);
+
+ foreach ($contextIds as $contextId) {
+ $cacheManager = CacheManager::getManager();
+ $cache = $cacheManager->getFileCache(
+ $contextId,
+ 'submissions_annotations',
+ [$this, 'cacheDismiss']
+ );
+
+ $cache->flush();
+ $hypothesisHelper = new HypothesisHelper();
+ $cache->setEntireCache($hypothesisHelper->getSubmissionsAnnotations($contextId));
+ }
+
+ return true;
+ }
+
+ function cacheDismiss() {
+ return null;
+ }
+}
diff --git a/controllers/HypothesisHandler.php b/controllers/HypothesisHandler.php
new file mode 100644
index 0000000..317bb9b
--- /dev/null
+++ b/controllers/HypothesisHandler.php
@@ -0,0 +1,27 @@
+get($galleyId);
+ if(!$galley) {
+ return json_encode(null);
+ }
+
+ $fileId = $galley->getFile()->getId();
+ $galleyDownloadUrl = str_replace('view', 'download', $galleyUrl);
+ $galleyDownloadUrl .= "/$fileId";
+
+ return json_encode([
+ 'downloadUrl' => $galleyDownloadUrl,
+ 'annotationMsg' => __('plugins.generic.hypothesis.annotation'),
+ 'annotationsMsg' => __('plugins.generic.hypothesis.annotations')
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/js/addAnnotationViewers.js b/js/addAnnotationViewers.js
new file mode 100644
index 0000000..c812e3b
--- /dev/null
+++ b/js/addAnnotationViewers.js
@@ -0,0 +1,58 @@
+
+function insertAfter(newNode, referenceNode) {
+ referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
+}
+
+function createAnnotationViewerNode(galleyId) {
+ var viewerLi = document.createElement('li');
+ viewerLi.classList.add('annotation_viewer');
+ viewerLi.classList.add('hidden_viewer');
+ var viewer = document.createElement('a');
+ viewer.id = 'annotation_viewer_link-' + galleyId;
+
+ viewerLi.appendChild(viewer);
+ return viewerLi;
+}
+
+async function addAnnotationViewers() {
+ let galleyLinks = document.getElementsByClassName('obj_galley_link pdf');
+
+ for (let galleyLink of galleyLinks) {
+ let galleyUrl = galleyLink.href;
+ let explodedUrl = galleyUrl.split('/');
+ let galleyId = explodedUrl[explodedUrl.length - 1];
+ let viewerNode = createAnnotationViewerNode(galleyId);
+ insertAfter(viewerNode, galleyLink.parentNode);
+
+ let viewerData = await $.get(
+ app.hypothesisHandlerUrl + 'get-annotation-viewer-data',
+ {
+ galleyUrl: galleyUrl
+ }
+ );
+ viewerData = JSON.parse(viewerData);
+
+ if (viewerData !== null) {
+ $.get(
+ 'https://hypothes.is/api/search?limit=0&group=__world__&uri=' + viewerData['downloadUrl'],
+ function(response) {
+ if (response['total'] > 0) {
+ const viewerButton = document.getElementById('annotation_viewer_link-' + galleyId);
+ const viewerLi = viewerButton.parentNode;
+ const suffix = (response['total'] == 1) ? viewerData['annotationMsg'] : viewerData['annotationsMsg'];
+ viewerButton.textContent = response['total'] + ' ' + suffix;
+
+ const galleyLink = viewerLi.previousElementSibling.getElementsByTagName('a')[0];
+ galleyLink.href = galleyLink.href + '?hasAnnotations=true';
+ viewerButton.href = galleyLink.href;
+ viewerLi.classList.remove('hidden_viewer');
+ }
+ }
+ );
+ }
+
+ }
+}
+
+
+$(document).ready(addAnnotationViewers);
\ No newline at end of file
diff --git a/js/load.js b/js/load.js
new file mode 100644
index 0000000..299015d
--- /dev/null
+++ b/js/load.js
@@ -0,0 +1,35 @@
+let maxCharNumber = 300;
+let annotationTargets = document.querySelectorAll('.annotation_target > blockquote');
+let annotationContents = document.querySelectorAll('.annotation_content > blockquote');
+
+annotationTargets.forEach(target => {
+ addReadMoreLogic(target);
+});
+
+annotationContents.forEach(content => {
+ addReadMoreLogic(content);
+});
+
+function addReadMoreLogic(element) {
+ if(element.textContent.length <= maxCharNumber) {
+ let readMoreBtn = element.nextElementSibling;
+ readMoreBtn.style.display = "none";
+ }
+ else {
+ let trimmedText = element.textContent.trim();
+ let textToDisplay = trimmedText.slice(0, maxCharNumber);
+ let textMore = trimmedText.slice(maxCharNumber);
+ element.innerHTML = `${textToDisplay}
...${textMore}`;
+ }
+}
+
+function toggleReadMore(btn){
+ let parent = btn.parentElement;
+ parent.querySelector('.dots').classList.toggle('hide');
+ parent.querySelector('.more').classList.toggle('hide');
+ if(btn.classList.contains('read_more'))
+ btn.nextElementSibling.classList.remove('hide');
+ else
+ btn.previousElementSibling.classList.remove('hide');
+ btn.classList.add('hide');
+}
\ No newline at end of file
diff --git a/locale/en/locale.po b/locale/en/locale.po
index 1d54807..f7b208b 100644
--- a/locale/en/locale.po
+++ b/locale/en/locale.po
@@ -16,3 +16,33 @@ msgstr "Hypothes.is Plugin"
msgid "plugins.generic.hypothesis.description"
msgstr "This plugin enables Hypothes.is integration into OJS article views for reader annotation and commenting."
+
+msgid "plugins.generic.hypothesis.annotation"
+msgstr "annotation"
+
+msgid "plugins.generic.hypothesis.annotations"
+msgstr "annotations"
+
+msgid "plugins.generic.hypothesis.annotationsPage"
+msgstr "Annotations"
+
+msgid "plugins.generic.hypothesis.annotationsPageNumber"
+msgstr "Annotations - Page {$pageNumber}"
+
+msgid "plugins.generic.hypothesis.noSubmissionsWithAnnotations"
+msgstr "There are no annotated submissions"
+
+msgid "plugins.generic.hypothesis.orderBy"
+msgstr "Order by:"
+
+msgid "plugins.generic.hypothesis.orderBy.datePublished"
+msgstr "Date published"
+
+msgid "plugins.generic.hypothesis.orderBy.lastAnnotation"
+msgstr "Date of last annotation"
+
+msgid "plugins.generic.hypothesis.annotationsMenuItem.title"
+msgstr "Annotations"
+
+msgid "plugins.generic.hypothesis.annotationsMenuItem.description"
+msgstr "Link to the page displaying annotations on PDF galleys"
diff --git a/locale/es/locale.po b/locale/es/locale.po
index 7ffe464..1cbde81 100644
--- a/locale/es/locale.po
+++ b/locale/es/locale.po
@@ -18,3 +18,33 @@ msgstr ""
msgid "plugins.generic.hypothesis.name"
msgstr "Módulo Hypothes.is"
+
+msgid "plugins.generic.hypothesis.annotation"
+msgstr "annotación"
+
+msgid "plugins.generic.hypothesis.annotations"
+msgstr "anotaciones"
+
+msgid "plugins.generic.hypothesis.annotationsPage"
+msgstr "Anotaciones"
+
+msgid "plugins.generic.hypothesis.annotationsPageNumber"
+msgstr "Anotaciones - Página {$pageNumber}"
+
+msgid "plugins.generic.hypothesis.noSubmissionsWithAnnotations"
+msgstr "No hay envíos anotados"
+
+msgid "plugins.generic.hypothesis.orderBy"
+msgstr "Ordenar por:"
+
+msgid "plugins.generic.hypothesis.orderBy.datePublished"
+msgstr "Fecha de publicación"
+
+msgid "plugins.generic.hypothesis.orderBy.lastAnnotation"
+msgstr "Fecha de la última annotación"
+
+msgid "plugins.generic.hypothesis.annotationsMenuItem.title"
+msgstr "Anotaciones"
+
+msgid "plugins.generic.hypothesis.annotationsMenuItem.description"
+msgstr "Enlace a la página que muestra anotaciones en PDFs de galeradas"
diff --git a/locale/pt_BR/locale.po b/locale/pt_BR/locale.po
index 7339509..3eb9b26 100644
--- a/locale/pt_BR/locale.po
+++ b/locale/pt_BR/locale.po
@@ -18,3 +18,33 @@ msgstr ""
msgid "plugins.generic.hypothesis.name"
msgstr "Plugin Hypothes.is"
+
+msgid "plugins.generic.hypothesis.annotation"
+msgstr "anotação"
+
+msgid "plugins.generic.hypothesis.annotations"
+msgstr "anotações"
+
+msgid "plugins.generic.hypothesis.annotationsPage"
+msgstr "Anotações"
+
+msgid "plugins.generic.hypothesis.annotationsPageNumber"
+msgstr "Anotações - Página {$pageNumber}"
+
+msgid "plugins.generic.hypothesis.noSubmissionsWithAnnotations"
+msgstr "Não há submissões com anotações"
+
+msgid "plugins.generic.hypothesis.orderBy"
+msgstr "Ordenar por:"
+
+msgid "plugins.generic.hypothesis.orderBy.datePublished"
+msgstr "Data de postagem"
+
+msgid "plugins.generic.hypothesis.orderBy.lastAnnotation"
+msgstr "Data da última anotação"
+
+msgid "plugins.generic.hypothesis.annotationsMenuItem.title"
+msgstr "Anotações"
+
+msgid "plugins.generic.hypothesis.annotationsMenuItem.description"
+msgstr "Link para a página exibindo anotações em PDFs de composições finais"
diff --git a/locale/pt_PT/locale.po b/locale/pt_PT/locale.po
index df28e3b..78e8938 100644
--- a/locale/pt_PT/locale.po
+++ b/locale/pt_PT/locale.po
@@ -16,3 +16,27 @@ msgstr "Plugin Hypothes.is"
msgid "plugins.generic.hypothesis.description"
msgstr "Este plugin permite a integração do Hypothes.is nas visualizações de artigo do OJS permitindo anotações e comentários dos leitores."
+
+msgid "plugins.generic.hypothesis.annotation"
+msgstr "anotação"
+
+msgid "plugins.generic.hypothesis.annotations"
+msgstr "anotações"
+
+msgid "plugins.generic.hypothesis.annotationsPage"
+msgstr "Anotações"
+
+msgid "plugins.generic.hypothesis.annotationsPageNumber"
+msgstr "Anotações - Página {$pageNumber}"
+
+msgid "plugins.generic.hypothesis.noSubmissionsWithAnnotations"
+msgstr "Não há submissões com anotações"
+
+msgid "plugins.generic.hypothesis.orderBy"
+msgstr "Ordenar por:"
+
+msgid "plugins.generic.hypothesis.orderBy.datePublished"
+msgstr "Data de postagem"
+
+msgid "plugins.generic.hypothesis.orderBy.lastAnnotation"
+msgstr "Data da última anotação"
\ No newline at end of file
diff --git a/pages/annotations/AnnotationsHandler.php b/pages/annotations/AnnotationsHandler.php
new file mode 100644
index 0000000..70bac28
--- /dev/null
+++ b/pages/annotations/AnnotationsHandler.php
@@ -0,0 +1,132 @@
+addPolicy(new ContextRequiredPolicy($request));
+
+ if (Application::getName() == 'ojs2') {
+ $this->addPolicy(new \APP\security\authorization\OjsJournalMustPublishPolicy($request));
+ } else {
+ $this->addPolicy(new \APP\security\authorization\OpsServerMustPublishPolicy($request));
+ }
+
+ return parent::authorize($request, $args, $roleAssignments);
+ }
+
+ public function index($args, $request) {
+ $plugin = PluginRegistry::getPlugin('generic', 'hypothesisplugin');
+ $context = $request->getContext();
+
+ $paginationParams = $this->getPaginationParams($args, $request, $context);
+ $pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $context->getId());
+
+ $templateMgr = TemplateManager::getManager($request);
+ $templateMgr->assign($paginationParams);
+ $templateMgr->assign('pubIdPlugins', $pubIdPlugins);
+ $templateMgr->assign('context', $context);
+ $templateMgr->assign('application', Application::getName());
+
+ $jsUrl = $request->getBaseUrl() . '/' . $plugin->getPluginPath() . '/js/load.js';
+ $templateMgr->addJavascript('AnnotationsPage', $jsUrl, ['contexts' => 'frontend']);
+
+ return $templateMgr->display($plugin->getTemplateResource('annotationsPage.tpl'));
+ }
+
+ private function getPaginationParams($args, $request, $context): array {
+ $page = isset($args[0]) ? (int) $args[0] : 1;
+ $itemsPerPage = $context->getData('itemsPerPage') ? $context->getData('itemsPerPage') : (int) Config::getVar('interface', 'items_per_page');
+ $offset = $page > 1 ? ($page - 1) * $itemsPerPage : 0;
+
+ $orderBy = ($request->getUserVar('orderBy') ?? self::ORDER_BY_LAST_ANNOTATION);
+ $submissionsAnnotations = $this->getSubmissionsAnnotations($context->getId(), $orderBy);
+ $pageAnnotations = array_slice($submissionsAnnotations, $offset, $itemsPerPage);
+
+ $total = count($submissionsAnnotations);
+ $showingStart = $offset + 1;
+ $showingEnd = min($offset + $itemsPerPage, $offset + count($pageAnnotations));
+ $nextPage = $total > $showingEnd ? $page + 1 : null;
+ $prevPage = $showingStart > 1 ? $page - 1 : null;
+
+ foreach ($pageAnnotations as $submissionAnnotation) {
+ $submissionAnnotation->submission = Repo::submission()->get($submissionAnnotation->submissionId);
+ }
+
+ return [
+ 'submissionsAnnotations' => $pageAnnotations,
+ 'orderBy' => $orderBy,
+ 'showingStart' => $showingStart,
+ 'showingEnd' => $showingEnd,
+ 'total' => $total,
+ 'nextPage' => $nextPage,
+ 'prevPage' => $prevPage,
+ 'authorUserGroups' => Repo::userGroup()->getCollector()
+ ->filterByRoleIds([\PKP\security\Role::ROLE_ID_AUTHOR])
+ ->filterByContextIds([$context->getId()])
+ ->getMany()->remember(),
+ ];
+ }
+
+ private function getSubmissionsAnnotations($contextId, $orderBy) {
+ $cacheManager = CacheManager::getManager();
+ $cache = $cacheManager->getFileCache(
+ $contextId,
+ 'submissions_annotations',
+ [$this, 'cacheDismiss']
+ );
+
+ $submissionsAnnotations = $cache->getContents();
+
+ if (is_null($submissionsAnnotations)){
+ $cache->flush();
+ $hypothesisHelper = new HypothesisHelper();
+ $cache->setEntireCache($hypothesisHelper->getSubmissionsAnnotations($contextId));
+ $submissionsAnnotations = $cache->getContents();
+ }
+
+ $orderingFunction = $orderBy.'Ordering';
+ usort($submissionsAnnotations, [$this, $orderingFunction]);
+
+ return $submissionsAnnotations;
+ }
+
+ public function lastAnnotationOrdering($submissionAnnotationsA, $submissionAnnotationsB) {
+ $lastAnnotationA = $submissionAnnotationsA->annotations[0];
+ $lastAnnotationB = $submissionAnnotationsB->annotations[0];
+
+ if($lastAnnotationA->dateCreated == $lastAnnotationB->dateCreated) return 0;
+
+ return ($lastAnnotationA->dateCreated < $lastAnnotationB->dateCreated) ? 1 : -1;
+ }
+
+ public function datePublishedOrdering($submissionAnnotationsA, $submissionAnnotationsB) {
+ $hypothesisDao = new HypothesisDAO();
+ $datePublishedA = $hypothesisDao->getDatePublished($submissionAnnotationsA->submissionId);
+ $datePublishedB = $hypothesisDao->getDatePublished($submissionAnnotationsB->submissionId);
+
+ if($datePublishedA == $datePublishedB) return 0;
+
+ return ($datePublishedA < $datePublishedB) ? 1 : -1;
+ }
+
+ function cacheDismiss() {
+ return null;
+ }
+}
diff --git a/scheduledTasks.xml b/scheduledTasks.xml
new file mode 100644
index 0000000..f17624a
--- /dev/null
+++ b/scheduledTasks.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+ Updates the cache of submissions with annotations.
+
+
+
\ No newline at end of file
diff --git a/styles/annotationViewer.css b/styles/annotationViewer.css
new file mode 100644
index 0000000..dd0294d
--- /dev/null
+++ b/styles/annotationViewer.css
@@ -0,0 +1,30 @@
+.obj_article_details .galleys_links li {
+ margin-right: 0.25em;
+}
+
+.obj_article_details .galleys_links .annotation_viewer {
+ margin-top: 0.5em;
+}
+
+.obj_article_summary .galleys_links .annotation_viewer {
+ margin: 0;
+}
+
+.annotation_viewer.hidden_viewer {
+ visibility: hidden;
+ width: 0;
+ height: 0;
+}
+
+.annotation_viewer > a {
+ padding: 4px 8px;
+ border: 1px solid rgba(0,0,0,0.54);
+ border-radius: 15px;
+ color: rgba(0,0,0,0.54);
+ line-height: 20px;
+ font-size: 14px;
+}
+
+.annotation_viewer > a:hover {
+ color: rgba(0,0,0,0.54);
+}
\ No newline at end of file
diff --git a/styles/annotationsPage.css b/styles/annotationsPage.css
new file mode 100644
index 0000000..fee6002
--- /dev/null
+++ b/styles/annotationsPage.css
@@ -0,0 +1,89 @@
+#orderSubmissions {
+ margin-bottom: 1rem;
+}
+
+#selectOrderSubmissions {
+ background: #FFF;
+ border: 1px solid rgba(0,0,0,0.4);
+ border-radius: 3px;
+ font-size: 0.93rem;
+ height: calc(2.143rem - 2px);
+}
+
+.submission_annotations {
+ background-color: #fafaf8;
+ padding: 10px 15px;
+ border-radius: 3px;
+}
+
+.submission_annotations .title {
+ font-size: 16px;
+ line-height: 20px;
+ font-weight: 700;
+}
+
+.submission_annotations .title a {
+ text-decoration: none;
+}
+
+.submission_annotations .title .subtitle {
+ display: block;
+ margin-top: 0.25em;
+ margin-bottom: 0.25em;
+ font-weight: 400;
+ color: rgba(0,0,0,0.54);
+}
+
+.meta {
+ padding-top: 5px;
+ font-size: 14px;
+ line-height: 20px;
+}
+
+.meta > .authors {
+ padding-right: 5em;
+}
+
+.meta > .published, .meta > .doi {
+ color: rgba(0,0,0,0.54);
+}
+
+.annotation {
+ background-color: #FFF;
+ font-size: 14px;
+ margin: 10px 0 0 15px;
+ padding: 14px;
+ border-radius: 3px;
+ box-shadow: 0px 2px 3px 0 rgba(0,0,0,0.2);
+}
+
+.annotation_header span {
+ color: rgba(0,0,0,0.54);
+ margin-left: 8px;
+ float: right;
+}
+
+.annotation_target {
+ font-style: italic;
+ margin: 1em 0;
+ padding: 0 1em;
+ color: rgba(0,0,0,0.54);
+}
+
+.annotation_target blockquote, .annotation_content blockquote {
+ margin-bottom: 4px;
+}
+
+.read_more, .read_less {
+ padding: 3px 6px;
+ border: 1px solid #007bff;
+ border-radius: 14px;
+ color: #007bff;
+ background-color: #FFF;
+ line-height: 20px;
+ font-weight: 700;
+}
+
+.hide {
+ display: none;
+}
\ No newline at end of file
diff --git a/templates/annotationsPage.tpl b/templates/annotationsPage.tpl
new file mode 100644
index 0000000..c2cbcbf
--- /dev/null
+++ b/templates/annotationsPage.tpl
@@ -0,0 +1,66 @@
+{capture assign="pageTitle"}
+ {if $prevPage}
+ {translate key="plugins.generic.hypothesis.annotationsPageNumber" pageNumber=$prevPage+1}
+ {else}
+ {translate key="plugins.generic.hypothesis.annotationsPage"}
+ {/if}
+{/capture}
+
+{include file="frontend/components/header.tpl" pageTitleTranslated=$pageTitle}
+
+
+
{$pageTitle|escape}
+
+ {if empty($submissionsAnnotations)}
+
{translate key="plugins.generic.hypothesis.noSubmissionsWithAnnotations"}
+ {else}
+
+
+
+
+
+
+ {foreach from=$submissionsAnnotations item="submissionAnnotations"}
+ -
+ {include file="../../../plugins/generic/hypothesis/templates/submissionAnnotations.tpl"}
+
+ {/foreach}
+
+
+ {* Pagination *}
+ {if $prevPage > 1}
+ {capture assign=prevUrl}{url router=$smarty.const.ROUTE_PAGE page="annotations" path=$prevPage params=['orderBy' => $orderBy]}{/capture}
+ {elseif $prevPage === 1}
+ {capture assign=prevUrl}{url router=$smarty.const.ROUTE_PAGE page="annotations" params=['orderBy' => $orderBy]}{/capture}
+ {/if}
+ {if $nextPage}
+ {capture assign=nextUrl}{url router=$smarty.const.ROUTE_PAGE page="annotations" path=$nextPage params=['orderBy' => $orderBy]}{/capture}
+ {/if}
+ {include
+ file="frontend/components/pagination.tpl"
+ prevUrl=$prevUrl
+ nextUrl=$nextUrl
+ showingStart=$showingStart
+ showingEnd=$showingEnd
+ total=$total
+ }
+ {/if}
+
+
+{include file="frontend/components/footer.tpl"}
+
+
diff --git a/templates/hypothesisConfig.tpl b/templates/hypothesisConfig.tpl
new file mode 100644
index 0000000..9f00b33
--- /dev/null
+++ b/templates/hypothesisConfig.tpl
@@ -0,0 +1,16 @@
+
diff --git a/templates/submissionAnnotations.tpl b/templates/submissionAnnotations.tpl
new file mode 100644
index 0000000..71c41e7
--- /dev/null
+++ b/templates/submissionAnnotations.tpl
@@ -0,0 +1,74 @@
+{assign var="submission" value=$submissionAnnotations->submission}
+{assign var="annotations" value=$submissionAnnotations->annotations}
+
+
+
+
+
+ {foreach from=$annotations item="annotation"}
+
+
+ {if not empty($annotation->target)}
+
+
{$annotation->target|escape}
+
+
+
+ {/if}
+
+ {$annotation->content|escape}
+
+
+
+
+ {/foreach}
+