Commit aedc0922 authored by Benjamin Rokseth's avatar Benjamin Rokseth
Browse files

Merge branch 'release/0.3.0'

parents 52f5b6e4 276395a0
......@@ -12,6 +12,44 @@ a secrets.env:
The resulting compose file can then be used with `docker-compose up -d` to provision.
# Releases
## 0.3.0 (2016-11-09)
KOHA: 90437fe3ee4e3d5af59182d7f3be477940b484f4
- patron-client:
- add validator to firstname, add IE11 hack for firstname and lastname
- Add proper headers for no caching to appropriate get requests
- Use homebranch instead of holdingbranch
- attempt at fixing closing filters when scrolling on mobile safari
- Fix sorting on work page and related tests
- Remove proptype from metaitem
- Show titles in preferred languages in the search results
- catalinker:
- manglende_opplysninger_på_autoriteter' into develop
- fix showing and patching of authorities connected to other authorities DEICH-425
- number of pages as multivalue. Main title single value.
- copy nonEditable when set to first input of blank node input group DEICH-434
- use correct subject type when extracting fields ti show from search result DEICH-432
- add pluralised form of dialog message DEICH-389
- check for existing ISBN/EAN before fetching external data DEICH-389
-force format ISBN numbers
- services:
- add endpoint to find projections of resources by simple query
- koha:
- add framework query param to biblio CRUD
- Add bug 17561 reserveslip fix
- add patch to use framework in batch mod items
- Show 'Preg brikker' button next to RFID status button
- sync homebranch, not holdingbranch
- Add csv headers to runreports cronjob
- Add maxdays to longoverdue cronjob
- Add apacheconf overrides
- stability fixes on patron search/edit
- default search fields
- Add codedLocationQualifier to labelgenerator response
- show items.location in holds queue
- upstream changes:
https://github.com/Koha-Community/Koha/compare/239b81c1dd0cfa2e5e1f720ba9b8554536a9bc44...674d3875c81af122eb7f925845fe73cc4d817db4
## 0.2.0 (2016-11-02) First post-release!
......
......@@ -48,6 +48,7 @@ services:
PIDGEON_PASS: "${PIDGEON_PASS}"
SIP_AUTOPASS: "${SIP_AUTOPASS}"
APACHE_MINSERVERS: "${APACHE_MINSERVERS}"
APACHE_TIMEOUT: "${APACHE_TIMEOUT}"
APACHE_SERVER_STATUS_NET: "${APACHE_SERVER_STATUS_NET}"
APACHE_REMOTE_INTERNAL_PROXY: "${APACHE_REMOTE_INTERNAL_PROXY}"
ports:
......
......@@ -51,6 +51,7 @@ services:
PIDGEON_PASS: "${PIDGEON_PASS}"
SIP_AUTOPASS: "${SIP_AUTOPASS}"
APACHE_MINSERVERS: "${APACHE_MINSERVERS}"
APACHE_TIMEOUT: "${APACHE_TIMEOUT}"
APACHE_SERVER_STATUS_NET: "${APACHE_SERVER_STATUS_NET}"
APACHE_REMOTE_INTERNAL_PROXY: "${APACHE_REMOTE_INTERNAL_PROXY}"
ports:
......
......@@ -51,6 +51,7 @@ services:
SIP_AUTOPASS: "${SIP_AUTOPASS}"
SIP_WORKERS: 125
APACHE_MINSERVERS: "${APACHE_MINSERVERS}"
APACHE_TIMEOUT: "${APACHE_TIMEOUT}"
APACHE_SERVER_STATUS_NET: "${APACHE_SERVER_STATUS_NET}"
APACHE_REMOTE_INTERNAL_PROXY: "${APACHE_REMOTE_INTERNAL_PROXY}"
ports:
......
......@@ -2,7 +2,7 @@ export SIP_PORT=6001
export SIP_PROXY_PORT=6002
export SIP_AUTOPASS=secret
export SIP_AUTOPASS_ENCRYPTED=encryptme
export KOHA_IMAGE_TAG=30986a49afce194ca1c3da1965ce96448946a844
export KOHA_IMAGE_TAG=90437fe3ee4e3d5af59182d7f3be477940b484f4
export KOHA_ADMINUSER=admin
export KOHA_ADMINPASS=secret
export KOHA_INSTANCE=name
......@@ -37,5 +37,6 @@ export PIDGEON_URL=pidgeonurl
export PIDGEON_USER=pidgeonuser
export PIDGEON_PASS=pidgeonpass
export APACHE_MINSERVERS=5
export APACHE_TIMEOUT=300
export APACHE_SERVER_STATUS_NET=127.0.0.1/24
export APACHE_REMOTE_INTERNAL_PROXY=172.19.0.0/16
\ No newline at end of file
-- MySQL dump 10.13 Distrib 5.5.32, for debian-linux-gnu (x86_64)
--
-- Script to load data from csv into table `authorised_values`
-- Script to load data from csv into tables `authorised_values` and `authorised_value_categories`
--
LOCK TABLES `authorised_value_categories` WRITE;
/*!40000 ALTER TABLE `authorised_value_categories` DISABLE KEYS */;
INSERT IGNORE INTO authorised_value_categories (category_name)
VALUES ("WITHDRAWN"), ("LOST"), ("NOT_LOAN"), ("RESTRICTED"), ("DAMAGED"), ("DOORACCESS");
/*!40000 ALTER TABLE `authorised_value_categories` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `authorised_values` WRITE;
DELETE FROM authorised_values WHERE category IN ("WITHDRAWN", "LOST", "NOT_LOAN", "RESTRICTED", "DAMAGED", "DOORACCESS");
/*!40000 ALTER TABLE `authorised_values` DISABLE KEYS */;
......
......@@ -22,6 +22,16 @@ $mintgreen: #DCFC9F;
outline-offset: 2px;
}
@mixin pencil {
display: inline-block;
background-image: url('/images/lower_right_pencil.svg');
background-repeat: no-repeat;
background-position: left top;
padding-left: 2em;
cursor: pointer;
height: 20px
}
.label-font {
font-weight: bold;
}
......@@ -500,7 +510,7 @@ body {
width: 100%;
}
.add-new-btn-row {
padding: 19px 13px 8px 8px;
padding: 0 13px 23px 8px;
a:not(.delete-link), button, span {
float: right;
width: auto;
......@@ -610,13 +620,7 @@ body {
padding: 0.5em;
min-height: 65px;
.edit-resource {
display: inline-block;
background-image: url('/images/lower_right_pencil.svg');
background-repeat: no-repeat;
background-position: left top;
padding-left: 2em;
cursor: pointer;
height: 20px
@include pencil()
}
.select-result-item {
display: inline-block;
......@@ -1008,4 +1012,16 @@ body {
display: none;
z-index: 2000;
}
#alert-existing-resource-dialog {
.search-result {
max-height: 70px;
overflow: scroll;
.item {
.edit-resource {
@include pencil();
margin-right: -12px;
}
}
}
}
}
\ No newline at end of file
......@@ -13,12 +13,13 @@
var $ = require('jquery')
var ldGraph = require('ld-graph')
var URI = require('urijs')
module.exports = factory(Ractive, axios, Graph, Ontology, StringUtil, _, $, ldGraph, URI)
require('isbn2')
module.exports = factory(Ractive, axios, Graph, Ontology, StringUtil, _, $, ldGraph, URI, window.ISBN)
} else {
// Browser globals (root is window)
root.Main = factory(root.Ractive, root.axios, root.Graph, root.Ontology, root.StringUtil, root._, root.$, root.ldGraph, root.URI)
root.Main = factory(root.Ractive, root.axios, root.Graph, root.Ontology, root.StringUtil, root._, root.$, root.ldGraph, root.URI, root.ISBN)
}
}(this, function (Ractive, axios, Graph, Ontology, StringUtil, _, $, ldGraph, URI, dialog) {
}(this, function (Ractive, axios, Graph, Ontology, StringUtil, _, $, ldGraph, URI, dialog, ISBN) {
'use strict'
Ractive.DEBUG = false
......@@ -29,8 +30,10 @@
require('jquery-ui/dialog')
require('jquery-ui/accordion')
let etagData = {}
require('isbn2')
ISBN = ISBN || window.ISBN
var deepClone = function (object) {
var deepClone = function (object) {
var clone = _.clone(object)
_.each(clone, function (value, key) {
......@@ -271,6 +274,42 @@
})
}
var alertAboutExistingResource = function (spec, existingResources, proceed) {
ractive.set('existingResourcesDialog.existingResources', existingResources)
ractive.set('existingResourcesDialog.legend', existingResources.length > 1 ? spec.legendPlural.replace('${numberOfResources}', existingResources.length) : spec.legendSingular)
ractive.set('existingResourcesDialog.editResourceConfig', spec.editWithTemplate)
$('#alert-existing-resource-dialog').dialog({
resizable: false,
modal: true,
width: 550,
title: 'Denne utgivelsen finnes fra før',
class: 'existing-resources-dialog',
buttons: [
{
text: 'Fortsett',
click: function () {
$(this).dialog('close')
proceed()
}
},
{
text: 'Avbryt',
class: 'default',
click: function () {
$(this).dialog('close')
Main.restart()
}
}
],
open: function () {
$(this).siblings('.ui-dialog-buttonpane').find('button.default').focus()
}
})
ractive.set('existingResourcesDialog.dialogCloser', function () {
$('#alert-existing-resource-dialog').dialog('close')
})
}
function i18nLabelValue (label) {
if (Array.isArray(label)) {
return _.find(label, function (labelValue) {
......@@ -437,7 +476,7 @@
forAllGroupInputs(function (input1) {
if (input1.fragment === property && !_.contains(includeWhenValues, _.flatten([
fragmentPartOf(URI.parseQuery(document.location.href)[ property ] ||
_.flatten([input1.values[ 0 ].current.value])[0])
_.flatten([ input1.values[ 0 ].current.value ])[ 0 ])
])[ 0 ])) {
shouldInclude = false
return true
......@@ -738,11 +777,11 @@
var mainEntryInput = (input.parentInput && input.parentInput.isMainEntry === true) || (input.targetResourceIsMainEntry === true) || false
var mainEntryNode = (root.isA('MainEntry') === true) || ((options.wrapperObject && options.wrapperObject.isA('MainEntry') === true) || false)
if (options.overrideMainEntry || mainEntryInput === mainEntryNode) {
if (_.contains([ 'select-authorized-value', 'entity', 'searchable-authority-dropdown' ], input.type)) {
if (_.contains([ 'select-authorized-value', 'entity' ], input.type)) {
var index = 0
let values = setMultiValues(root.outAll(fragmentPartOf(predicate)), input, rootIndex)
promises = promises.concat(loadLabelsForAuthorizedValues(values, input, 0, root))
} else if (input.type === 'searchable-with-result-in-side-panel') {
} else if (input.type === 'searchable-with-result-in-side-panel' || input.type === 'searchable-authority-dropdown') {
if (!(input.suggestValueFrom && options.onlyValueSuggestions)) {
_.each(root.outAll(fragmentPartOf(predicate)), function (node, multiValueIndex) {
index = (input.isSubInput ? rootIndex : multiValueIndex) + (offset)
......@@ -823,6 +862,7 @@
ractive.set(`${input.keypath}.values.${valueIndex}.nonEditable`, true)
input.parentInput.allowAddNewButton = true
}
ractive.set(`${input.keypath}.allowAddNewButton`, true)
} else {
input.suggestedValues = input.suggestedValues || []
input.suggestedValues.push({
......@@ -1368,6 +1408,9 @@
if (input.reportFormat) {
ontologyInput.reportFormat = input.reportFormat
}
if (input.formatter) {
ontologyInput.formatter = input.formatter
}
}
copyResourceForms(input)
})
......@@ -1891,6 +1934,7 @@
'delete-work-dialog',
'delete-resource-dialog',
'confirm-enable-special-input-dialog',
'alert-existing-resource-dialog',
'accordion-header-for-collection',
'readonly-input',
'readonly-input-string',
......@@ -2127,8 +2171,9 @@
var keypath = inputValue.keypath
ractive.set(keypath + '.current.value', $(e.target).val())
var inputNode = ractive.get(grandParentOf(keypath))
if (!inputNode.isSubInput && keypath.indexOf('enableCreateNewResource') === -1) {
Main.patchResourceFromValue(ractive.get('targetUri.' + unPrefix(inputNode.domain)), inputNode.predicate,
let target = ractive.get('targetUri.' + unPrefix(inputNode.domain))
if (target && !inputNode.isSubInput && (keypath.indexOf('enableCreateNewResource') === -1 || keypath.indexOf('enableEditResource') === -1)) {
Main.patchResourceFromValue(target, inputNode.predicate,
ractive.get(keypath), inputNode.datatypes[ 0 ], errors, keypath)
}
ractive.update()
......@@ -2204,6 +2249,27 @@
teardown: function () {}
}
}
var formatter = function (node, formatter) {
if (formatter === 'isbn') {
$(node).on('input', function () {
let value = $(this).val()
let parsedIsbn = ISBN.parse(value)
if (parsedIsbn) {
if (parsedIsbn.isIsbn10()) {
$(this).val(parsedIsbn.asIsbn10(true))
} else {
if (parsedIsbn.isIsbn13()) {
$(this).val(parsedIsbn.asIsbn13(true))
}
}
ractive.updateModel()
}
})
}
return {
teardown: function () {}
}
}
titleRactive = new Ractive({
el: 'title',
template: '{{title.1 || title.2 || title.3 || "Katalogisering"}}',
......@@ -2397,7 +2463,8 @@
timePicker: timePicker,
slideDown: slideDown,
pasteSanitizer: pasteSanitizer,
searchable: searchable
searchable: searchable,
formatter: formatter
},
partials: applicationData.partials,
transitions: {
......@@ -2560,6 +2627,27 @@
loadWorksAsSubject(origin)
}
},
editResource: function (event, editWith, uri, preAction) {
if (preAction) {
preAction()
}
let initOptions = { presetValues: {}, task: editWith.descriptionKey }
updateBrowserLocationWithTemplate(editWith.template)
updateBrowserLocationWithUri(typeFromUri(uri), uri)
forAllGroupInputs(function (input) {
if (input.type === 'hidden-url-query-value' &&
typeof input.values[ 0 ].current.value === 'string' &&
input.values[ 0 ].current.value !== '') {
let shortValue = input.values[ 0 ].current.value.replace(input.widgetOptions.prefix, '')
initOptions.presetValues[ input.widgetOptions.queryParameter ] = shortValue
updateBrowserLocationWithQueryParameter(input.widgetOptions.queryParameter, shortValue)
}
})
if (uri.indexOf('publication') !== -1) {
updateBrowserLocationWithTab(1)
}
Main.init(initOptions)
},
selectSearchableItem: function (event, origin, displayValue, options) {
if (!eventShouldBeIgnored(event)) {
options = options || {}
......@@ -2567,24 +2655,9 @@
var inputKeyPath = grandParentOf(origin)
var input = ractive.get(inputKeyPath)
var uri = event.context.uri
var editWith = ractive.get(inputKeyPath + '.widgetOptions.editWithTemplate')
if (editWith) {
let initOptions = { presetValues: {}, task: editWith.descriptionKey }
updateBrowserLocationWithTemplate(editWith.template)
updateBrowserLocationWithUri(typeFromUri(uri), uri)
forAllGroupInputs(function (input) {
if (input.type === 'hidden-url-query-value' &&
typeof input.values[ 0 ].current.value === 'string' &&
input.values[ 0 ].current.value !== '') {
let shortValue = input.values[ 0 ].current.value.replace(input.widgetOptions.prefix, '')
initOptions.presetValues[ input.widgetOptions.queryParameter ] = shortValue
updateBrowserLocationWithQueryParameter(input.widgetOptions.queryParameter, shortValue)
}
})
if (uri.indexOf('publication') !== -1) {
updateBrowserLocationWithTab(1)
}
Main.init(initOptions)
var editWithTemplateSpec = ractive.get(inputKeyPath + '.widgetOptions.editWithTemplate')
if (editWithTemplateSpec) {
ractive.fire('editResource', event, editWithTemplateSpec, uri)
} else if (ractive.get(inputKeyPath + '.widgetOptions.enableInPlaceEditing')) {
var indexType = ractive.get(inputKeyPath + '.indexTypes.0')
var rdfType = ractive.get(inputKeyPath + '.widgetOptions.enableEditResource.forms.' + indexType).rdfType
......@@ -2824,12 +2897,9 @@
ractive.set(grandParentOf(event.keypath) + '.showInputs', null)
},
fetchValueSuggestions: function (event) {
var searchValue = event.context.current.value.replace(/[ -]/g, '')
if (searchValue && searchValue !== '') {
var searchExternalSourceInput = ractive.get(grandParentOf(event.keypath))
if (searchExternalSourceInput.searchForValueSuggestions.pattern && !(new RegExp(searchExternalSourceInput.searchForValueSuggestions.pattern).test(searchValue))) {
return
}
var searchValue = event.context.current.value
function doExternalSearch () {
searchExternalSourceInput.searchForValueSuggestions.hitsFromPreferredSource = []
searchExternalSourceInput.searchForValueSuggestions.valuesFromPreferredSource = []
_.each(allInputs(), function (input) {
......@@ -2903,6 +2973,39 @@
}).then(unblockUI)
})
}
if (searchValue && searchValue !== '') {
var searchExternalSourceInput = ractive.get(grandParentOf(event.keypath))
if (searchExternalSourceInput.searchForValueSuggestions.pattern && !(new RegExp(searchExternalSourceInput.searchForValueSuggestions.pattern).test(searchValue))) {
return
}
if (searchExternalSourceInput.searchForValueSuggestions.checkExistingResource) {
ractive.fire('checkExistingResource', event.context.current.value, searchExternalSourceInput.searchForValueSuggestions.checkExistingResource, doExternalSearch)
}
}
},
checkExistingResource: function (queryValue, spec, proceed) {
var searchUrl = proxyToServices(`${spec.url}?${spec.queryParameter}=${queryValue}${_.reduce(spec.showDetails, function (memo, fieldName) {
return memo + '&@return=' + fieldName
}, '')}`)
axios.get(searchUrl, { headers: { 'x-apicache-bypass': true } }).then(function (response) {
let parsed = ldGraph.parse(response.data)
let existingResources = parsed.byType(spec.type)
if (existingResources.length > 0) {
alertAboutExistingResource(spec, _.map(existingResources, function (resource) {
var detailsForResource = []
_.each(spec.showDetails, function (detail) {
let detailValue = resource.getAll(detail)[ 0 ]
if (detailValue) {
detailsForResource.push(detailValue.value)
}
})
return { uri: resource.id, details: detailsForResource.join(' ') }
}), proceed)
} else {
proceed()
}
})
},
acceptSuggestedPredefinedValue: function (event, value) {
var input = ractive.get(grandParentOf(event.keypath))
......@@ -3507,7 +3610,8 @@
saveSuggestionData: saveSuggestionData,
checkRangeStart: checkRangeStart,
checkRangeEnd: checkRangeEnd,
positionSupportPanels: positionSupportPanels
positionSupportPanels: positionSupportPanels,
ISBN: ISBN
}
return Main
}
......
......@@ -79,6 +79,9 @@
delPatches = [];
var currentValue = oldAndCurrentValue.current.value;
if (currentValue === null) {
currentValue = ''
}
var oldValue = oldAndCurrentValue.old.value;
if (_.isArray(oldValue) && !_.isArray(currentValue)) {
currentValue = [ currentValue ];
......
......@@ -44,6 +44,7 @@
"express-compile-sass": "^3.0.4",
"express-http-proxy": "^0.7.2",
"express-livereload": "0.0.24",
"isbn2": "^0.1.8",
"jade": "~1.11.0",
"jquery": "^2.1.4",
"jquery-ui": "1.10.5",
......
<div id="alert-existing-resource-dialog">
<p>{{existingResourcesDialog.legend}}</p>
{{#if existingResourcesDialog.existingResources}}
<div class="search-result">
{{#each existingResourcesDialog.existingResources}}
<div class="item">
<a class="edit-resource" on-click="editResource:{{existingResourcesDialog.editResourceConfig}},{{uri}}, {{existingResourcesDialog.dialogCloser}}" title="Rediger"/>
<span class="details">
{{details}}
</span>
</div>
{{/each}}
</div>
{{/if}}
</div>
......@@ -5,5 +5,6 @@
class="pure-u-1-1 {{#error}}invalid-input{{else}}valid{{/error}}"
on-blur='patchResource:{{predicate}},{{rdfType}}'
data-automation-id="{{../../dataAutomationId}}"
decorator="formatter:{{../../formatter}}"
value="{{./current.value}}" />
{{/if}}
......@@ -4,9 +4,9 @@
{{else}}
<select style="width: 100%;"
decorator="multi:{select2:['authoritySelectSingle'], detectChange:[]}" id="sel_{{uniqueId}}"
class="searchable-authority" multiple>
{{#if current.value[0]}}
<option value='{{current.value[0]}}' selected="selected">{{current.displayName}}</option>
class="searchable-authority" multiple value="{{current.value}}">
{{#if ./current.value}}
<option value='{{current.value}}' selected="selected">{{current.displayValue}}</option>
{{/if}}
</select>
{{/if}}
......
......@@ -4,6 +4,7 @@ class="pure-u-1-1 {{#error}}invalid-input{{else}}valid{{/error}} simple-searchab
on-enter='fetchValueSuggestions'
data-automation-id="{{dataAutomationId}}"
value="{{current.value}}"
decorator="formatter:{{searchForValueSuggestions.formatter}}"
{{#if searchForValueSuggestions.hitsFromPreferredSource}} disabled=disabled{{/if}}/>
<span class="legend">
{{#if current.value && !../../searchForValueSuggestions.hitsFromPreferredSource.length > 0 }}
......
......@@ -115,4 +115,5 @@
{{>delete-publication-dialog}}
{{>delete-work-dialog}}
{{>confirm-enable-special-input-dialog}}
{{>alert-existing-resource-dialog}}
</span>
......@@ -355,6 +355,7 @@ module.exports = (app) => {
searchForValueSuggestions: {
label: 'ISBN',
pattern: '^[ 0-9\-]+[xX]?\s*$',
formatter: 'isbn',
patternMismatchMessage: 'Dette ser ikke ut som et gyldig ISBN-nummer',
parameterName: 'isbn',
automationId: 'searchValueSuggestions',
......@@ -363,6 +364,18 @@ module.exports = (app) => {
preferredSource: {
id: 'bibbi',
name: 'Biblioteksentralen'
},
checkExistingResource: {
url: 'services/publication',
queryParameter: 'isbn',
showDetails: [ 'mainTitle', 'subtitle', 'partNumber', 'partTitle', 'publicationYear'],
type: "Publication",
legendSingular: 'Det finnes allerede en registrert utgivelse med samme ISBN-nummer. Vil du åpne den, fortsette med nyregistrering likevel, eller avbryte registreringen?',
legendPlural: 'Det finnes allerede ${numberOfResources} registrerte utgivelser med samme ISBN-nummer. Vil du åpne en av disse, fortsette med nyregistrering likevel, eller avbryte registreringen?',
editWithTemplate: {
template: "workflow",
descriptionKey: 'maintPub'
}
}
}
}
......@@ -384,6 +397,18 @@ module.exports = (app) => {
preferredSource: {
id: 'bibbi',
name: 'Biblioteksentralen'
},
checkExistingResource: {
url: 'services/publication',
queryParameter: 'hasEan',
showDetails: [ 'mainTitle', 'subtitle', 'partNumber', 'partTitle', 'publicationYear'],
type: "Publication",
legendSingular: 'Det finnes allerede en registrert utgivelse med samme EAN-nummer. Vil du åpne den, fortsette med nyregistrering likevel, eller avbryte registreringen?',
legendPlural: 'Det finnes allerede ${numberOfResources} registrerte utgivelser med samme EAN-nummer. Vil du åpne en av disse, fortsette med nyregistrering likevel, eller avbryte registreringen?',
editWithTemplate: {
template: "workflow",
descriptionKey: 'maintPub'
}
}
}
}
......@@ -552,13 +577,16 @@ module.exports = (app) => {
},