View
@@ -1,14 +1,22 @@
# -*- coding: utf-8 -*-
import datetime
import logging
import colander
import deform
import horus.views
import json
from horus.lib import FlashMessage
from horus.resources import UserFactory
from pyramid import httpexceptions, security
from pyramid.view import view_config, view_defaults
from h.auth.local import schemas
from h.events import LoginEvent
from h.models import _
from h.notification.models import Subscriptions
log = logging.getLogger(__name__)
def ajax_form(request, result):
@@ -135,7 +143,10 @@ def wrapper(context, request):
meth = getattr(inst, self.attr)
result = meth()
result = ajax_form(request, result)
result['model'] = model(request)
if 'model' in result:
result['model'].update(model(request))
else:
result['model'] = model(request)
result.pop('form', None)
return result
return wrapper
@@ -181,7 +192,16 @@ def login(self):
except httpexceptions.HTTPBadRequest as e:
return e.detail
else:
if request.user is not None:
log.info("Stat: auth.local.login",
extra={"metric": "auth.local.login",
"value": 1,
"mtype": "counter"})
request.user.last_login_date = datetime.datetime.utcnow()
self.db.add(request.user)
remember(request, request.user)
event = LoginEvent(self.request, self.request.user)
self.request.registry.notify(event)
return result
@@ -199,6 +219,10 @@ class ForgotPasswordController(horus.views.ForgotPasswordController):
def reset_password(self):
request = self.request
result = super(ForgotPasswordController, self).reset_password()
log.info("Stat: auth.local.reset_password",
extra={"metric": "auth.local.reset_password",
"value": 1,
"mtype": "counter"})
remember(request, request.user)
return result
@@ -228,6 +252,10 @@ class RegisterController(horus.views.RegisterController):
def register(self):
request = self.request
result = super(RegisterController, self).register()
log.info("Stat: auth.local.register",
extra={"metric": "auth.local.register",
"value": 1,
"mtype": "counter"})
remember(request, request.user)
return result
@@ -242,6 +270,8 @@ class AsyncRegisterController(RegisterController):
@view_auth_defaults
@view_config(attr='edit_profile', route_name='edit_profile')
@view_config(attr='disable_user', route_name='disable_user')
@view_config(attr='profile', route_name='profile')
@view_config(attr='unsubscribe', route_name='unsubscribe')
class ProfileController(horus.views.ProfileController):
def edit_profile(self):
request = self.request
@@ -255,6 +285,21 @@ def edit_profile(self):
username = appstruct['username']
pwd = appstruct['pwd']
subscriptions = appstruct['subscriptions']
if subscriptions:
# Update the subscriptions table
subs = json.loads(subscriptions)
if username == subs['uri']:
s = Subscriptions.get_by_id(request, subs['id'])
if s:
s.active = subs['active']
self.db.add(s)
return {}
else:
return dict(errors=[{'subscriptions': _('Non existing subscription')}], code=404)
else:
return dict(errors=[{'username': _('Invalid username')}], code=400)
# Password check
user = self.User.get_user(request, username, pwd)
@@ -288,10 +333,28 @@ def disable_user(self):
else:
return dict(errors=[{'pwd': _('Invalid password')}], code=401)
def profile(self):
request = self.request
user_id = request.authenticated_userid
subscriptions = Subscriptions.get_subscriptions_for_uri(request, user_id)
return {'model': {'subscriptions': subscriptions}}
def unsubscribe(self):
request = self.request
subscription_id = request.GET['subscription_id']
subscription = Subscriptions.get_by_id(request, subscription_id)
if subscription:
subscription.active = False
self.db.add(subscription)
return {}
return {}
@view_defaults(accept='application/json', name='app', renderer='json')
@view_config(attr='edit_profile', request_param='__formid__=edit_profile')
@view_config(attr='disable_user', request_param='__formid__=disable_user')
@view_config(attr='profile', request_param='__formid__=profile')
@view_config(attr='unsubscribe', request_param='__formid__=unsubscribe')
class AsyncProfileController(ProfileController):
__view_mapper__ = AsyncFormViewMapper
@@ -308,6 +371,8 @@ def includeme(config):
config.add_route('disable_user', '/disable/{user_id}',
factory=UserFactory,
traverse="/{user_id}")
config.add_route('unsubscribe', '/unsubscribe/{subscription_id}',
traverse="/{subscription_id}")
config.include('horus')
config.scan(__name__)
View
@@ -1,15 +1,15 @@
var ACTION_STATES = {
active: {
icons: {
19: "images/action-icon-active.png",
38: "images/action-icon-active@2x.png"
19: "images/browser-icon-active.png",
38: "images/browser-icon-active@2x.png"
},
title: "Disable annotation"
},
sleeping: {
icons: {
19: "images/action-icon-inactive.png",
38: "images/action-icon-inactive@2x.png"
19: "images/browser-icon-inactive.png",
38: "images/browser-icon-inactive@2x.png"
},
title: "Enable annotation"
}
@@ -34,13 +34,11 @@ function isPDFViewerURL(url) {
function inject(tab) {
if (isPDFURL(tab.url)) {
if (!isPDFViewerURL(tab.url)) {
// console.log("Reloading document with PDF.js...")
chrome.tabs.update(tab.id, {
url: getPDFViewerURL(tab.url)
})
}
} else {
// console.log("Doing normal non-pdf insertion on page action")
chrome.tabs.executeScript(tab.id, {
file: 'public/embed.js'
}, function () {
@@ -54,13 +52,11 @@ function inject(tab) {
function remove(tab) {
if (isPDFViewerURL(tab.url)) {
// console.log("Going back to the native viewer.")
url = tab.url.slice(getPDFViewerURL('').length).split('#')[0];
chrome.tabs.update(tab.id, {
url: decodeURIComponent(url)
})
} else {
// console.log("Doing normal non-pdf removal on page action")
chrome.tabs.executeScript(tab.id, {
code: [
'var script = document.createElement("script");',
@@ -93,20 +89,25 @@ function state(tabId, value) {
}
function setPageAction(tabId, value) {
chrome.pageAction.setIcon({
function setBrowserAction(tabId, value) {
chrome.browserAction.setIcon({
tabId: tabId,
path: ACTION_STATES[value].icons
})
chrome.pageAction.setTitle({
chrome.browserAction.setTitle({
tabId: tabId,
title: ACTION_STATES[value].title
})
chrome.pageAction.show(tabId)
}
function onInstalled() {
function onInstalled(installDetails) {
if (installDetails.reason === 'install') {
chrome.tabs.create({url: 'https://hypothes.is/welcome'}, function (tab) {
state(tab.id, 'active');
});
}
/* We need this so that 3-rd party cookie blocking does not kill us.
See https://github.com/hypothesis/h/issues/634 for more info.
This is intended to be a temporary fix only.
@@ -123,7 +124,7 @@ function onInstalled() {
for (var i in tabs) {
var tabId = tabs[i].id
, tabState = state(tabId) || 'sleeping'
setPageAction(tabId, tabState)
setBrowserAction(tabId, tabState)
}
})
}
@@ -134,7 +135,7 @@ function onUpdateAvailable() {
}
function onPageAction(tab) {
function onBrowserAction(tab) {
var newState
if (state(tab.id) == 'active') {
@@ -145,7 +146,7 @@ function onPageAction(tab) {
inject(tab)
}
setPageAction(tab.id, newState)
setBrowserAction(tab.id, newState)
}
@@ -162,17 +163,17 @@ function onTabRemoved(tab) {
function onTabUpdated(tabId, info, tab) {
var currentState = state(tabId) || 'sleeping'
setPageAction(tabId, currentState)
setBrowserAction(tabId, currentState)
if (currentState == 'active' && info.status == 'loading') {
if (currentState == 'active' && info.status == 'complete') {
inject(tab)
}
}
chrome.runtime.onInstalled.addListener(onInstalled)
chrome.runtime.onUpdateAvailable.addListener(onUpdateAvailable)
chrome.pageAction.onClicked.addListener(onPageAction)
chrome.browserAction.onClicked.addListener(onBrowserAction)
chrome.tabs.onCreated.addListener(onTabCreated)
chrome.tabs.onRemoved.addListener(onTabRemoved)
chrome.tabs.onUpdated.addListener(onTabUpdated)
View

Large diffs are not rendered by default.

Oops, something went wrong.
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -171,7 +171,11 @@ if (typeof PDFJS === 'undefined') {
if (typeof VBArray !== 'undefined') {
Object.defineProperty(xhrPrototype, 'response', {
get: function xmlHttpRequestResponseGet() {
return new Uint8Array(new VBArray(this.responseBody).toArray());
if (this.responseType === 'arraybuffer') {
return new Uint8Array(new VBArray(this.responseBody).toArray());
} else {
return this.responseText;
}
}
});
return;
@@ -237,7 +241,7 @@ if (typeof PDFJS === 'undefined') {
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
window.atob = function (input) {
input = input.replace(/=+$/, '');
if (input.length % 4 == 1) {
if (input.length % 4 === 1) {
throw new Error('bad atob input');
}
for (
@@ -293,7 +297,7 @@ if (typeof PDFJS === 'undefined') {
var dataset = {};
for (var j = 0, jj = this.attributes.length; j < jj; j++) {
var attribute = this.attributes[j];
if (attribute.name.substring(0, 5) != 'data-') {
if (attribute.name.substring(0, 5) !== 'data-') {
continue;
}
var key = attribute.name.substring(5).replace(/\-([a-z])/g,
@@ -416,7 +420,7 @@ if (typeof PDFJS === 'undefined') {
function isDisabled(node) {
return node.disabled || (node.parentNode && isDisabled(node.parentNode));
}
if (navigator.userAgent.indexOf('Opera') != -1) {
if (navigator.userAgent.indexOf('Opera') !== -1) {
// use browser detection since we cannot feature-check this bug
document.addEventListener('click', ignoreIfTargetDisabled, true);
}
View
@@ -53,7 +53,7 @@ var FontInspector = (function FontInspectorClosure() {
var selects = document.getElementsByTagName('input');
for (var i = 0; i < selects.length; ++i) {
var select = selects[i];
if (select.dataset.fontName != fontName) {
if (select.dataset.fontName !== fontName) {
continue;
}
select.checked = !select.checked;
@@ -216,7 +216,7 @@ var StepperManager = (function StepperManagerClosure() {
}
for (i = 0; i < steppers.length; ++i) {
var stepper = steppers[i];
if (stepper.pageIndex == pageIndex) {
if (stepper.pageIndex === pageIndex) {
stepper.panel.removeAttribute('hidden');
} else {
stepper.panel.setAttribute('hidden', true);
@@ -225,7 +225,7 @@ var StepperManager = (function StepperManagerClosure() {
var options = stepperChooser.options;
for (i = 0; i < options.length; ++i) {
var option = options[i];
option.selected = option.value == pageIndex;
option.selected = (option.value | 0) === pageIndex;
}
},
saveBreakPoints: function saveBreakPoints(pageIndex, bps) {
@@ -332,7 +332,7 @@ var Stepper = (function StepperClosure() {
line.className = 'line';
line.dataset.idx = i;
chunk.appendChild(line);
var checked = this.breakPoints.indexOf(i) != -1;
var checked = this.breakPoints.indexOf(i) !== -1;
var args = operatorList.argsArray[i] || [];
var breakCell = c('td');
@@ -419,7 +419,7 @@ var Stepper = (function StepperClosure() {
var allRows = this.panel.getElementsByClassName('line');
for (var x = 0, xx = allRows.length; x < xx; ++x) {
var row = allRows[x];
if (row.dataset.idx == idx) {
if (parseInt(row.dataset.idx, 10) === idx) {
row.style.backgroundColor = 'rgb(251,250,207)';
row.scrollIntoView();
} else {
@@ -597,7 +597,7 @@ var PDFBug = (function PDFBugClosure() {
activePanel = index;
var tools = this.tools;
for (var j = 0; j < tools.length; ++j) {
if (j == index) {
if (j === index) {
buttons[j].setAttribute('class', 'active');
tools[j].active = true;
tools[j].panel.removeAttribute('hidden');
View
@@ -13,8 +13,10 @@
# limitations under the License.
# Main toolbar buttons (tooltips and alt text for images)
previous.title=Pot buk Mukato
next.title=Pot buk Malubo Kore
previous.title=Pot buk mukato
previous_label=Mukato
next.title=Pot buk malubo
next_label=Malubo
# LOCALIZATION NOTE (page_label, page_of):
# These strings are concatenated to form the "Page: X of Y" string.
@@ -23,27 +25,75 @@ next.title=Pot buk Malubo Kore
page_label=Pot buk:
page_of=pi {{pageCount}}
zoom_out.title=Dwogo Woko
zoom_out_label=Dwogo Woko
zoom_in.title=Dwogo iyie
zoom_in_label=Dwogo iyie
zoom_out.title=Jwik Matidi
zoom_out_label=Jwik Matidi
zoom_in.title=Kwot Madit
zoom_in_label=Kwot Madit
zoom.title=Kwoti
print.title=Goo
print_label=Goo
presentation_mode.title=Lokke i kit me tyer
presentation_mode_label=Kit me tyer
open_file.title=Yab Pwail
open_file_label=Yabi
open_file_label=Yab
print.title=Go
print_label=Go
download.title=Gam
download_label=Gam
bookmark.title=Neno matye (loki onyo yabi i dirica manyen)
bookmark_label=Neno Matye
bookmark.title=Neno ma kombedi (lok onyo yab i dirica manyen)
bookmark_label=Neno ma kombedi
# Secondary toolbar and context menu
tools.title=Gintic
tools_label=Gintic
first_page.title=Cit i pot buk mukwongo
first_page.label=Cit i pot buk mukwongo
first_page_label=Cit i pot buk mukwongo
last_page.title=Cit i pot buk magiko
last_page.label=Cit i pot buk magiko
last_page_label=Cit i pot buk magiko
page_rotate_cw.title=Wire i tung lacuc
page_rotate_cw.label=Wire i tung lacuc
page_rotate_cw_label=Wire i tung lacuc
page_rotate_ccw.title=Wire i tung lacam
page_rotate_ccw.label=Wire i tung lacam
page_rotate_ccw_label=Wire i tung lacam
hand_tool_enable.title=Ye gintic me cing
hand_tool_enable_label=Ye gintic me cing
hand_tool_disable.title=Juk gintic me cing
hand_tool_disable_label=Juk gintic me cing
# Document properties dialog box
document_properties.title=Jami me gin acoya…
document_properties_label=Jami me gin acoya…
document_properties_file_name=Nying pwail:
document_properties_file_size=Dit pa pwail:
document_properties_kb={{size_kb}} KB ({{size_b}} bytes)
document_properties_mb={{size_mb}} MB ({{size_b}} bytes)
document_properties_title=Wiye:
document_properties_author=Ngat mucoyo:
document_properties_subject=Lok:
document_properties_keywords=Lok mapire tek:
document_properties_creation_date=Nino dwe me cwec:
document_properties_modification_date=Nino dwe me yub:
document_properties_date_string={{date}}, {{time}}
document_properties_creator=Lacwec:
document_properties_producer=Layub PDF:
document_properties_version=Kit PDF:
document_properties_page_count=Kwan me pot buk:
document_properties_close=Lor
# Tooltips and alt text for side panel toolbar buttons
# (the _label strings are alt text for the buttons, the .title strings are
# tooltips)
outline.title=Nyut ryeno rek pa Coc acoya
outline_label=Ryeno rek me Coc acoya
thumbs.title=Nyut Capa cing
thumbs_label=Capa cing
toggle_sidebar.title=Lok gintic ma inget
toggle_sidebar_label=Lok gintic ma inget
outline.title=Nyut rek pa gin acoya
outline_label=Pek pa gin acoya
attachments.title=Nyut twec
attachments_label=Twec
thumbs.title=Nyut cal
thumbs_label=Cal
findbar.title=Nong iye gin acoya
findbar_label=Nong
# Thumbnails panel item (tooltip and alt text for images)
@@ -52,46 +102,62 @@ findbar_label=Nong
thumb_page_title=Pot buk {{page}}
# LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
# number.
thumb_page_canvas=Capa cing e Pot buk {{page}}
# Context menu
thumb_page_canvas=Cal me pot buk {{page}}
# Find panel button title and messages
find_previous.title=Nong en matime malubo kore pi lok
find_next.title=Nong en matime malubo kore pi lok
find_not_found=Phrase pe ononge
find_label=Nong:
find_previous.title=Nong timme pa lok mukato
find_previous_label=Mukato
find_next.title=Nong timme pa lok malubo
find_next_label=Malubo
find_highlight=Wer weng
find_match_case_label=Lok marwate
find_reached_top=Oo iwi gin acoya, omede ki i tere
find_reached_bottom=Oo i agiki me gin acoya, omede ki iwiye
find_not_found=Lok pe ononge
# Error panel labels
error_more_info=Ngec Mukene
error_less_info=Ngec Manok
error_close=Lor
# LOCALIZATION NOTE (error_version_info): "{{version}}" and "{{build}}" will be
# replaced by the PDF.JS version and build ID.
error_version_info=PDF.js v{{version}} (build: {{build}})
# LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an
# english string describing the error.
error_message=Kwena: {{message}}
# LOCALIZATION NOTE (error_stack): "{{stack}}" will be replaced with a stack
# trace.
error_stack=Agiki onyo acaki {{stack}}
error_stack=Can kikore {{stack}}
# LOCALIZATION NOTE (error_file): "{{file}}" will be replaced with a filename
error_file=Pwail: {{file}}
# LOCALIZATION NOTE (error_line): "{{line}}" will be replaced with a line number
error_line=Rek: {{line}}
rendering_error=Bal otyeko time kun jalo pot buk.
rendering_error=Bal otime i kare me nyuto pot buk.
# Predefined zoom values
page_scale_width=Bor wi Pot buk
page_scale_fit=Pot buk Romo
page_scale_auto=Dowogo ne matime pire kene
page_scale_actual=Kit Mamite
page_scale_width=Lac me iye pot buk
page_scale_fit=Porre me pot buk
page_scale_auto=Kwot pire kene
page_scale_actual=Dite kikome
# Loading indicator messages
loading_error_indicator=Bal
loading_error=Bal otyeko time kun pango PDF.
loading_error=Bal otime kun cano PDF.
invalid_file_error=Pwail me PDF ma pe atir onyo obale woko.
missing_file_error=Pwail me PDF tye ka rem.
# LOCALIZATION NOTE (text_annotation_type.alt): This is used as a tooltip.
# "{{type}}" will be replaced with an annotation type from a list defined in
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
text_annotation_type.alt=[{{type}} Lok angea manok]
request_password=I ung otyeko gwoko PDF:
password_label=Ket mung me donyo me yabo pwail me PDF man.
password_invalid=Mung me donyo pe atir. Tim ber i tem doki.
password_ok=OK
password_cancel=Juk
printing_not_supported=Ciko: Layeny ma pe teno goyo liweng.
printing_not_ready=Ciko: PDF pe ocane weng me agoya.
web_fonts_disabled=Kijuko dit pa coc me kakube woko: pe romo tic ki dit pa coc me PDF ma kiketo i kine.
document_colors_disabled=Pe ki ye ki gin acoya me PDF me tic ki rangi gi kengi: 'Ye pot buk me yero rangi mamegi kengi' kijuko woko i layeny.
View
@@ -57,6 +57,10 @@ page_rotate_ccw.title=Roteer anti-kloksgewys
page_rotate_ccw.label=Roteer anti-kloksgewys
page_rotate_ccw_label=Roteer anti-kloksgewys
hand_tool_enable.title=Aktiveer handjie
hand_tool_enable_label=Aktiveer handjie
hand_tool_disable.title=Deaktiveer handjie
hand_tool_disable_label=Deaktiveer handjie
# Document properties dialog box
document_properties.title=Dokumenteienskappe…
@@ -85,6 +89,8 @@ toggle_sidebar.title=Sypaneel aan/af
toggle_sidebar_label=Sypaneel aan/af
outline.title=Wys dokumentoorsig
outline_label=Dokumentoorsig
attachments.title=Wys aanhegsels
attachments_label=Aanhegsels
thumbs.title=Wys duimnaels
thumbs_label=Duimnaels
findbar.title=Soek in dokument
View
@@ -152,8 +152,8 @@ missing_file_error=সন্ধানহিন PDF ফাইল।
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
text_annotation_type.alt=[{{type}} টোকা]
password_label=এই PDF ফাইল খোলিবলৈ পাছৱাৰ্ড সুমুৱাওক।
password_invalid=অবৈধ পাছৱাৰ্ড। অনুগ্ৰহ কৰি পুনৰ চেষ্টা কৰক।
password_label=এই PDF ফাইল খোলিবলৈ পাছৱৰ্ড সুমুৱাওক।
password_invalid=অবৈধ পাছৱৰ্ড। অনুগ্ৰহ কৰি পুনৰ চেষ্টা কৰক।
password_ok=ঠিক আছে
password_cancel=বাতিল কৰক
View
@@ -57,9 +57,30 @@ page_rotate_ccw.title=Saat İstiqamətinin Əksinə Fırlat
page_rotate_ccw.label=Saat İstiqamətinin Əksinə Fırlat
page_rotate_ccw_label=Saat İstiqamətinin Əksinə Fırlat
hand_tool_enable.title=Əl alətini aktiv et
hand_tool_enable_label=Əl alətini aktiv et
hand_tool_disable.title=Əl alətini deaktiv et
hand_tool_disable_label=Əl alətini deaktiv et
# Document properties dialog box
document_properties.title=Sənəd xüsusiyyətləri…
document_properties_label=Sənəd xüsusiyyətləri…
document_properties_file_name=Fayl adı:
document_properties_file_size=Fayl ölçüsü:
document_properties_kb={{size_kb}} KB ({{size_b}} bayt)
document_properties_mb={{size_mb}} MB ({{size_b}} bayt)
document_properties_title=Başlık:
document_properties_author=Müəllif:
document_properties_subject=Mövzu:
document_properties_keywords=Açar sözlər:
document_properties_creation_date=Yaradılış Tarixi :
document_properties_modification_date=Dəyişdirilmə Tarixi :
document_properties_date_string={{date}}, {{time}}
document_properties_creator=Yaradan:
document_properties_producer=PDF yaradıcısı:
document_properties_version=PDF versiyası:
document_properties_page_count=Səhifə sayı:
document_properties_close=Qapat
# Tooltips and alt text for side panel toolbar buttons
# (the _label strings are alt text for the buttons, the .title strings are
@@ -68,6 +89,8 @@ toggle_sidebar.title=Yan Paneli Aç/Bağla
toggle_sidebar_label=Yan Paneli Aç/Bağla
outline.title=Sənəd struktunu göstər
outline_label=Sənəd strukturu
attachments.title=Bağlamaları göstər
attachments_label=Bağlamalar
thumbs.title=Kiçik şəkilləri göstər
thumbs_label=Kiçik şəkillər
findbar.title=Sənəddə Tap
@@ -96,6 +119,7 @@ find_not_found=Uyğunlaşma tapılmadı
# Error panel labels
error_more_info=Daha çox məlumati
error_less_info=Daha az məlumat
error_close=Qapat
# LOCALIZATION NOTE (error_version_info): "{{version}}" and "{{build}}" will be
# replaced by the PDF.JS version and build ID.
error_version_info=PDF.js v{{version}} (yığma: {{build}})
@@ -133,7 +157,7 @@ password_invalid=Şifrə yanlışdır. Bir daha sınayın.
password_ok=OK
password_cancel=Ləğv et
printing_not_supported=Xəbərdarlıq: Çap bu brauzer tərəfindən tam olaraq dəstəklənmir.
printing_not_supported=Xəbərdarlıq: Çap bu səyyah tərəfindən tam olaraq dəstəklənmir.
printing_not_ready=Xəbərdarlıq: PDF çap üçün tam yüklənməyib.
web_fonts_disabled=Veb Şriftlər söndürülüb: yerləşdirilmiş PDF şriftlərini istifadə etmək mümkün deyil.
web_fonts_disabled=Web Şriftlər söndürülüb: yerləşdirilmiş PDF şriftlərini istifadə etmək mümkün deyil.
document_colors_disabled=PDF sənədlərə öz rənglərini işlətməyə icazə verilmir: 'Səhifələrə öz rənglərini istifadə etməyə icazə vermə' səyyahda söndürülüb.
View
@@ -89,6 +89,8 @@ toggle_sidebar.title=Превключване на страничната лен
toggle_sidebar_label=Превключване на страничната лента
outline.title=Показване на очертанията на документа
outline_label=Очертание на документа
attachments.title=Показване на притурките
attachments_label=Притурки
thumbs.title=Показване на миниатюрите
thumbs_label=Миниатюри
findbar.title=Намиране в документа
View
@@ -80,7 +80,7 @@ document_properties_creator=Creador:
document_properties_producer=Generador de PDF:
document_properties_version=Versió de PDF:
document_properties_page_count=Nombre de pàgines:
document_properties_close=Tanca
document_properties_close=Close
# Tooltips and alt text for side panel toolbar buttons
# (the _label strings are alt text for the buttons, the .title strings are
View
@@ -30,12 +30,12 @@ zoom_out_label=Zoom Out
zoom_in.title=Zoom In
zoom_in_label=Zoom In
zoom.title=Zoom
print.title=Print
print_label=Print
presentation_mode.title=Switch to Presentation Mode
presentation_mode_label=Presentation Mode
open_file.title=Open File
open_file_label=Open
print.title=Print
print_label=Print
download.title=Download
download_label=Download
bookmark.title=Current view (copy or open in new window)
@@ -53,9 +53,9 @@ last_page_label=Go to Last Page
page_rotate_cw.title=Rotate Clockwise
page_rotate_cw.label=Rotate Clockwise
page_rotate_cw_label=Rotate Clockwise
page_rotate_ccw.title=Rotate Counter-Clockwise
page_rotate_ccw.label=Rotate Counter-Clockwise
page_rotate_ccw_label=Rotate Counter-Clockwise
page_rotate_ccw.title=Rotate Anti-Clockwise
page_rotate_ccw.label=Rotate Anti-Clockwise
page_rotate_ccw_label=Rotate Anti-Clockwise
hand_tool_enable.title=Enable hand tool
hand_tool_enable_label=Enable hand tool
@@ -89,6 +89,8 @@ toggle_sidebar.title=Toggle Sidebar
toggle_sidebar_label=Toggle Sidebar
outline.title=Show Document Outline
outline_label=Document Outline
attachments.title=Show Attachments
attachments_label=Attachments
thumbs.title=Show Thumbnails
thumbs_label=Thumbnails
findbar.title=Find in Document
View
@@ -89,6 +89,8 @@ toggle_sidebar.title=Cambiar barra lateral
toggle_sidebar_label=Cambiar barra lateral
outline.title=Mostrar esquema del documento
outline_label=Esquema del documento
attachments.title=Mostrar adjuntos
attachments_label=Adjuntos
thumbs.title=Mostrar miniaturas
thumbs_label=Miniaturas
findbar.title=Buscar en el documento
View
@@ -106,3 +106,43 @@ toggle_sidebar_label=ટૉગલ બાજુપટ્ટી
web_fonts_disabled=વેબ ફોન્ટ નિષ્ક્રિય થયેલ છે: ઍમ્બેડ થયેલ PDF ફોન્ટને વાપરવાનું અસમર્થ.
document_colors_disabled=PDF દસ્તાવેજો તેનાં પોતાના રંગોને વાપરવા પરવાનગી આપતા નથી: \'તેનાં પોતાનાં રંગોને પસંદ કરવા માટે પાનાંને પરવાનગી આપો\' બ્રાઉઝરમાં નિષ્ક્રિય થયેલ છે.
text_annotation_type.alt=[{{type}} Annotation]
attachments.title=જોડાણોને બતાવો
attachments_label=જોડાણો
document_properties_author=લેખક:
document_properties_close=બંધ કરો
document_properties_creation_date=નિર્માણ તારીખ:
document_properties_creator=નિર્માતા:
document_properties_date_string={{date}}, {{time}}
document_properties_file_name=ફાઇલ નામ:
document_properties_file_size=ફાઇલ માપ:
document_properties_kb={{size_kb}} KB ({{size_b}} બાઇટ)
document_properties_keywords=કિવર્ડ:
document_properties_label=દસ્તાવેજ ગુણધર્મો…
document_properties_mb={{size_mb}} MB ({{size_b}} બાઇટ)
document_properties_modification_date=ફેરફાર તારીખ:
document_properties_page_count=પાનાં ગણતરી:
document_properties_producer=PDF નિર્માતા:
document_properties_subject=વિષય:
document_properties_title=શીર્ષક:
first_page.title=પ્રથમ પાનાં પર જાવ
first_page_label=પ્રથમ પાનાં પર જાવ
hand_tool_disable.title=હાથનાં સાધનને નિષ્ક્રિય કરો
hand_tool_disable_label=હાથનાં સાધનને નિષ્ક્રિય કરો
hand_tool_enable.title=હાથનાં સાધનને સક્રિય કરો
hand_tool_enable_label=હાથનાં સાધનને સક્રિય કરો
last_page.title=છેલ્લા પાનાં પર જાવ
last_page_label=છેલ્લા પાનાં પર જાવ
page_rotate_ccw.title=ઘડિયાળનાં કાંટાની વિરુદ્દ ફેરવો
page_rotate_ccw_label=ઘડિયાળનાં કાંટાની વિરુદ્દ ફેરવો
page_rotate_cw.title=ઘડિયાળનાં કાંટા તરફ ફેરવો
page_rotate_cw_label=ઘડિયાળનાં કાંટા તરફ ફેરવો
password_cancel=રદ કરો
password_invalid=અયોગ્ય પાસવર્ડ. મહેરબાની કરીને ફરી પ્રયત્ન કરો.
password_label=આ PDF ફાઇલને ખોલવા પાસવર્ડને દાખલ કરો.
password_ok=બરાબર
tools.title=સાધનો
tools_label=સાધનો
ocument_properties.title=દસ્તાવેજ ગુણધર્મો…
document_properties_version=PDF આવૃત્તિ:
View
@@ -89,6 +89,8 @@ toggle_sidebar.title=Բացել/Փակել Կողային վահանակը
toggle_sidebar_label=Բացել/Փակել Կողային վահանակը
outline.title=Ցուցադրել փաստաթղթի բովանդակությունը
outline_label=Փաստաթղթի բովանդակությունը
attachments.title=Ցուցադրել կցորդները
attachments_label=Կցորդներ
thumbs.title=Ցուցադրել Մանրապատկերը
thumbs_label=Մանրապատկերը
findbar.title=Գտնել փաստաթղթում
View
@@ -42,10 +42,15 @@ bookmark.title=ಪ್ರಸಕ್ತ ನೋಟ (ಪ್ರತಿ ಮಾಡು ಅ
bookmark_label=ಪ್ರಸಕ್ತ ನೋಟ
# Secondary toolbar and context menu
last_page.title=ಕೊನೆಯ ಪುಟಕ್ಕೆ ತೆರಳು
# Document properties dialog box
document_properties_kb={{size_kb}} KB ({{size_b}} ಬೈಟ್‍ಗಳು)
document_properties_mb={{size_mb}} MB ({{size_b}} ಬೈಟ್‍ಗಳು)
document_properties_title=ಶೀರ್ಷಿಕೆ:
document_properties_author=ಕರ್ತೃ:
document_properties_date_string={{date}}, {{time}}
# Tooltips and alt text for side panel toolbar buttons
# (the _label strings are alt text for the buttons, the .title strings are
@@ -54,6 +59,7 @@ toggle_sidebar.title=ಬದಿಪಟ್ಟಿಯನ್ನು ಹೊರಳಿಸ
toggle_sidebar_label=ಬದಿಪಟ್ಟಿಯನ್ನು ಹೊರಳಿಸು
outline.title=ದಸ್ತಾವೇಜಿನ ಹೊರರೇಖೆಯನ್ನು ತೋರಿಸು
outline_label=ದಸ್ತಾವೇಜಿನ ಹೊರರೇಖೆ
attachments_label=ಲಗತ್ತುಗಳು
thumbs.title=ಚಿಕ್ಕಚಿತ್ರದಂತೆ ತೋರಿಸು
thumbs_label=ಚಿಕ್ಕಚಿತ್ರಗಳು
findbar.title=ದಸ್ತಾವೇಜಿನಲ್ಲಿ ಹುಡುಕು
@@ -82,6 +88,7 @@ find_not_found=ವಾಕ್ಯವು ಕಂಡು ಬಂದಿಲ್ಲ
# Error panel labels
error_more_info=ಹೆಚ್ಚಿನ ಮಾಹಿತಿ
error_less_info=ಕಡಿಮೆ ಮಾಹಿತಿ
error_close=ಮುಚ್ಚು
# LOCALIZATION NOTE (error_version_info): "{{version}}" and "{{build}}" will be
# replaced by the PDF.JS version and build ID.
error_version_info=PDF.js v{{version}} (build: {{build}})
View
@@ -89,6 +89,8 @@ toggle_sidebar.title=Rodyti / slėpti šoninį polangį
toggle_sidebar_label=Šoninis polangis
outline.title=Rodyti dokumento metmenis
outline_label=Dokumento metmenys
attachments.title=Rodyti priedus
attachments_label=Priedai
thumbs.title=Rodyti puslapių miniatiūras
thumbs_label=Miniatiūros
findbar.title=Ieškoti dokumente
View
@@ -68,7 +68,7 @@ document_properties_label=မှတ်တမ်းမှတ်ရာ ဂုဏ
document_properties_file_name=ဖိုင် :
document_properties_file_size=ဖိုင်ဆိုဒ် :
document_properties_kb={{size_kb}} ကီလိုဘိုတ် ({size_kb}}ဘိုတ်)
document_properties_mb={{size_mb}} မစ်ဂါဘိုတ် ({size_b}} ဘိုတ်)
document_properties_mb={{size_mb}} MB ({{size_b}} bytes)
document_properties_title=ခေါင်းစဉ်‌ -
document_properties_author=ရေးသားသူ:
document_properties_subject=အကြောင်းအရာ:\u0020
View
@@ -89,6 +89,8 @@ toggle_sidebar.title=Slå av/på sidestolpe
toggle_sidebar_label=Slå av/på sidestolpe
outline.title=Vis dokumentdisposisjon
outline_label=Dokumentdisposisjon
attachments.title=Vis vedlegg
attachments_label=Vedlegg
thumbs.title=Vis miniatyrbilde
thumbs_label=Miniatyrbilde
findbar.title=Finn i dokumentet
View
@@ -25,11 +25,11 @@ next_label=Neste
page_label=Side:
page_of=av {{pageCount}}
zoom_out.title=Zoom ut
zoom_out_label=Zoom ut
zoom_in.title=Zoom inn
zoom_in_label=Zoom inn
zoom.title=Zoom
zoom_out.title=Mindre
zoom_out_label=Mindre
zoom_in.title=Større
zoom_in_label=Større
zoom.title=Skalering
presentation_mode.title=Byt til presentasjonsmodus
presentation_mode_label=Presentasjonsmodus
open_file.title=Opna fil
@@ -138,7 +138,7 @@ rendering_error=Ein feil oppstod ved oppteikning av sida.
# Predefined zoom values
page_scale_width=Sidebreidde
page_scale_fit=Tilpass til sida
page_scale_auto=Automatisk zoom
page_scale_auto=Automatisk skalering
page_scale_actual=Verkeleg storleik
# Loading indicator messages
View
@@ -78,9 +78,10 @@ document_properties_close=Mbylle
# tooltips)
toggle_sidebar.title=Shfaqni/Fshihni Anështyllën
toggle_sidebar_label=Shfaqni/Fshihni Anështyllën
outline.title=Shfaq Përvijim Dokumenti
outline_label=Shfaq Përvijim Dokumenti
attachments.title=Shfaq Bashkëngjitje
attachments_label=Bashkëngjitje
thumbs.title=Shfaq Miniatura
thumbs_label=Miniatura
findbar.title=Gjej në Dokument
View
@@ -38,7 +38,7 @@ print.title=Штампај
print_label=Штампај
download.title=Преузми
download_label=Преузми
bookmark.title=Тренутни приказ(копирај или отвори нови прозор)
bookmark.title=Тренутни приказ (копирај или отвори нови прозор)
bookmark_label=Тренутни приказ
# Secondary toolbar and context menu
View
@@ -89,8 +89,8 @@ toggle_sidebar.title=Visa/dölj sidofält
toggle_sidebar_label=Visa/dölj sidofält
outline.title=Visa dokumentöversikt
outline_label=Dokumentöversikt
attachments.title=Visa bifogade filer
attachments_label=Bifogade filer
attachments.title=Visa Bilagor
attachments_label=Bilagor
thumbs.title=Visa miniatyrer
thumbs_label=Miniatyrer
findbar.title=Sök i dokument
@@ -160,4 +160,4 @@ password_cancel=Avbryt
printing_not_supported=Varning: Utskrifter stöds inte helt av den här webbläsaren.
printing_not_ready=Varning: PDF:en är inte klar för utskrift.
web_fonts_disabled=Webbtypsnitt är inaktiverade: kan inte använda inbäddade PDF-typsnitt.
document_colors_disabled=PDF-dokument tillåts inte använda egna färger: \'Låt sidor använda egna färger\' är inaktiverat i webbläsaren.
document_colors_disabled=PDF-dokument tillåts inte använda egna färger: 'Låt sidor använda egna färger' är inaktiverat i webbläsaren.
View
@@ -1,6 +1,17 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Copyright 2012 Mozilla Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Main toolbar buttons (tooltips and alt text for images)
# LOCALIZATION NOTE (page_label, page_of):
@@ -12,6 +23,11 @@ zoom.title=அளவு
open_file.title=கோப்பினைத் திறக்க
open_file_label=திறக்க
# Secondary toolbar and context menu
# Document properties dialog box
# Tooltips and alt text for side panel toolbar buttons
# (the _label strings are alt text for the buttons, the .title strings are
# tooltips)
@@ -22,14 +38,11 @@ open_file_label=திறக்க
# LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
# number.
# Context menu
# Find panel button title and messages
find_previous.title=இந்த சொற்றொடரின் முன்னைய நிகழ்வை தேடு
find_next.title=இந்த சொற்றொடரின் அடுத்த நிகழ்வைத் தேடு
# Error panel labels
error_close=மூடுக
# LOCALIZATION NOTE (error_version_info): "{{version}}" and "{{build}}" will be
# replaced by the PDF.JS version and build ID.
# LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an
@@ -47,4 +60,5 @@ error_close=மூடுக
# "{{type}}" will be replaced with an annotation type from a list defined in
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
password_ok=ஆம்
View
@@ -67,8 +67,8 @@ document_properties.title=Iipropati zoxwebhu…
document_properties_label=Iipropati zoxwebhu…
document_properties_file_name=Igama lefayile:
document_properties_file_size=Isayizi yefayile:
document_properties_kb={{size_kb}} KB ({{size_b}} iibhayiti)
document_properties_mb={{size_mb}} MB ({{size_b}} iibhayiti)
document_properties_kb={{size_kb}} KB (iibhayiti{{size_b}})
document_properties_mb={{size_mb}} MB (iibhayithi{{size_b}})
document_properties_title=Umxholo:
document_properties_author=Umbhali:
document_properties_subject=Umbandela:
@@ -122,7 +122,7 @@ error_less_info=Inkcazelo Encinane
error_close=Vala
# LOCALIZATION NOTE (error_version_info): "{{version}}" and "{{build}}" will be
# replaced by the PDF.JS version and build ID.
error_version_info=I-PDF.js v{{uhlelo}} (yakha: {{build}})
error_version_info=I-PDF.js v{{uhlelo}} (yakha: {{yakha}})
# LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an
# english string describing the error.
error_message=Umyalezo: {{message}}
View
@@ -326,7 +326,7 @@ html[dir='rtl'] #toolbarContainer, .findbar, .secondaryToolbar {
#loadingBar {
position: relative;
width: 100%;
height: 6px;
height: 4px;
background-color: #333;
border-bottom: 1px solid #333;
}
@@ -1408,7 +1408,7 @@ canvas {
font-size: 0.8em;
}
.loadingInProgress #errorWrapper {
top: 39px;
top: 37px;
}
#errorMessageLeft {
@@ -1884,7 +1884,7 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
z-index: 100;
}
.loadingInProgress #sidebarContainer {
top: 39px;
top: 37px;
}
#sidebarContent {
top: 32px;
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
@@ -27,7 +27,7 @@
"persistent": true,
"scripts": ["background.js"]
},
"page_action": {
"browser_action": {
"placeholder": "work around http://crbug.com/86449"
},
"web_accessible_resources": [
View

This file was deleted.

Oops, something went wrong.
View
@@ -9,3 +9,9 @@ def __init__(self, request, annotation, action):
self.request = request
self.annotation = annotation
self.action = action
class LoginEvent(object):
def __init__(self, request, user):
self.request = request
self.user = user
View
@@ -2,27 +2,6 @@
# pylint: disable=too-many-public-methods
from zope.interface import Interface
__all__ = [
'IDBSession',
'IUIStrings',
'IUserClass',
'IActivationClass',
'IAnnotationClass',
'ILoginForm',
'IRegisterForm',
'IForgotPasswordForm',
'IResetPasswordForm',
'IProfileForm',
'ILoginSchema',
'IRegisterSchema',
'IForgotPasswordSchema',
'IResetPasswordSchema',
'IProfileSchema',
]
class IAnnotationClass(Interface):
pass
View
@@ -0,0 +1,83 @@
"""Change UserSubscriptions table to Subscriptions
Revision ID: 209c3cd1a864
Revises: 2246cd7f5801
Create Date: 2014-10-24 13:19:15.932243
"""
# revision identifiers, used by Alembic.
revision = '209c3cd1a864'
down_revision = '2246cd7f5801'
from alembic import op
import sqlalchemy as sa
from sqlalchemy.types import TypeDecorator, VARCHAR
class JSONEncodedDict(TypeDecorator):
"""Represents an immutable structure as a json-encoded string.
Usage::
JSONEncodedDict(255)
"""
# pylint: disable=too-many-public-methods
impl = VARCHAR
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
def python_type(self):
return dict
template_enum = sa.Enum('reply_notification', 'custom_search',
name="subscription_template")
type_enum = sa.Enum('system', 'user',
name="subscription_type")
def upgrade():
op.drop_table('user_subscriptions')
op.create_table(
'subscriptions',
sa.Column('id', sa.INTEGER, primary_key=True),
sa.Column('uri', sa.Unicode(256), nullable=False),
sa.Column('query', JSONEncodedDict(4096), nullable=True, default={}),
sa.Column('template', sa.VARCHAR(64), nullable=False),
sa.Column('parameters', JSONEncodedDict(1024), nullable=True, default={}),
sa.Column('description', sa.VARCHAR(256), default=""),
sa.Column('active', sa.BOOLEAN, default=True, nullable=False)
)
def downgrade():
op.drop_table('subscriptions')
op.create_table(
'user_subscriptions',
sa.Column('id', sa.INTEGER, primary_key=True),
sa.Column(
'username',
sa.Unicode(30),
sa.ForeignKey(
'%s.%s' % ('user', 'username'),
onupdate='CASCADE',
ondelete='CASCADE'
),
nullable=False),
sa.Column('description', sa.VARCHAR(256), default=""),
sa.Column('template', template_enum, nullable=False,
default='custom_search'),
sa.Column('active', sa.BOOLEAN, default=True, nullable=False),
sa.Column('query', JSONEncodedDict(4096), nullable=False),
sa.Column('type', type_enum, nullable=False, default='user'),
)
View
@@ -46,10 +46,18 @@ def __acl__(self):
'created': {'type': 'date'},
'updated': {'type': 'date'},
'quote': {'type': 'string'},
'tags': {'type': 'string', 'index': 'analyzed', 'analyzer': 'lower_keyword'},
'tags': {
'type': 'string',
'index': 'analyzed',
'analyzer': 'lower_keyword'
},
'text': {'type': 'string'},
'deleted': {'type': 'boolean'},
'uri': {'type': 'string', 'index_analyzer': 'uri_index', 'search_analyzer': 'uri_search'},
'uri': {
'type': 'string',
'index_analyzer': 'uri_index',
'search_analyzer': 'uri_search'
},
'user': {'type': 'string', 'index': 'analyzed', 'analyzer': 'user'},
'consumer': {'type': 'string', 'index': 'not_analyzed'},
'target': {
@@ -59,15 +67,23 @@ def __acl__(self):
'path': 'just_name',
'fields': {
'id': {'type': 'string', 'index': 'not_analyzed'},
'uri': {'type': 'string', 'index_analyzer': 'uri_index', 'search_analyzer': 'uri_search'},
'uri': {
'type': 'string',
'index_analyzer': 'uri_index',
'search_analyzer': 'uri_search'
},
},
},
'source': {
'type': 'multi_field',
'path': 'just_name',
'fields': {
'source': {'type': 'string', 'index': 'not_analyzed'},
'uri': {'type': 'string', 'index_analyzer': 'uri_index', 'search_analyzer': 'uri_search'},
'uri': {
'type': 'string',
'index_analyzer': 'uri_index',
'search_analyzer': 'uri_search'
},
},
},
'selector': {
View
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
def includeme(config):
config.include('.types')
config.include('.gateway')
config.include('.models')
config.include('.notifier')
config.include('.reply_template')
View
@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
import re
import logging
from urlparse import urlparse
import requests
from bs4 import BeautifulSoup
from pyramid.events import subscriber
from pyramid.renderers import render
from pyramid.security import Everyone, principals_allowed_by_permission
from h import events
from h.notification.gateway import user_profile_url, standalone_url
from h.notification.notifier import send_email, TemplateRenderException
from h.notification.types import ROOT_PATH
log = logging.getLogger(__name__) # pylint: disable=invalid-name
TXT_TEMPLATE = ROOT_PATH + 'document_owner_notification.txt'
HTML_TEMPLATE = ROOT_PATH + 'document_owner_notification.pt'
SUBJECT_TEMPLATE = ROOT_PATH + 'document_owner_notification_subject.txt'
# ToDo: Turn this feature into uri based.
# Add page uris to the subscriptions table
# And then the domain mailer can be configured to separate web-pages
def create_template_map(request, annotation):
if 'tags' in annotation:
tags = '\ntags: ' + ', '.join(annotation['tags'])
else:
tags = ''
user = re.search("^acct:([^@]+)", annotation['user']).group(1)
return {
'document_title': annotation['title'],
'document_path': annotation['uri'],
'text': annotation['text'],
'tags': tags,
'user_profile': user_profile_url(request, annotation['user']),
'user': user,
'path': standalone_url(request, annotation['id']),
'timestamp': annotation['created'],
'selection': annotation['quote']
}
# TODO: Introduce proper cache for content parsing
def get_document_owners(content):
parsed_data = BeautifulSoup(content)
documents = parsed_data.select('a[rel="reply-to"]')
hrefs = []
for d in documents:
if re.match(r'^mailto:', d['href'], re.IGNORECASE):
hrefs.append(d['href'][7:])
return hrefs
# XXX: All below can be removed in the future after
# we can create a custom subscription for page uri
@subscriber(events.AnnotationEvent)
def domain_notification(event):
if event.action != 'create':
return
try:
annotation = event.annotation
request = event.request
# Check for authorization. Send notification only for public annotation
# XXX: This will be changed and fine grained when
# user groups will be introduced
allowed = principals_allowed_by_permission(annotation, 'read')
if Everyone not in allowed:
return
uri = annotation['uri']
# TODO: Fetching the page should be done via a webproxy
r = requests.get(uri)
emails = get_document_owners(r.text)
# Now send the notifications
url_struct = urlparse(annotation['uri'])
domain = url_struct.hostname or url_struct.path
domain = re.sub(r'^www.', '', domain)
for email in emails:
# Domain matching
mail_domain = email.split('@')[-1]
if mail_domain == domain:
try:
# Render e-mail parts
tmap = create_template_map(request, annotation)
text = render(TXT_TEMPLATE, tmap, request)
html = render(HTML_TEMPLATE, tmap, request)
subject = render(SUBJECT_TEMPLATE, tmap, request)
send_email(request, subject, text, html, [email])
# ToDo: proper exception handling here
except TemplateRenderException:
log.exception('Failed to render domain-mailer template')
except:
log.exception(
'Unknown error when trying to render'
'domain-mailer template')
except:
log.exception('Problem with domain notification')
def includeme(config):
config.scan(__name__)
View
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
import re
from h.auth.local import models
def user_name(user):
return re.search(r'^acct:([^@]+)', user).group(1)
def user_profile_url(request, user):
username = user_name(user)
return request.application_url + '/u/' + username
def standalone_url(request, annotation_id):
return request.application_url + '/a/' + annotation_id
def get_user_by_name(request, username):
return models.User.get_by_username(request, username)
def includeme(config):
pass
View
@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
import logging
import json
import sqlalchemy as sa
from sqlalchemy.types import TypeDecorator, VARCHAR
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy import func, and_
from pyramid_basemodel import Base
from hem.db import get_session
from horus.models import BaseModel
log = logging.getLogger(__name__)
class JSONEncodedDict(TypeDecorator):
"""Represents an immutable structure as a json-encoded string.
Usage::
JSONEncodedDict(255)
"""
# pylint: disable=too-many-public-methods
impl = VARCHAR
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
def python_type(self):
return dict
class SubscriptionsMixin(BaseModel):
# pylint: disable=no-self-use
@declared_attr
def __table_args__(self):
return sa.Index('subs_uri_idx_%s' % self.__tablename__, 'uri'),
@declared_attr
def uri(self):
return sa.Column(
sa.Unicode(256),
nullable=False
)
@declared_attr
def query(self):
return sa.Column(JSONEncodedDict(4096), nullable=True, default={})
@declared_attr
def template(self):
return sa.Column(sa.VARCHAR(64), nullable=False)
@declared_attr
def parameters(self):
return sa.Column(JSONEncodedDict(1024), nullable=True, default={})
@declared_attr
def description(self):
return sa.Column(sa.VARCHAR(256), default="")
@declared_attr
def active(self):
return sa.Column(sa.BOOLEAN, default=True, nullable=False)
@classmethod
def get_active_subscriptions(cls, request):
session = get_session(request)
return session.query(cls).filter(cls.active).all()
@classmethod
def get_active_subscriptions_for_a_template(cls, request, template):
session = get_session(request)
return session.query(cls).filter(
and_(
cls.active,
func.lower(cls.template) == func.lower(template)
)
).all()
@classmethod
def get_subscriptions_for_uri(cls, request, uri):
session = get_session(request)
return session.query(cls).filter(
func.lower(cls.uri) == func.lower(uri)
).all()
@classmethod
def get_a_template_for_uri(cls, request, uri, template):
session = get_session(request)
return session.query(cls).filter(
and_(
func.lower(cls.uri) == func.lower(uri),
func.lower(cls.template) == func.lower(template)
)
).all()
class Subscriptions(SubscriptionsMixin, Base):
pass
def includeme(config):
config.include('pyramid_basemodel')
config.include('pyramid_tm')
config.scan(__name__)
View
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
from pyramid_mailer.interfaces import IMailer
from pyramid_mailer.message import Message
class TemplateRenderException(Exception):
pass
def send_email(request, subject, text, html, recipients):
body = text.decode('utf8')
mailer = request.registry.queryUtility(IMailer)
subject = subject.decode('utf8')
message = Message(subject=subject,
recipients=recipients,
body=body,
html=html)
mailer.send(message)
def includeme(config):
config.scan(__name__)
View
@@ -0,0 +1,197 @@
# -*- coding: utf-8 -*-
import logging
from datetime import datetime
from pyramid.events import subscriber
from pyramid.security import Everyone, principals_allowed_by_permission
from pyramid.renderers import render
from hem.db import get_session
from horus.events import NewRegistrationEvent
from h.notification.notifier import send_email, TemplateRenderException
from h.notification import types
from h.notification.models import Subscriptions
from h.notification.gateway import user_name, \
user_profile_url, standalone_url, get_user_by_name
from h.notification.types import ROOT_PATH
from h.events import LoginEvent, AnnotationEvent
from h import interfaces
log = logging.getLogger(__name__) # pylint: disable=invalid-name
TXT_TEMPLATE = ROOT_PATH + 'reply_notification.txt'
HTML_TEMPLATE = ROOT_PATH + 'reply_notification.pt'
SUBJECT_TEMPLATE = ROOT_PATH + 'reply_notification_subject.txt'
def parent_values(annotation, request):
if 'references' in annotation:
registry = request.registry
store = registry.queryUtility(interfaces.IStoreClass)(request)
parent = store.read(annotation['references'][-1])
if 'references' in parent:
grandparent = store.read(parent['references'][-1])
parent['quote'] = grandparent['text']
return parent
else:
return {}
def create_template_map(request, reply, data):
document_title = ''
if 'document' in reply:
document_title = reply['document'].get('title', '')
parent_user = user_name(data['parent']['user'])
reply_user = user_name(reply['user'])
# Currently we cut the UTC format because time.strptime has problems
# parsing it, and of course it'd only correct the backend's timezone
# which is not meaningful for international users
date_format = '%Y-%m-%dT%H:%M:%S.%f'
parent_timestamp = datetime.strptime(data['parent']['created'][:-6],
date_format)
reply_timestamp = datetime.strptime(reply['created'][:-6],
date_format)
seq = ('http://', str(request.domain),
'/app?__formid__=unsubscribe&subscription_id=',
str(data['subscription']['id']))
unsubscribe = "".join(seq)
return {
'document_title': document_title,
'document_path': data['parent']['uri'],
'parent_text': data['parent']['text'],
'parent_user': parent_user,
'parent_timestamp': parent_timestamp,
'parent_user_profile': user_profile_url(
request, data['parent']['user']),
'parent_path': standalone_url(request, data['parent']['id']),
'reply_text': reply['text'],
'reply_user': reply_user,
'reply_timestamp': reply_timestamp,
'reply_user_profile': user_profile_url(request, reply['user']),
'reply_path': standalone_url(request, reply['id']),
'unsubscribe': unsubscribe
}
def get_recipients(request, data):
username = user_name(data['parent']['user'])
user_obj = get_user_by_name(request, username)
if not user_obj:
log.warn("User not found! " + str(username))
raise TemplateRenderException('User not found')
return [user_obj.email]
def check_conditions(annotation, data):
# Get the e-mail of the owner
if 'user' not in data['parent'] or not data['parent']['user']:
return False
# Do not notify users about their own replies
if annotation['user'] == data['parent']['user']:
return False
# Is he the proper user?
if data['parent']['user'] != data['subscription']['uri']:
return False
# Else okay
return True
@subscriber(AnnotationEvent)
def send_notifications(event):
# Extract data
action = event.action
request = event.request
annotation = event.annotation
# And for them we need only the creation action
if action != 'create':
return
# Check for authorization. Send notification only for public annotation
# XXX: This will be changed and fine grained when
# user groups will be introduced
if Everyone not in principals_allowed_by_permission(annotation, 'read'):
return
# Store the parent values as additional data
data = {
'parent': parent_values(annotation, request)
}
subscriptions = Subscriptions.get_active_subscriptions_for_a_template(
request,
types.REPLY_TEMPLATE
)
for subscription in subscriptions:
data['subscription'] = {
'id': subscription.id,
'uri': subscription.uri,
'parameters': subscription.parameters,
'query': subscription.query
}
# Validate annotation
if check_conditions(annotation, data):
try:
# Render e-mail parts
tmap = create_template_map(request, annotation, data)
text = render(TXT_TEMPLATE, tmap, request)
html = render(HTML_TEMPLATE, tmap, request)
subject = render(SUBJECT_TEMPLATE, tmap, request)
recipients = get_recipients(request, data)
send_email(request, subject, text, html, recipients)
# ToDo: proper exception handling here
except TemplateRenderException:
log.exception('Failed to render subscription'
' template %s', subscription)
except:
log.exception('Unknown error when trying to render'
' subscription template %s', subscription)
# Create a reply template for a uri
def create_subscription(request, uri, active):
session = get_session(request)
subs = Subscriptions(
uri=uri,
template=types.REPLY_TEMPLATE,
description='General reply notification',
active=active
)
session.add(subs)
session.flush()
@subscriber(NewRegistrationEvent)
def registration_subscriptions(event):
request = event.request
user_uri = 'acct:{}@{}'.format(event.user.username, request.domain)
create_subscription(event.request, user_uri, True)
event.user.subscriptions = True
# For backwards compatibility, generate reply notification if not exists
@subscriber(LoginEvent)
def check_reply_subscriptions(event):
request = event.request
user_uri = 'acct:{}@{}'.format(event.user.username, request.domain)
res = Subscriptions.get_a_template_for_uri(
request,
user_uri,
types.REPLY_TEMPLATE
)
if not len(res):
create_subscription(event.request, user_uri, True)
event.user.subscriptions = True
def includeme(config):
config.scan(__name__)
View
@@ -0,0 +1,13 @@
<p>> ${selection}
${text}
${tags}
From:
${user}
${user_profile}
On ${timestamp} at (${path})
About ${document_path}
--
This is an annotation on your document from Hypothes.is / Annotator (URL here)</p>
View
File renamed without changes.
View
File renamed without changes.
View
@@ -10,3 +10,4 @@
<blockquote>&ldquo;${reply_text}&rdquo;</blockquote>
<p><a href="">Reply to ${reply_user}</a>.</p>
<p>If you'd rather not get this notification you can unsubscribe from it <a href="${unsubscribe}">here</a></p>
View
@@ -11,3 +11,5 @@ On ${reply_timestamp.strftime('%d %B at %H:%M')} ${reply_user} said:
View this reply: ${reply_path}
or view anna's stream: ${reply_user_profile}
If you'd rather not get this notification you can unsubscribe from it by clicking here: ${unsubscribe}
View
File renamed without changes.
View
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Notification Template types
REPLY_TEMPLATE = 'reply template'
DOCUMENT_OWNER = 'document_owner'
ROOT_PATH = 'h:notification/templates/'
def includeme(config):
pass
View

This file was deleted.

Oops, something went wrong.
View
@@ -97,15 +97,15 @@ def __getitem__(self, key):
except httpexceptions.HTTPException as e:
if e.status_code in [401, 404]:
raise httpexceptions.HTTPNotFound(
body_template=
"Either no annotation exists with this identifier, or you "
"don't have the permissions required for viewing it."
body_template=("Either no annotation exists with this "
"identifier, or you don't have the "
"permissions required for viewing it.")
)
else:
raise e
Annotation = registry.queryUtility(interfaces.IAnnotationClass)
annotation = Annotation(data)
annotation_ctor = registry.queryUtility(interfaces.IAnnotationClass)
annotation = annotation_ctor(data)
annotation.__name__ = key
annotation.__parent__ = self
View
@@ -16,7 +16,7 @@ if (window.hasOwnProperty('hypothesisRole')) {
if (window.hasOwnProperty('hypothesisConfig')) {
if (typeof window.hypothesisConfig === 'function') {
options = window.hypothesisConfig();
options = jQuery.extend(options, window.hypothesisConfig());
} else {
throw new TypeError('hypothesisConfig must be a function, see: ' + docs);
}
View
Binary file not shown.
View
@@ -37,4 +37,5 @@
<glyph unicode="&#xe61b;" d="M0 0l0.572 26.857q4.285 1.143 24.285 4.572t30.285 7.714q2 3.428 3.572 7.714t2.428 9.572 1.572 9.286 0.857 10.714 0.143 9.714v18.714q0 280.572-6.285 292.857-1.143 2.286-6.285 4.143t-12.715 3.143-14.143 2-13.857 1.285-8.714 0.857l-1.143 23.714q28 0.572 97.143 3.286t106.572 2.714q6.572 0 19.572-0.143t19.286-0.143q20 0 39-3.714t36.714-12 30.857-20.286 21.143-29.857 8-39.286q0-14.857-4.714-27.286t-11.143-20.572-18.428-16.428-20.857-12.857-24-11.428q44-10 73.286-38.286t29.286-70.857q0-28.572-10-51.286t-26.714-37.286-39.428-24.428-46.714-13.857-50.286-4q-12.572 0-37.714 0.857t-37.714 0.857q-30.285 0-87.715-3.143t-66-3.428zM152.286 405.714q0-14.285 1.143-43.143t1.143-43.428q0-7.714-0.143-22.857t-0.143-22.571q0-13.143 0.286-19.714 12-2 31.143-2 23.429 0 40.857 3.714t31.428 12.714 21.286 25.572 7.286 40.572q0 20-8.286 35t-22.572 23.428-30.857 12.428-35.429 4q-14.286 0-37.143-3.714zM153.857 83.714q0.143-10.572 1.286-23.857t3.429-19q21.143-9.143 40-9.143 107.428 0 107.428 95.714 0 32.572-11.714 51.428-7.714 12.572-17.572 21.143t-19.286 13.286-23 7.143-24 3-27 0.572q-20.857 0-28.857-2.857 0-15.143-0.143-45.428t-0.143-45.143q0-2.286-0.286-19.286t-0.143-27.572z" horiz-adv-x="403" />
<glyph unicode="&#xe61c;" d="M0 0.572l4.857 24.286q1.715 0.572 23.285 6.143t31.857 10.714q8 10 11.715 28.857 0.285 2 17.715 82.572t32.572 155.286 14.857 84.714v7.143q-6.857 3.714-15.572 5.285t-19.857 2.286-16.572 1.572l5.428 29.428q9.428-0.572 34.285-1.857t42.715-2 34.428-0.714q13.714 0 28.143 0.714t34.571 2 28.143 1.857q-1.428-11.143-5.428-25.428-8.572-2.857-29-8.143t-31-9.571q-2.286-5.429-4-12.143t-2.571-11.428-2.143-13-1.857-12q-7.714-42.286-25-119.857t-22.143-101.571q-0.571-2.572-3.714-16.572t-5.714-25.714-4.572-23.857-1.714-16.428l0.286-5.143q4.857-1.143 52.857-8.857-0.857-12.572-4.571-28.286-3.143 0-9.286-0.428t-9.286-0.428q-8.286 0-24.857 2.857t-24.571 2.857q-39.428 0.572-58.857 0.572-14.572 0-40.857-2.572t-34.572-3.143z" horiz-adv-x="293" />
<glyph unicode="&#xe61d;" d="M0 45.714v347.428q0 18.857 13.428 32.286t32.285 13.428h457.143q18.857 0 32.285-13.428t13.428-32.286v-347.428q0-18.857-13.428-32.286t-32.286-13.428h-457.143q-18.857 0-32.285 13.428t-13.428 32.286zM36.572 45.714q0-3.714 2.715-6.428t6.428-2.714h457.143q3.714 0 6.428 2.714t2.715 6.428v347.428q0 3.714-2.714 6.428t-6.428 2.714h-457.143q-3.715 0-6.428-2.714t-2.715-6.428v-347.428zM73.143 73.143v54.857l91.429 91.428 45.714-45.714 146.286 146.286 118.857-118.857v-128h-402.286zM73.143 310.857q0 22.857 16 38.857t38.857 16 38.857-16 16-38.857-16-38.857-38.857-16-38.857 16-16 38.857z" horiz-adv-x="549" />
<glyph unicode="&#xe61e;" d="M288 160v-96h64l-96-96-96 96h64v96zM224 288v96h-64l96 96 96-96h-64v-96zM192 192h-96v-64l-96 96 96 96v-64h96zM320 256h96v64l96-96-96-96v64h-96z" />
</font></defs></svg>
View
Binary file not shown.
View
Binary file not shown.
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -1,10 +1,10 @@
@font-face {
font-family: 'h';
src:url('fonts/h.eot?reaxcw');
src:url('fonts/h.eot?#iefixreaxcw') format('embedded-opentype'),
url('fonts/h.woff?reaxcw') format('woff'),
url('fonts/h.ttf?reaxcw') format('truetype'),
url('fonts/h.svg?reaxcw#h') format('svg');
src:url('fonts/h.eot?-cd3sth');
src:url('fonts/h.eot?#iefix-cd3sth') format('embedded-opentype'),
url('fonts/h.woff?-cd3sth') format('woff'),
url('fonts/h.ttf?-cd3sth') format('truetype'),
url('fonts/h.svg?-cd3sth#h') format('svg');
font-weight: normal;
font-style: normal;
}
@@ -23,6 +23,42 @@
-moz-osx-font-smoothing: grayscale;
}
.h-icon-move:before {
content: "\e61e";
}
.h-icon-quote:before {
content: "\e610";
}
.h-icon-numlist:before {
content: "\e611";
}
.h-icon-ulist:before {
content: "\e612";
}
.h-icon-code:before {
content: "\e613";
}
.h-icon-markdown:before {
content: "\e614";
}
.h-icon-minus:before {
content: "\e615";
}
.h-icon-users:before {
content: "\e616";
}
.h-icon-user:before {
content: "\e617";
}
.h-icon-earth:before {
content: "\e618";
}
.h-icon-link:before {
content: "\e619";
}
.h-icon-search:before {
content: "\e61a";
}
.h-icon-math:before {
content: "\e600";
}
@@ -71,39 +107,6 @@
.h-icon-highlighter:before {
content: "\e60f";
}
.h-icon-quote:before {
content: "\e610";
}
.h-icon-numlist:before {
content: "\e611";
}
.h-icon-ulist:before {
content: "\e612";
}
.h-icon-code:before {
content: "\e613";
}
.h-icon-markdown:before {
content: "\e614";
}
.h-icon-minus:before {
content: "\e615";
}
.h-icon-users:before {
content: "\e616";
}
.h-icon-user:before {
content: "\e617";
}
.h-icon-earth:before {
content: "\e618";
}
.h-icon-link:before {
content: "\e619";
}
.h-icon-search:before {
content: "\e61a";
}
.h-icon-bold:before {
content: "\e61b";
}
View
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="60px" height="45px" viewBox="0 0 60 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.1.1 (8761) - http://www.bohemiancoding.com/sketch -->
<title>Slice 2</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="Path-1-+-Triangle-1" sketch:type="MSLayerGroup" transform="translate(2.000000, -2.000000)">
<path d="M0.273888323,46.8543613 C0.792991324,12.7899443 27.9928896,9.26957714 53.1377225,6.24948249" id="Path-1" stroke="#979797" stroke-width="2" sketch:type="MSShapeGroup"></path>
<polygon id="Triangle-1" fill="#979797" sketch:type="MSShapeGroup" transform="translate(51.500000, 6.500000) rotate(82.000000) translate(-51.500000, -6.500000) " points="51.5 1 57 12 46 12 "></polygon>
</g>
</g>
</svg>
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Diff not rendered.
View
Diff not rendered.
View
Diff not rendered.
View
Diff not rendered.
View
Diff not rendered.
View
Diff not rendered.
View
@@ -28,6 +28,11 @@ class AccountController
$scope.$broadcast 'formState', form.$name, '' # Update status btn
$scope.tab = 'Account'
session.profile({user_id: $scope.persona}).$promise
.then (result) =>
$scope.subscriptions = result.subscriptions
# Data for each of the forms
$scope.editProfile = {}
$scope.changePassword = {}
@@ -66,6 +71,17 @@ class AccountController
promise = session.edit_profile(packet)
promise.$promise.then(successHandler, errorHandler)
$scope.updated = (index, form) ->
packet =
username: $scope.persona
subscriptions: JSON.stringify $scope.subscriptions[index]
successHandler = angular.bind(null, onSuccess, form)
errorHandler = angular.bind(null, onError, form)
promise = session.edit_profile(packet)
promise.$promise.then(successHandler, errorHandler)
angular.module('h.auth')
.controller('AccountController', AccountController)
View
@@ -111,6 +111,12 @@ configure = [
method: 'GET'
withCredentials: true
sessionProvider.actions.profile =
method: 'GET'
params:
__formid__: 'profile'
withCredentials: true
for action in AUTH_SESSION_ACTIONS
sessionProvider.actions[action] =
method: 'POST'
View
@@ -31,12 +31,14 @@ class AppController
'documentHelpers', 'drafts'
]
constructor: (
$location, $q, $route, $scope, $timeout
$location, $q, $route, $scope, $timeout,
annotator, flash, identity, socket, streamfilter,
documentHelpers, drafts
) ->
{plugins, host, providers} = annotator
isFirstRun = $location.search().hasOwnProperty('firstrun')
applyUpdates = (action, data) ->
"""Update the application with new data from the websocket."""
return unless data?.length
@@ -134,7 +136,6 @@ class AppController
, slots * 500
_sock.onmessage = (msg) ->
#console.log msg
unless msg.data.type? and msg.data.type is 'annotation-notification'
return
data = msg.data.payload
@@ -195,6 +196,8 @@ class AppController
$scope.persona = null
reset()
$scope.login() if isFirstRun
oncancel = ->
$scope.dialog.visible = false
View
@@ -187,11 +187,15 @@ AnnotationController = [
break
domain = extractURIComponent(uri, 'hostname')
documentTitle = if Array.isArray(model.document.title)
model.document.title[0]
else
model.document.title
@document =
uri: uri
domain: domain
title: model.document.title or domain
title: documentTitle or domain
if @document.title.length > 30
@document.title = @document.title[0..29] + '…'
View
@@ -7,6 +7,7 @@ class Annotator.Guest extends Annotator
events:
".annotator-adder button click": "onAdderClick"
".annotator-adder button mousedown": "onAdderMousedown"
".annotator-adder button mouseup": "onAdderMouseup"
"setTool": "onSetTool"
"setVisibleHighlights": "onSetVisibleHighlights"
@@ -26,6 +27,9 @@ class Annotator.Guest extends Annotator
tool: 'comment'
visibleHighlights: false
html: jQuery.extend {}, Annotator::html,
adder: '<div class="annotator-adder"><button class="h-icon-pen"></button></div>'
constructor: (element, options, config = {}) ->
options.noScan = true
super
@@ -57,7 +61,7 @@ class Annotator.Guest extends Annotator
origin: origin
scope: "#{scope}:provider"
onReady: =>
console.log "Guest functions are ready for #{origin}"
this.publish('panelReady')
setTimeout =>
event = document.createEvent "UIEvents"
event.initUIEvent "annotatorReady", false, false, window, 0
@@ -66,12 +70,12 @@ class Annotator.Guest extends Annotator
# Load plugins
for own name, opts of @options
if not @plugins[name]
if not @plugins[name] and Annotator.Plugin[name]
this.addPlugin(name, opts)
unless config.dontScan
# Scan the document text with the DOM Text libraries
this.scanDocument "Guest initialized"
this._scan()
# Watch for newly rendered highlights, and update positions in sidebar
this.subscribe "highlightsCreated", (highlights) =>
@@ -101,15 +105,13 @@ class Annotator.Guest extends Annotator
pages = Object.keys(hls).map (s) -> parseInt s
# Do we have any highlights left?
if pages.length
console.log "We still have something left"
firstPage = pages.sort()[0] # Determine the first page
firstHl = hls[firstPage] # Determine the first (topmost) hl
# Store the position of this anchor inside target
highlight.anchor.target.pos =
top: highlight.getTop()
heigth: highlight.getHeight()
else
console.log "No pos left"
delete highlight.anchor.target.pos
# Announce the new positions, so that the sidebar knows
@@ -169,13 +171,10 @@ class Annotator.Guest extends Annotator
.bind('getDocumentInfo', (trans) =>
(@plugins.PDF?.getMetaData() ? Promise.reject())
.then (md) =>
# console.log "Returning PDF metadata", md
trans.complete
uri: @getHref()
metadata: md
.catch (problem) =>
# console.log "Returning standard metadata, because"
# console.log problem.stack ? problem
trans.complete
uri: @getHref()
metadata: @plugins.Document?.metadata
@@ -197,14 +196,6 @@ class Annotator.Guest extends Annotator
@plugins.Heatmap._scheduleUpdate()
)
scanDocument: (reason = "something happened") =>
try
console.log "Analyzing host frame, because " + reason + "..."
this._scan()
catch e
console.log e.message
console.log e.stack
_setupWrapper: ->
@wrapper = @element
.on 'click', (event) =>
@@ -352,6 +343,10 @@ class Annotator.Guest extends Annotator
method: 'addToken'
params: token
onAdderMouseup: ->
event.preventDefault()
event.stopPropagation()
onAdderMousedown: ->
onAdderClick: (event) =>
View
@@ -20,35 +20,47 @@ class Annotator.Host extends Annotator.Guest
# XXX: Hack for missing window.location.origin in FF
hostOrigin ?= window.location.protocol + "//" + window.location.host
src = options.app
if options.firstRun
# Allow options.app to contain query string params.
src = src + (if '?' in src then '&' else '?') + 'firstrun'
app = $('<iframe></iframe>')
.attr('name', 'hyp_sidebar_frame')
.attr('seamless', '')
.attr('src', "#{options.app}#/?xdm=#{encodeURIComponent(hostOrigin)}")
.attr('src', src)
super element, options, dontScan: true
app.appendTo(@frame)
if options.firstRun
this.on 'panelReady', => this.actuallyShowFrame(transition: false)
if @plugins.Heatmap?
this._setupDragEvents()
@plugins.Heatmap.element.on 'click', (event) =>
if @frame.hasClass 'annotator-collapsed'
this.showFrame()
# Scan the document
this.scanDocument "Host initialized"
this._scan()
actuallyShowFrame: (options={transition: true}) ->
unless @drag.enabled
@frame.css 'margin-left': "#{-1 * @frame.width()}px"
if options.transition
@frame.removeClass 'annotator-no-transition'
else
@frame.addClass 'annotator-no-transition'
@frame.removeClass 'annotator-collapsed'
_setupXDM: (options) ->
channel = super
channel
.bind('showFrame', (ctx) =>
unless @drag.enabled
@frame.css 'margin-left': "#{-1 * @frame.width()}px"
@frame.removeClass 'annotator-no-transition'
@frame.removeClass 'annotator-collapsed'
)
.bind 'showFrame', (ctx) => this.actuallyShowFrame()
.bind('hideFrame', (ctx) =>
@frame.css 'margin-left': ''
View
@@ -111,8 +111,6 @@ class Annotator.Plugin.Bridge extends Annotator.Plugin
(options.origin.match /^resource:\/\//)
options.origin = '*'
console.log "Bridge plugin connecting to #{options.origin}"
# options.debugOutput = true
channel = Channel.build(options)
## Remote method call bindings
@@ -165,7 +163,6 @@ class Annotator.Plugin.Bridge extends Annotator.Plugin
while queue.length
parent = queue.shift()
if parent isnt window
console.log window.location.toString(), 'sending beacon...'
parent.postMessage '__annotator_dhcp_discovery', @options.origin
for child in parent.frames
queue.push child
@@ -183,10 +180,6 @@ class Annotator.Plugin.Bridge extends Annotator.Plugin
success: (result) -> d.resolve result
error: (error, reason) ->
if error isnt 'timeout_error'
console.log 'Error in call! Reason: ' + reason
console.log error
console.log "Call was:", options
console.log 'Destroying channel!'
d.reject error, reason
else
d.resolve null
View
@@ -1,7 +1,5 @@
class Annotator.Plugin.Discovery extends Annotator.Plugin
pluginInit: ->
console.log "Initializing discovery plugin."
svc = $('link')
.filter ->
this.rel is 'service' and this.type is 'application/annotatorsvc+json'
View
@@ -135,7 +135,6 @@ class Annotator.Plugin.Heatmap extends Annotator.Plugin
anchor = ann.anchors[0]
if not next? or start.page*dir < anchor.startPage*dir
# This one is obviously better
#console.log "Found anchor on better page."
start:
page: anchor.startPage
top: anchor.highlight[anchor.startPage]?.getTop()
@@ -146,27 +145,23 @@ class Annotator.Plugin.Heatmap extends Annotator.Plugin
if hl?
# We have a real highlight, let's compare coordinates
if start.top*dir < hl.getTop()*dir
#console.log "Found anchor on same page, better pos."
# OK, this one is better
start:
page: start.page
top: hl.getTop()
next: [anchor]
else
# No, let's keep the old one instead
#console.log "Found anchor on same page, worse pos. (Known: ", start.top, "; found: ", hl.getTop(), ")"
acc
else
# The page is not yet rendered, can't decide yet.
# Let's just store this one, too
#console.log "Found anchor on same page, unknown pos."
start: page: start.page
next: $.merge next, [anchor]
else
# No, we have clearly seen better alternatives
acc
, {}
#console.log "Next is", next
# Get an anchor from the page we want to go to
anchor = next[0]
View
@@ -92,7 +92,6 @@ class Hypothesis extends Annotator
origin: origin
scope: "#{scope}:provider"
onReady: =>
console.log "Provider functions are ready for #{origin}"
if source is $window.parent then @host = channel
entities = []
channel = this._setupXDM options
@@ -272,7 +271,6 @@ class Hypothesis extends Annotator
for p in @annotator.providers
for uri in p.entities
unless entities[uri]?
console.log "Loading annotations for: " + uri
entities[uri] = true
this.loadAnnotationsFromSearch (angular.extend {}, query, uri: uri)
@@ -308,17 +306,12 @@ class Hypothesis extends Annotator
switch @socialView.name
when "none"
# Sweet, nothing to do, just clean up previous filters
console.log "Not applying any Social View filters."
delete query.user
when "single-player"
if @plugins.Permissions?.user
console.log "Social View filter: single player mode."
query.user = @plugins.Permissions.user
else
console.log "Social View: single-player mode, but ignoring it, since not logged in."
delete query.user
else
console.warn "Unsupported Social View: '" + @socialView.name + "'!"
setTool: (name) ->
return if name is @tool
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View
@@ -13,7 +13,6 @@ body {
@extend .noise;
font-family: $sans-font-family;
font-weight: 300;
word-wrap: break-word;
}
#{nest("hgroup", "#{headings()}")} {
@@ -27,11 +26,6 @@ ol {
padding-left: 3em;
}
.bookmarklet {
padding: .12em;
border: 1px dashed $gray;
}
#wrapper {
padding: .72em;
text-align: center;
@@ -69,7 +63,6 @@ ol {
@include respond-to(wide-handhelds tablets desktops) {
@include pie-clearfix;
text-align: left;
img { float: left; }
}
margin-bottom: 1em;
@@ -165,4 +158,29 @@ ol {
font-size: 1.231em;
margin: 0;
}
img {
float: left;
}
}
.masthead-small {
img {
width: 48px;
height: auto;
padding-top: 1px;
}
hgroup {
margin-left: 54px;
}
.masthead-heading {
font-size: 20px;
line-height: 1em;
}
.masthead-subheading {
font-size: 14px;
}
}
View
@@ -5,6 +5,7 @@ $black: #000 !default;
// GRAYS
$gray: #777 !default;
$gray-darker: #333;
$gray-dark: #585858;
$gray-light: #969696 !default;
$gray-lighter: #d3d3d3 !default;
View
@@ -17,6 +17,7 @@ $headings-color: $text-color;
@import 'simple-search';
@import 'tags-input';
@import 'page';
@import 'help-page';
//ELEMENT STYLES////////////////////////////////
a {
@@ -26,6 +27,7 @@ a {
}
body {
position: relative;
background-color: $body-background;
color: $text-color;
line-height: 1.4;
View
@@ -0,0 +1,162 @@
@import "./mixins/icons";
.help-page {
padding-top: 2.5em;
padding-bottom: 2.5em;
background: white;
@include breakpoint(920px) {
padding-right: 460px;
}
.masthead {
margin-bottom: 2.5em;
}
}
.help-page-content {
margin: auto;
padding: 0 20px;
min-width: 480px;
@include breakpoint(1160px) {
padding: 0 5% 0 10%;
}
@include breakpoint(1400px) {
padding: 0 10% 0 20%;
}
}
.help-page-heading {
color: $gray-darker;
margin-bottom: 1em;
font-size: 1.5em;
}
.help-page-heading,
.help-page-lede {
text-align: center;
}
.help-page-lede {
font-style: italic;
margin-bottom: 2.5em;
}
.help-page-section {
padding: 2.5em;
border-top: 1px solid #EAEAEA;
&:first-child {
border-top: none;
padding-top: 0;
}
}
.help-page-sidebar {
position: fixed;
top: 20px;
right: 2.5em;
bottom: 20px;
width: 380px;
display: block;
@include breakpoint(920px) {
border: $gray-lighter dotted 2px;
border-radius: 3px;
}
}
@mixin help-icon {
border: 1px solid rgba(0, 0, 0, 0.2);
padding: 0.4em;
border-radius: 0.4em;
background: #FFF;
text-shadow: 0 0 2px #F9F9F9, 0 0 0 #777;
color: rgba(200, 200, 200, 0.3);
font-size: 10px;
}
#help-1 {
position: fixed;
top: 60px;
right: 60px;
width: 210px;
color: $gray-light;
text-align: right;
@include icons {
@include help-icon;
font-size: 14px;
}
}
#help-2 {
background: url(../images/help-arrow.svg) 0 0 no-repeat;
width: 60px;
height: 45px;
position: absolute;
top: -10px;
right: -10px;
}
.numbered-list {
counter-reset: numbered-list;
}
.numbered-list-item {
position: relative;
counter-increment: numbered-list;
padding-left: 3em;
padding-right: 1.25em;
margin-bottom: 2.5em;
list-style-type: none;
&:before {
content: counter(numbered-list);
display: block;
position: absolute;
top: .125em;
left: 0;
width: 2.125em;
height: 1.8125em;
border: .125em solid $hypothered;
border-radius: 50%;
text-align: center;
padding-top: .3125em; // 24px == Line height of text.
}
}
.feature {
margin-bottom: 1.5em;
}
.feature-heading {
color: $gray-darker;
font-size: 1.125em;
margin-bottom: .555em;
}
.feature-icon {
font-size: .875em;
margin-right: .3em;
}
.help-icon {
@include help-icon;
}
.bookmarklet {
white-space: nowrap;
color: $gray-darker;
padding: 2px 4px;
border: 1px solid;
border-radius: 2px;
font-size: 13px;
cursor: move;
@include icons {
font-size: 12px;
}
}
View
@@ -39,26 +39,30 @@ $base-font-size: 14px;
width: 6px;
}
button, button:first-child {
button {
@include sweetbutton;
background-image: url("../images/pen_1.png") !important;
background-size: 65%;
background-position: center;
background-repeat: no-repeat;
font-family: h;
border: none;
cursor: pointer;
height: 100%;
text-indent: -999em;
width: 100%;
font-size: 18px;
margin: 0;
padding: 0;
text-align: center;
background: white !important;
color: $border !important;
&::-moz-focus-inner {
border: 0;
}
&:hover {
color: $hoverborder !important;
}
}
&:hover {
@include box-shadow(1px 1px 6px -2px $gray-light);
border-color: $hoverborder;
&:before {
View
@@ -34,22 +34,28 @@ $break-desktop: 1024px !default;
}
}
@mixin breakpoint($min) {
@media only screen and (min-width: $min) {
@content;
}
}
// Mobile first media queries. Encorages development to work with mobile and
// modify as the viewport grows rather than designing for individual bands.
@mixin wide-handheld-and-up {
@media only screen and (min-width: $break-wide-handheld + 1) {
@include breakpoint($break-wide-handheld + 1) {
@content;
}
}
@mixin tablet-and-up {
@media only screen and (min-width: $break-tablet + 1) {
@include breakpoint($break-tablet + 1) {
@content;
}
}
@mixin desktop-and-up {
@media only screen and (min-width: $break-desktop + 1) {
@include breakpoint($break-desktop + 1) {
@content;
}
}
View
@@ -16,7 +16,7 @@
h2 {
font-size: 1.618em;
margin: .7606em 0 .3803em;
margin: .7606em 0;
}
h3 {
Oops, something went wrong.