View | Details | Raw Unified | Return to bug 11559
Collapse All | Expand All

(-)a/cataloguing/addbiblio.pl (+6 lines)
Lines 767-774 if ($frameworkcode eq 'FA'){ Link Here
767
        'stickyduedate'      => $fa_stickyduedate,
767
        'stickyduedate'      => $fa_stickyduedate,
768
        'duedatespec'        => $fa_duedatespec,
768
        'duedatespec'        => $fa_duedatespec,
769
    );
769
    );
770
} elsif ( $input->cookie( 'catalogue_editor_' . $loggedinuser ) eq 'advanced' ) {
771
    # Only use the advanced editor for non-fast-cataloging.
772
    # breedingid is not handled because those would only come off a Z39.50
773
    # search initiated by the basic editor.
774
    print $input->redirect( '/cgi-bin/koha/cataloguing/editor.pl' . ( $biblionumber && ( '#catalog:' . $biblionumber ) ) );
770
}
775
}
771
776
777
772
# Getting the list of all frameworks
778
# Getting the list of all frameworks
773
# get framework list
779
# get framework list
774
my $frameworks = getframeworks;
780
my $frameworks = getframeworks;
(-)a/cataloguing/editor.pl (+70 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
#
3
# Copyright 2013 ByWater
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 2 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
#
20
21
use Modern::Perl '2009';
22
23
use CGI;
24
use MARC::Record;
25
26
use C4::Auth;
27
use C4::Biblio;
28
use C4::Context;
29
use C4::Output;
30
use C4::XSLT qw( XSLTGetFilename );
31
32
my $input = CGI->new;
33
34
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
35
    {
36
        template_name   => 'cataloguing/editor.tt',
37
        query           => $input,
38
        type            => 'intranet',
39
        authnotrequired => 0,
40
        flagsrequired   => { editcatalogue => 'edit_catalogue' },
41
    }
42
);
43
44
# Needed information for cataloging plugins
45
$template->{VARS}->{DefaultLanguageField008} = pack( 'A3', C4::Context->preference('DefaultLanguageField008') || 'eng' );
46
47
# Z39.50 servers
48
my $dbh = C4::Context->dbh;
49
$template->{VARS}->{z3950_targets} = $dbh->selectall_arrayref( q{
50
    SELECT * FROM z3950servers
51
    ORDER BY name
52
}, { Slice => {} } );
53
54
my @xsltResultStylesheets;
55
my @xsltDetailStylesheets;
56
57
foreach my $syntax ( qw( MARC21 UNIMARC NORMARC ) ) {
58
    if ( XSLTGetFilename( $syntax, 'XSLTResultsDisplay' ) =~ m,/intranet-tmpl/.*|^https:?.*, ) {
59
        push @xsltResultStylesheets, { syntax => $syntax, url => $& };
60
    }
61
62
    if ( XSLTGetFilename( $syntax, 'XSLTDetailsDisplay' ) =~ m,/intranet-tmpl/.*|^https:?.*, ) {
63
        push @xsltDetailStylesheets, { syntax => $syntax, url => $& };
64
    }
65
}
66
67
$template->{VARS}->{xslt_result_stylesheets} = \@xsltResultStylesheets;
68
$template->{VARS}->{xslt_detail_stylesheets} = \@xsltDetailStylesheets;
69
70
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/koha-backend.js (+201 lines)
Line 0 Link Here
1
define( [ '/cgi-bin/koha/svc/cataloguing/framework?frameworkcode=&callback=define', 'marc-record' ], function( defaultFramework, MARC ) {
2
    var _authorised_values = defaultFramework.authorised_values;
3
    var _frameworks = {};
4
    var _framework_mappings = {};
5
6
    function _fromXMLStruct( data ) {
7
        result = {};
8
9
        $(data).children().eq(0).children().each( function() {
10
            var $contents = $(this).contents();
11
            if ( $contents.length == 1 && $contents[0].nodeType == Node.TEXT_NODE ) {
12
                result[ this.localName ] = $contents[0].data;
13
            } else {
14
                result[ this.localName ] = $contents.filter( function() { return this.nodeType != Node.TEXT_NODE || !this.data.match( /^\s+$/ ) } ).toArray();
15
            }
16
        } );
17
18
        return result;
19
    }
20
21
    function _importFramework( frameworkcode, frameworkinfo ) {
22
        _frameworks[frameworkcode] = frameworkinfo;
23
        _framework_mappings[frameworkcode] = {};
24
25
        $.each( frameworkinfo, function( i, tag ) {
26
            var tagnum = tag[0], taginfo = tag[1];
27
28
            var subfields = {};
29
30
            $.each( taginfo.subfields, function( i, subfield ) {
31
                subfields[ subfield[0] ] = subfield[1];
32
            } );
33
34
            _framework_mappings[frameworkcode][tagnum] = $.extend( {}, taginfo, { subfields: subfields } );
35
        } );
36
    }
37
38
    _importFramework( '', defaultFramework.framework );
39
40
    var KohaBackend = {
41
        NOT_EMPTY: {}, // Sentinel value
42
43
        GetAllTagsInfo: function( frameworkcode, tagnumber ) {
44
            return _framework_mappings[frameworkcode];
45
        },
46
47
        GetAuthorisedValues: function( category ) {
48
            return _authorised_values[category];
49
        },
50
51
        GetTagInfo: function( frameworkcode, tagnumber ) {
52
            if ( !_framework_mappings[frameworkcode] ) return undefined;
53
            return _framework_mappings[frameworkcode][tagnumber];
54
        },
55
56
        GetRecord: function( id, callback ) {
57
            $.get(
58
                '/cgi-bin/koha/svc/bib/' + id
59
            ).done( function( data ) {
60
                var record = new MARC.Record();
61
                record.loadMARCXML(data);
62
                callback(record);
63
            } ).fail( function( data ) {
64
                callback( { data: { error: data } } );
65
            } );
66
        },
67
68
        CreateRecord: function( record, callback ) {
69
            $.ajax( {
70
                type: 'POST',
71
                url: '/cgi-bin/koha/svc/new_bib',
72
                data: record.toXML(),
73
                contentType: 'text/xml'
74
            } ).done( function( data ) {
75
                callback( _fromXMLStruct( data ) );
76
            } ).fail( function( data ) {
77
                callback( { error: data } );
78
            } );
79
        },
80
81
        SaveRecord: function( id, record, callback ) {
82
            $.ajax( {
83
                type: 'POST',
84
                url: '/cgi-bin/koha/svc/bib/' + id,
85
                data: record.toXML(),
86
                contentType: 'text/xml'
87
            } ).done( function( data ) {
88
                callback( _fromXMLStruct( data ) );
89
            } ).fail( function( data ) {
90
                callback( { data: { error: data } } );
91
            } );
92
        },
93
94
        GetTagsBy: function( frameworkcode, field, value ) {
95
            var result = {};
96
97
            $.each( _frameworks[frameworkcode], function( undef, tag ) {
98
                var tagnum = tag[0], taginfo = tag[1];
99
100
                if ( taginfo[field] == value ) result[tagnum] = true;
101
            } );
102
103
            return result;
104
        },
105
106
        GetSubfieldsBy: function( frameworkcode, field, value ) {
107
            var result = {};
108
109
            $.each( _frameworks[frameworkcode], function( undef, tag ) {
110
                var tagnum = tag[0], taginfo = tag[1];
111
112
                $.each( taginfo.subfields, function( undef, subfield ) {
113
                    var subfieldcode = subfield[0], subfieldinfo = subfield[1];
114
115
                    if ( subfieldinfo[field] == value ) {
116
                        if ( !result[tagnum] ) result[tagnum] = {};
117
118
                        result[tagnum][subfieldcode] = true;
119
                    }
120
                } );
121
            } );
122
123
            return result;
124
        },
125
126
        FillRecord: function( frameworkcode, record, allTags ) {
127
            $.each( _frameworks[frameworkcode], function( undef, tag ) {
128
                var tagnum = tag[0], taginfo = tag[1];
129
130
                if ( taginfo.mandatory != "1" && !allTags ) return;
131
132
                var fields = record.fields(tagnum);
133
134
                if ( fields.length == 0 ) {
135
                    var newField = new MARC.Field( tagnum, ' ', ' ', [] );
136
                    fields.push( newField );
137
                    record.addFieldGrouped( newField );
138
139
                    if ( tagnum < '010' ) {
140
                        newField.addSubfield( [ '@', '' ] );
141
                        return;
142
                    }
143
                }
144
145
                $.each( taginfo.subfields, function( undef, subfield ) {
146
                    var subfieldcode = subfield[0], subfieldinfo = subfield[1];
147
148
                    if ( subfieldinfo.mandatory != "1" && !allTags ) return;
149
150
                    $.each( fields, function( undef, field ) {
151
                        if ( !field.hasSubfield(subfieldcode) ) field.addSubfieldGrouped( [ subfieldcode, '' ] );
152
                    } );
153
                } );
154
            } );
155
        },
156
157
        ValidateRecord: function( frameworkcode, record ) {
158
            var errors = [];
159
160
            var mandatoryTags = KohaBackend.GetTagsBy( '', 'mandatory', '1' );
161
            var mandatorySubfields = KohaBackend.GetSubfieldsBy( '', 'mandatory', '1' );
162
            var nonRepeatableTags = KohaBackend.GetTagsBy( '', 'repeatable', '0' );
163
            var nonRepeatableSubfields = KohaBackend.GetSubfieldsBy( '', 'repeatable', '0' );
164
165
            $.each( mandatoryTags, function( tag ) {
166
                if ( !record.hasField( tag ) ) errors.push( { type: 'missingTag', tag: tag } );
167
            } );
168
169
            var seenTags = {};
170
171
            $.each( record.fields(), function( undef, field ) {
172
                if ( seenTags[ field.tagnumber() ] && nonRepeatableTags[ field.tagnumber() ] ) {
173
                    errors.push( { type: 'unrepeatableTag', line: field.sourceLine, tag: field.tagnumber() } );
174
                    return;
175
                }
176
177
                seenTags[ field.tagnumber() ] = true;
178
179
                var seenSubfields = {};
180
181
                $.each( field.subfields(), function( undef, subfield ) {
182
                    if ( seenSubfields[ subfield[0] ] != null && nonRepeatableSubfields[ field.tagnumber() ][ subfield[0] ] ) {
183
                        errors.push( { type: 'unrepeatableSubfield', subfield: subfield[0], line: field.sourceLine } );
184
                    } else {
185
                        seenSubfields[ subfield[0] ] = subfield[1];
186
                    }
187
                } );
188
189
                $.each( mandatorySubfields[ field.tagnumber() ] || {}, function( subfield ) {
190
                    if ( !seenSubfields[ subfield ] ) {
191
                        errors.push( { type: 'missingSubfield', subfield: subfield[0], line: field.sourceLine } );
192
                    }
193
                } );
194
            } );
195
196
            return errors;
197
        },
198
    };
199
200
    return KohaBackend;
201
} );
(-)a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/macros.js (+196 lines)
Line 0 Link Here
1
define( [ 'widget' ], function( Widget ) {
2
    function _setIndicators( editor, ind1, ind2 ) {
3
        var info = editor.getLineInfo( editor.cm.getCursor() );
4
        if (!info || !info.subfields) return false;
5
6
        var cur = editor.cm.getCursor();
7
8
        var indicators = [ ind1 || info.contents.substring(4, 5) || '_', ind2 || info.contents.substring(6, 7) || '_' ];
9
10
        editor.cm.replaceRange(
11
            info.tagNumber + ' ' + indicators.join(' ') + ' ' + info.contents.substring(8),
12
            { line: cur.line, ch: 0 },
13
            { line: cur.line },
14
            'marcAware'
15
        );
16
17
        return true;
18
    }
19
20
    var _commandGenerators = [
21
        [ /^copy field data$/i, function() {
22
            return function( editor, state ) {
23
                var info = editor.getLineInfo( editor.cm.getCursor() );
24
                if (!info) return false;
25
26
                if (info.subfields) {
27
                    state.clipboard = info.contents.substring(4);
28
                } else {
29
                    state.clipboard = info.contents.substring(8);
30
                }
31
            };
32
        } ],
33
        [ /^copy subfield data$/i, function() {
34
            return function( editor, state ) {
35
                var info = editor.getLineInfo( editor.cm.getCursor() );
36
                if (!info) return false;
37
38
                var cur = editor.cm.getCursor();
39
40
                if (info.subfields) {
41
                    for (var i = 0; i < info.subfields.length; i++) {
42
                        var end = i == info.subfields.length - 1 ? info.contents.length : info.subfields[i+1].ch;
43
                        if (cur.ch > end) continue;
44
45
                        state.clipboard = info.contents.substring(info.subfields[i].ch + 3, end);
46
                        return;
47
                    }
48
                }
49
50
                return false;
51
            }
52
        } ],
53
        [ /^delete field$/i, function() {
54
            return function( editor, state ) {
55
                var cur = editor.cm.getCursor();
56
57
                editor.cm.replaceRange( "", { line: cur.line, ch: 0 }, { line: cur.line + 1, ch: 0 }, 'marcAware' );
58
            }
59
        } ],
60
        [ /^goto field end$/i, function() {
61
            return function( editor, state ) {
62
                editor.cm.setCursor( { line: editor.cm.lastLine() } );
63
            }
64
        } ],
65
        [ /^goto field (\w{3})$/i, function(field) {
66
            var matcher = new RegExp('^' + field + ' ');
67
            return function( editor, state ) {
68
                for ( var line = 0, contents; (contents = editor.cm.getLine(line)); line++ ) {
69
                    if ( matcher.exec( contents ) ) {
70
                        editor.cm.setCursor( { line: line, ch: 0 } );
71
                        return;
72
                    }
73
                }
74
75
                return false;
76
            }
77
        } ],
78
        [ /^goto subfield end$/i, function() {
79
            return function( editor, state ) {
80
                var cur = editor.cm.getCursor();
81
82
                editor.cm.setCursor( { line: cur.line } );
83
            }
84
        } ],
85
        [ /^goto subfield (\w)$/i, function( code ) {
86
            return function( editor, state ) {
87
                var info = editor.getLineInfo( editor.cm.getCursor() );
88
                if (!info || !info.subfields) return false;
89
90
                var cur = editor.cm.getCursor();
91
92
                for (var i = 0; i < info.subfields.length; i++) {
93
                    if ( info.subfields[i].code != code ) continue;
94
95
                    var end = i == info.subfields.length - 1 ? info.contents.length : info.subfields[i+1].ch;
96
                    editor.cm.setCursor( { line: cur.line, ch: end } );
97
                    return;
98
                }
99
100
                return false;
101
            }
102
        } ],
103
        [ /^insert (new )?field (\w{3}) data=(.*)/i, function(undef, field, data) {
104
            var new_contents = field + ( field < '100' ? ' ' : ' _ _ ' ) + data.replace(/\\([0-9a-z])/g, '$$$1 ');
105
            return function( editor, state ) {
106
                var line, contents;
107
108
                for ( line = 0; (contents = editor.cm.getLine(line)); line++ ) {
109
                    if ( contents && contents[0] > field[0] ) break;
110
                }
111
112
                if ( line > editor.cm.lastLine() ) {
113
                    new_contents = '\n' + new_contents;
114
                } else {
115
                    new_contents = new_contents + '\n';
116
                }
117
118
                editor.cm.replaceRange( new_contents, { line: line, ch: 0 }, null, 'marcAware' );
119
                editor.cm.setCursor( { line: line, ch: 0 } );
120
            }
121
        } ],
122
        [ /^insert (new )?subfield (\w) data=(.*)/i, function(undef, subfield, data) {
123
            return function( editor, state ) {
124
                editor.cm.replaceRange( '$' + subfield + ' ' + data, { line: editor.cm.getCursor().line }, null, 'marcAware' );
125
            }
126
        } ],
127
        [ /^paste$/i, function() {
128
            return function( editor, state ) {
129
                var cur = editor.cm.getCursor();
130
131
                editor.cm.replaceRange( state.clipboard, cur, null, 'marcAware' );
132
            }
133
        } ],
134
        [ /^set indicator([12])=([ _0-9])$/i, function( ind, value ) {
135
            return function( editor, state ) {
136
                return ind == '1' ? _setIndicators( editor, ind1, null ) : _setIndicators( editor, null, ind2 );
137
            }
138
        } ],
139
        [ /^set indicators=([ _0-9])([ _0-9])$/i, function( ind1, ind2 ) {
140
            return function( editor, state ) {
141
                return _setIndicators( editor, ind1, ind2 );
142
            }
143
        } ],
144
    ];
145
146
    var Macros = {
147
        Compile: function( macro ) {
148
            var result = { commands: [], errors: [] };
149
150
            $.each( macro.split(/\r\n|\n/), function( line, contents ) {
151
                var command;
152
153
                if ( contents.match(/^\s*$/) ) return;
154
155
                $.each( _commandGenerators, function( undef, gen ) {
156
                    var match;
157
158
                    if ( !( match = gen[0].exec( contents ) ) ) return;
159
160
                    command = gen[1].apply(null, match.slice(1));
161
                    return false;
162
                } );
163
164
                if ( !command ) {
165
                    result.errors.push( { line: line, error: 'unrecognized' } );
166
                }
167
168
                result.commands.push( { func: command, orig: contents, line: line } );
169
            } );
170
171
            return result;
172
        },
173
        Run: function( editor, macro ) {
174
            var compiled = Macros.Compile(macro);
175
            if ( compiled.errors.length ) return { errors: compiled.errors };
176
            var state = {
177
                clipboard: '',
178
            };
179
180
            var result = { errors: [] };
181
182
            editor.cm.operation( function() {
183
                $.each( compiled.commands, function( undef, command ) {
184
                    if ( command.func( editor, state ) === false ) {
185
                        result.errors.push( { line: command.line, error: 'failed' } );
186
                        return false;
187
                    }
188
                } );
189
            } );
190
191
            return result;
192
        },
193
    };
194
195
    return Macros;
196
} );
(-)a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-editor.js (+358 lines)
Line 0 Link Here
1
define( [ 'marc-record', 'koha-backend', 'preferences', 'text-marc', 'widget' ], function( MARC, KohaBackend, Preferences, TextMARC, Widget ) {
2
    var NOTIFY_TIMEOUT = 250;
3
4
    function editorCursorActivity( cm ) {
5
        var editor = cm.marceditor;
6
        if ( editor.textMode ) return;
7
8
        $('#status-tag-info').empty();
9
        $('#status-subfield-info').empty();
10
11
        var info = editor.getLineInfo( cm.getCursor() );
12
13
        if ( !info ) return; // No tag at all on this line
14
15
        var taginfo = KohaBackend.GetTagInfo( '', info.tagNumber );
16
        $('#status-tag-info').html( '<strong>' + info.tagNumber + ':</strong> ' );
17
18
        if ( taginfo ) {
19
            $('#status-tag-info').append( taginfo.lib );
20
21
            if ( !info.currentSubfield ) return; // No current subfield
22
23
            var subfieldinfo = taginfo.subfields[info.currentSubfield];
24
            $('#status-subfield-info').html( '<strong>$' + info.currentSubfield + ':</strong> ' );
25
26
            if ( subfieldinfo ) {
27
                $('#status-subfield-info').append( subfieldinfo.lib );
28
            } else {
29
                $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
30
            }
31
        } else {
32
            $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
33
        }
34
    }
35
36
    function editorBeforeChange( cm, change ) {
37
        var editor = cm.marceditor;
38
        if ( editor.textMode || change.origin == 'marcAware' ) return;
39
40
        // FIXME: Should only cancel changes if this is a control field/subfield widget
41
        if ( change.from.line !== change.to.line || Math.abs( change.from.ch - change.to.ch ) > 1 || change.text.length != 1 || change.text[0].length != 0 ) return; // Not single-char change
42
43
        if ( change.from.ch == change.to.ch - 1 && cm.findMarksAt( { line: change.from.line, ch: change.from.ch + 1 } ).length ) {
44
            change.cancel();
45
        } else if ( change.from.ch == change.to.ch && cm.findMarksAt(change.from).length && !change.text[0].match(/^[$|ǂ‡]$/) ) {
46
            change.cancel();
47
        }
48
    }
49
50
    function editorChange( cm, change ) {
51
        var editor = cm.marceditor;
52
        if ( editor.textMode ) return;
53
54
        var updatedLines = {};
55
        do {
56
            var from = change.from;
57
            var to = change.to;
58
            if ( to.line < from.line || to.ch < from.ch ) {
59
                var temp = from;
60
                from = to;
61
                to = temp;
62
            }
63
64
            var startLine, endLine;
65
            if ( change.text.length == 2 && from.line == to.line && from.ch == to.ch) {
66
                if ( from.ch == 0 ) {
67
                    startLine = endLine = from.line;
68
                } else if ( from.ch == cm.getLine(from.line).length ){
69
                    startLine = endLine = from.line + 1;
70
                }
71
            } else {
72
                startLine = (from.ch == cm.getLine(from.line).length && from.line < to.line) ? Math.min(cm.lastLine(), from.line + 1) : from.line;
73
                endLine = ((to.ch == 0 && from.line < to.line) ? Math.max(to.line - 1, 0) : to.line) + change.text.length - 1;
74
            }
75
76
            for ( var line = startLine; line <= endLine; line++ ) {
77
                if ( updatedLines[line] ) continue;
78
79
                if ( Preferences.user.fieldWidgets ) Widget.UpdateLine( cm.marceditor, line );
80
                if ( change.origin != 'setValue' && change.origin != 'marcWidgetPrefill' ) cm.addLineClass( line, 'wrapper', 'modified-line' );
81
                updatedLines[line] = true;
82
            }
83
        } while ( change = change.next )
84
85
        Widget.ActivateAt( cm, cm.getCursor() );
86
        cm.marceditor.startNotify();
87
    }
88
89
    // Editor helper functions
90
    function activateTabPosition( cm, cur, idx ) {
91
        cm.setCursor( cur );
92
        Widget.ActivateAt( cm, cur, idx );
93
    }
94
95
    function getTabPositions( editor, cur ) {
96
        var info = editor.getLineInfo( cur || editor.cm.getCursor() );
97
98
        if ( info ) {
99
            if ( info.subfields ) {
100
                var positions = [ 0, 4, 6 ];
101
102
                $.each( info.subfields, function( undef, subfield ) {
103
                    positions.push( subfield.ch + 3 );
104
                } );
105
106
                return positions;
107
            } else {
108
                return [ 0, 4 ];
109
            }
110
        } else {
111
            return [];
112
        }
113
    }
114
115
    var _editorKeys = {
116
        Enter: function( cm ) {
117
            var cursor = cm.getCursor();
118
            cm.replaceRange( '\n', { line: cursor.line }, null, 'marcAware' );
119
            cm.setCursor( { line: cursor.line + 1, ch: 0 } );
120
        },
121
122
        'Shift-Enter': function( cm ) {
123
            var cursor = cm.getCursor();
124
            cm.replaceRange( '\n', { line: cursor.line, ch: 0 }, null, 'marcAware' );
125
            cm.setCursor( { line: cursor.line, ch: 0 } );
126
        },
127
128
        'Ctrl-X': function( cm ) {
129
            // Delete subfield (or cut)
130
            if ( cm.somethingSelected() ) return true;
131
132
            var cur = cm.getCursor();
133
            var info = cm.marceditor.getLineInfo( cur );
134
            if ( !info || !info.subfields ) return true;
135
136
            for (var i = 0; i < info.subfields.length; i++) {
137
                var end = i == info.subfields.length - 1 ? info.contents.length : info.subfields[i+1].ch;
138
                if (cur.ch > end) continue;
139
140
                cm.replaceRange( "", { line: cur.line, ch: info.subfields[i].ch }, { line: cur.line, ch: end }, 'marcAware' );
141
                return;
142
            }
143
        },
144
145
        'Shift-Ctrl-X': function( cm ) {
146
            // Delete line
147
            var cur = cm.getCursor();
148
149
            cm.replaceRange( "", { line: cur.line, ch: 0 }, { line: cur.line + 1, ch: 0 }, 'marcAware' );
150
        },
151
152
        Tab: function( cm ) {
153
            // Move through parts of tag/fixed fields
154
            var positions = getTabPositions( cm.marceditor );
155
            var cur = cm.getCursor();
156
157
            for ( var i = 0; i < positions.length; i++ ) {
158
                if ( positions[i] > cur.ch ) {
159
                    activateTabPosition( cm, { line: cur.line, ch: positions[i] } );
160
                    return false;
161
                }
162
            }
163
164
            cm.setCursor( { line: cur.line + 1, ch: 0 } );
165
        },
166
167
        'Shift-Tab': function( cm ) {
168
            // Move backwards through parts of tag/fixed fields
169
            var positions = getTabPositions( cm.marceditor );
170
            var cur = cm.getCursor();
171
172
            for ( var i = positions.length - 1; i >= 0; i-- ) {
173
                if ( positions[i] < cur.ch ) {
174
                    activateTabPosition( cm, { line: cur.line, ch: positions[i] } );
175
                    return false;
176
                }
177
            }
178
179
            if ( cur.line == 0 ) return;
180
181
            var prevPositions = getTabPositions( cm, { line: cur.line - 1, ch: cm.getLine( cur.line - 1 ).length } );
182
183
            if ( prevPositions.length ) {
184
                activateTabPosition( cm, { line: cur.line - 1, ch: prevPositions[ prevPositions.length - 1 ] }, -1 );
185
            } else {
186
                cm.setCursor( { line: cur.line - 1, ch: 0 } );
187
            }
188
        },
189
    };
190
191
    function MARCEditor( position ) {
192
        this.cm = CodeMirror(
193
            position,
194
            {
195
                extraKeys: _editorKeys,
196
                gutters: [
197
                    'modified-line-gutter',
198
                ],
199
                lineWrapping: true,
200
                mode: {
201
                    name: 'marc',
202
                    nonRepeatableTags: KohaBackend.GetTagsBy( '', 'repeatable', '0' ),
203
                    nonRepeatableSubfields: KohaBackend.GetSubfieldsBy( '', 'repeatable', '0' )
204
                }
205
            }
206
        );
207
        this.cm.marceditor = this;
208
209
        this.cm.on( 'beforeChange', editorBeforeChange );
210
        this.cm.on( 'change', editorChange );
211
        this.cm.on( 'cursorActivity', editorCursorActivity );
212
213
        this.subscribers = [];
214
        this.subscribe( function( marceditor ) {
215
            Widget.Notify( marceditor );
216
        } );
217
    }
218
219
    MARCEditor.prototype.setUseWidgets = function( val ) {
220
        if ( val ) {
221
            for ( var line = 0; line <= this.cm.lastLine(); line++ ) {
222
                Widget.UpdateLine( this, line );
223
            }
224
        } else {
225
            $.each( this.cm.getAllMarks(), function( undef, mark ) {
226
                if ( mark.widget ) mark.widget.clearToText();
227
            } );
228
        }
229
    };
230
231
    MARCEditor.prototype.focus = function() {
232
        this.cm.focus();
233
    };
234
235
    MARCEditor.prototype.refresh = function() {
236
        this.cm.refresh();
237
    };
238
239
    MARCEditor.prototype.displayRecord = function( record ) {
240
        this.cm.setValue( TextMARC.RecordToText(record) );
241
    };
242
243
    MARCEditor.prototype.getRecord = function() {
244
        this.textMode = true;
245
246
        $.each( this.cm.getAllMarks(), function( undef, mark ) {
247
            if ( mark.widget ) mark.widget.clearToText();
248
        } );
249
        var record = TextMARC.TextToRecord( this.cm.getValue() );
250
        for ( var line = 0; line <= this.cm.lastLine(); line++ ) {
251
            if ( Preferences.user.fieldWidgets ) Widget.UpdateLine( this, line );
252
        }
253
254
        this.textMode = false;
255
256
        return record;
257
    };
258
259
    MARCEditor.prototype.getLineInfo = function( pos ) {
260
        var contents = this.cm.getLine( pos.line );
261
        if ( contents == null ) return {};
262
263
        var tagNumber = contents.match( /^([A-Za-z0-9]{3}) / );
264
265
        if ( !tagNumber ) return null; // No tag at all on this line
266
        tagNumber = tagNumber[1];
267
268
        if ( tagNumber < '010' ) return { tagNumber: tagNumber, contents: contents }; // No current subfield
269
270
        var matcher = /[$|ǂ‡]([a-z0-9%]) /g;
271
        var match;
272
273
        var subfields = [];
274
        var currentSubfield;
275
276
        while ( ( match = matcher.exec(contents) ) ) {
277
            subfields.push( { code: match[1], ch: match.index } );
278
            if ( match.index < pos.ch ) currentSubfield = match[1];
279
        }
280
281
        return { tagNumber: tagNumber, subfields: subfields, currentSubfield: currentSubfield, contents: contents };
282
    };
283
284
    MARCEditor.prototype.addError = function( line, error ) {
285
        var found = false;
286
        var options = {};
287
288
        if ( line == null ) {
289
            line = 0;
290
            options.above = true;
291
        }
292
293
        $.each( this.cm.getLineHandle(line).widgets || [], function( undef, widget ) {
294
            if ( !widget.isErrorMarker ) return;
295
296
            found = true;
297
298
            $( widget.node ).append( '; ' + error );
299
            widget.changed();
300
301
            return false;
302
        } );
303
304
        if ( found ) return;
305
306
        var node = $( '<div class="structure-error"><i class="icon-remove"></i> ' + error + '</div>' )[0];
307
        var widget = this.cm.addLineWidget( line, node, options );
308
309
        widget.node = node;
310
        widget.isErrorMarker = true;
311
    };
312
313
    MARCEditor.prototype.removeErrors = function() {
314
        for ( var line = 0; line < this.cm.lineCount(); line++ ) {
315
            $.each( this.cm.getLineHandle( line ).widgets || [], function( undef, lineWidget ) {
316
                if ( lineWidget.isErrorMarker ) lineWidget.clear();
317
            } );
318
        }
319
    };
320
321
    MARCEditor.prototype.getFixedField = function(field) {
322
        field += ' ';
323
        for ( var line = 0; line < this.cm.lineCount(); line++ ) {
324
            var contents = this.cm.getLine(line);
325
            if ( contents.substr( 0, 4 ) != field ) continue;
326
327
            var marks = this.cm.findMarksAt( { line: line, ch: 4 } );
328
            if ( marks[0] && marks[0].widget ) {
329
                return marks[0].widget.text;
330
            } else {
331
                return contents.substr(4);
332
            }
333
        }
334
335
        return null;
336
    };
337
338
    MARCEditor.prototype.startNotify = function() {
339
        if ( this.notifyTimeout ) clearTimeout( this.notifyTimeout );
340
        this.notifyTimeout = setTimeout( $.proxy( function() {
341
            this.notifyAll();
342
343
            this.notifyTimeout = null;
344
        }, this ), NOTIFY_TIMEOUT );
345
    };
346
347
    MARCEditor.prototype.notifyAll = function() {
348
        $.each( this.subscribers, $.proxy( function( undef, subscriber ) {
349
            subscriber(this);
350
        }, this ) );
351
    };
352
353
    MARCEditor.prototype.subscribe = function( subscriber ) {
354
        this.subscribers.push( subscriber );
355
    };
356
357
    return MARCEditor;
358
} );
(-)a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-mode.js (+129 lines)
Line 0 Link Here
1
/**
2
 * Textual MARC mode for CodeMirror.
3
 * Copyright (c) 2013 ByWater
4
 */
5
6
// Expected format: 245 _ 1 $a Pizza |c 34ars
7
8
CodeMirror.defineMode( 'marc', function( config, modeConfig ) {
9
    modeConfig.nonRepeatableTags = modeConfig.nonRepeatableTags || {};
10
    modeConfig.nonRepeatableSubfields = modeConfig.nonRepeatableSubfields || {};
11
12
    return {
13
        startState: function( prevState ) {
14
            var state = prevState || {};
15
16
            if ( !prevState ) {
17
                state.seenTags = {};
18
            }
19
20
            state.indicatorNeeded = false;
21
            state.subAllowed = true;
22
            state.subfieldCode = undefined;
23
            state.tagNumber = undefined;
24
            state.seenSubfields = {};
25
26
            return state;
27
        },
28
        copyState: function( prevState ) {
29
            var result = $.extend( {}, prevState );
30
            result.seenTags = $.extend( {}, prevState.seenTags );
31
            result.seenSubfields = $.extend( {}, prevState.seenSubfields );
32
33
            return result;
34
        },
35
        token: function( stream, state ) {
36
            var match;
37
            if ( stream.sol() ) {
38
                this.startState( state );
39
                if ( match = stream.match( /[0-9A-Za-z]+/ ) ) {
40
                    match = match[0];
41
                    if ( match.length != 3 ) {
42
                        if ( stream.eol() && match.length < 3 ) {
43
                            // Don't show error for incomplete number
44
                            return 'tagnumber';
45
                        } else {
46
                            stream.skipToEnd();
47
                            return 'error';
48
                        }
49
                    }
50
51
                    state.tagNumber = match;
52
                    if ( state.tagNumber < '010' ) {
53
                        // Control field
54
                        state.subAllowed = false;
55
                    }
56
57
                    if ( state.seenTags[state.tagNumber] && modeConfig.nonRepeatableTags[state.tagNumber] ) {
58
                        return 'bad-tagnumber';
59
                    } else {
60
                        state.seenTags[state.tagNumber] = true;
61
                        return 'tagnumber';
62
                    }
63
                } else {
64
                    stream.skipToEnd();
65
                    return 'error';
66
                }
67
            }
68
69
            if ( stream.eol() ) {
70
                return;
71
            }
72
73
            if ( !state.subAllowed && stream.pos == 3 ) {
74
                if ( stream.next() == ' ' ) {
75
                    return 'reqspace';
76
                } else {
77
                    stream.skipToEnd();
78
                    return 'error';
79
                }
80
            }
81
82
            if ( stream.pos < 8 && state.subAllowed ) {
83
                switch ( stream.pos ) {
84
                    case 3:
85
                    case 5:
86
                    case 7:
87
                        if ( stream.next() == ' ' ) {
88
                            return 'reqspace';
89
                        } else {
90
                            stream.skipToEnd();
91
                            return 'error';
92
                        }
93
                    case 4:
94
                    case 6:
95
                        if ( /[0-9A-Za-z_]/.test( stream.next() ) ) {
96
                            return 'indicator';
97
                        } else {
98
                            stream.skipToEnd();
99
                            return 'error';
100
                        }
101
                }
102
            }
103
104
            if ( state.subAllowed ) {
105
                if ( stream.pos != 8 && stream.match( /[^$|ǂ‡]+/ ) ) return;
106
107
                if ( stream.eat( /[$|ǂ‡]/ ) ) {
108
                    var subfieldCode;
109
                    if ( ( subfieldCode = stream.eat( /[a-z0-9%]/ ) ) && stream.eat( ' ' ) ) {
110
                        state.subfieldCode = subfieldCode;
111
                        if ( state.seenSubfields[state.subfieldCode] && ( modeConfig.nonRepeatableSubfields[state.tagNumber] || {} )[state.subfieldCode] ) {
112
                            return 'bad-subfieldcode';
113
                        } else {
114
                            state.seenSubfields[state.subfieldCode] = true;
115
                            return 'subfieldcode';
116
                        }
117
                    }
118
                }
119
120
                if ( stream.pos < 11 && ( !stream.eol() || stream.pos == 8 ) ) {
121
                    stream.skipToEnd();
122
                    return 'error';
123
                }
124
            } else {
125
                stream.skipToEnd();
126
            }
127
        }
128
    };
129
} );
(-)a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/marc-record.js (+351 lines)
Line 0 Link Here
1
/**
2
 * Adapted and cleaned up from biblios.net, which is purportedly under the GPL.
3
 * Source: http://git.librarypolice.com/?p=biblios.git;a=blob_plain;f=plugins/marc21editor/marcrecord.js;hb=master
4
 *
5
 * ISO2709 import/export is cribbed from marcjs, which is under the MIT license.
6
 * Source: https://github.com/fredericd/marcjs/blob/master/lib/marcjs.js
7
 */
8
9
define( function() {
10
    var MARC = {};
11
12
    var _escape_map = {
13
        "<": "&lt;",
14
        "&": "&amp;",
15
        "\"": "&quot;"
16
    };
17
18
    function _escape(str) {
19
        return str.replace( /[<&"]/, function (c) { return _escape_map[c] } );
20
    }
21
22
    function _intpadded(i, digits) {
23
        i = i + '';
24
        while (i.length < digits) {
25
            i = '0' + i;
26
        }
27
        return i;
28
    }
29
30
    MARC.Record = function (fieldlist) {
31
        this._fieldlist = fieldlist || [];
32
    }
33
34
    $.extend( MARC.Record.prototype, {
35
        leader: function(val) {
36
            var field = this.field('000');
37
38
            if (val) {
39
                if (field) {
40
                    field.subfield( '@', val );
41
                } else {
42
                    field = new MARC.Field( '000', '', '', [ [ '@', val ] ] );
43
                    this.addFieldGrouped(field);
44
                }
45
            } else {
46
                return ( field && field.subfield('@') ) || '     nam a22     7a 4500';
47
            }
48
        },
49
50
        /**
51
         * If a tagnumber is given, returns all fields with that tagnumber.
52
         * Otherwise, returns all fields.
53
         */
54
        fields: function(fieldno) {
55
            if (!fieldno) return this._fieldlist;
56
57
            var results = [];
58
            for(var i=0; i<this._fieldlist.length; i++){
59
                if( this._fieldlist[i].tagnumber() == fieldno ) {
60
                    results.push(this._fieldlist[i]);
61
                }
62
            }
63
64
            return results;
65
        },
66
67
        /**
68
         * Returns the first field with the given tagnumber, or false.
69
         */
70
        field: function(fieldno) {
71
            for(var i=0; i<this._fieldlist.length; i++){
72
                if( this._fieldlist[i].tagnumber() == fieldno ) {
73
                    return this._fieldlist[i];
74
                }
75
            }
76
            return false;
77
        },
78
79
        /**
80
         * Adds the given MARC.Field to the record, at the end.
81
         */
82
        addField: function(field) {
83
            this._fieldlist.push(field);
84
            return true;
85
        },
86
87
        /**
88
         * Adds the given MARC.Field to the record, at the end of the matching
89
         * x00 group. If a record has a 100, 245 and 300 field, for instance, a
90
         * 260 field would be added after the 245 field.
91
         */
92
        addFieldGrouped: function(field) {
93
            for ( var i = this._fieldlist.length - 1; i >= 0; i-- ) {
94
                if ( this._fieldlist[i].tagnumber()[0] <= field.tagnumber()[0] ) {
95
                    this._fieldlist.splice(i+1, 0, field);
96
                    return true;
97
                }
98
            }
99
            this._fieldlist.push(field);
100
            return true;
101
        },
102
103
        /**
104
         * Removes the first field with the given tagnumber. Returns false if no
105
         * such field was found.
106
         */
107
        removeField: function(fieldno) {
108
            for(var i=0; i<this._fieldlist.length; i++){
109
                if( this._fieldlist[i].tagnumber() == fieldno ) {
110
                    this._fieldlist.splice(i, 1);
111
                    return true;
112
                }
113
            }
114
            return false;
115
        },
116
117
        /**
118
         * Check to see if this record contains a field with the given
119
         * tagnumber.
120
         */
121
        hasField: function(fieldno) {
122
            for(var i=0; i<this._fieldlist.length; i++){
123
                if( this._fieldlist[i].tagnumber() == fieldno ) {
124
                    return true;
125
                }
126
            }
127
            return false;
128
        },
129
130
        toXML: function() {
131
            var xml = '<record xmlns="http://www.loc.gov/MARC21/slim">';
132
            for(var i=0; i<this._fieldlist.length; i++){
133
                xml += this._fieldlist[i].toXML();
134
            }
135
            xml += '</record>';
136
            return xml;
137
        },
138
139
        /**
140
         * Truncates this record, and loads in the data from the given MARCXML
141
         * document.
142
         */
143
        loadMARCXML: function(xmldoc) {
144
            var record = this;
145
            record.xmlSource = xmldoc;
146
            this._fieldlist.length = 0;
147
            this.leader( $('leader', xmldoc).text() );
148
            $('controlfield', xmldoc).each( function(i) {
149
                val = $(this).text();
150
                tagnum = $(this).attr('tag');
151
                record._fieldlist.push( new MARC.Field(tagnum, '', '', [ [ '@', val ] ]) );
152
            });
153
            $('datafield', xmldoc).each(function(i) {
154
                var value = $(this).text();
155
                var tagnum = $(this).attr('tag');
156
                var ind1 = $(this).attr('ind1') || ' ';
157
                var ind2 = $(this).attr('ind2') || ' ';
158
                var subfields = new Array();
159
                $('subfield', this).each(function(j) {
160
                    var sfval = $(this).text();
161
                    var sfcode = $(this).attr('code');
162
                    subfields.push( [ sfcode, sfval ] );
163
                });
164
                record._fieldlist.push( new MARC.Field(tagnum, ind1, ind2, subfields) );
165
            });
166
        },
167
168
        toISO2709: function() {
169
            var FT = '\x1e', RT = '\x1d', DE = '\x1f';
170
            var directory = '',
171
                from = 0,
172
                chunks = ['', ''];
173
174
            $.each( this._fieldlist, function( undef, element ) {
175
                var chunk = '';
176
                var tag = element.tagnumber();
177
                if (tag == '000') {
178
                    return;
179
                } else if (tag < '010') {
180
                    chunk = element.subfields()[0][1];
181
                } else {
182
                    chunk = element.indicators().join('');
183
                    $.each( element.subfields(), function( undef, subfield ) {
184
                        chunk += DE + subfield[0] + subfield[1];
185
                    } );
186
                }
187
                chunk += FT;
188
                chunks.push(chunk);
189
                directory += _intpadded(tag,3) + _intpadded(chunk.length,4) + _intpadded(from,5);
190
                from += chunk.length;
191
            });
192
193
            chunks.push(RT);
194
            directory += FT;
195
            var offset = 24 + 12 * (this._fieldlist.length - 1) + 1;
196
            var length = offset + from + 1;
197
            var leader = this.leader();
198
            leader = _intpadded(length,5) + leader.substr(5,7) + _intpadded(offset,5) +
199
                leader.substr(17);
200
            chunks[0] = leader;
201
            chunks[1] = directory;
202
            return chunks.join('');
203
        },
204
205
        loadISO2709: function(data) {
206
            this._fieldlist.length = 0;
207
            this.leader(data.substr(0, 24));
208
            var directory_len = parseInt(data.substring(12, 17), 0) - 25,
209
                number_of_tag = directory_len / 12;
210
            for (var i = 0; i < number_of_tag; i++) {
211
                var off = 24 + i * 12,
212
                    tag = data.substring(off, off+3),
213
                    len = parseInt(data.substring(off+3, off+7), 0) - 1,
214
                    pos = parseInt(data.substring(off+7, off+12), 0) + 25 + directory_len,
215
                    value = data.substring(pos, pos+len);
216
                if ( parseInt(tag) < 10 ) {
217
                    this.addField( new MARC.Field( tag, '', '', [ [ '@', value ] ] ) );
218
                } else {
219
                    if ( value.indexOf('\x1F') ) { // There are some letters
220
                        var ind1 = value.substr(0, 1), ind2 = value.substr(1, 1);
221
                        var subfields = [];
222
223
                        $.each( value.substr(3).split('\x1f'), function( undef, v ) {
224
                            if (v.length < 2) return;
225
                            subfields.push([v.substr(0, 1), v.substr(1)]);
226
                        } );
227
228
                        this.addField( new MARC.Field( tag, ind1, ind2, subfields ) );
229
                    }
230
                }
231
            }
232
        }
233
    } );
234
235
    MARC.Field = function(tagnumber, indicator1, indicator2, subfields) {
236
        this._tagnumber = tagnumber;
237
        this._indicators = [ indicator1, indicator2 ];
238
        this._subfields = subfields;
239
    };
240
241
    $.extend( MARC.Field.prototype, {
242
        tagnumber: function() {
243
            return this._tagnumber;
244
        },
245
246
        isControlField: function() {
247
            return this._tagnumber < '010';
248
        },
249
250
        indicator: function(num, val) {
251
            if( val != null ) {
252
                this._indicators[num] = val;
253
            }
254
            return this._indicators[num];
255
        },
256
257
        indicators: function() {
258
            return this._indicators;
259
        },
260
261
        hasSubfield: function(code) {
262
            for(var i = 0; i<this._subfields.length; i++) {
263
                if( this._subfields[i][0] == code ) {
264
                    return true;
265
                }
266
            }
267
            return false;
268
        },
269
270
        removeSubfield: function(code) {
271
            for(var i = 0; i<this._subfields.length; i++) {
272
                if( this._subfields[i][0] == code ) {
273
                    this._subfields.splice(i,1);
274
                    return true;
275
                }
276
            }
277
            return false;
278
        },
279
280
        subfields: function() {
281
            return this._subfields;
282
        },
283
284
        addSubfield: function(sf) {
285
            this._subfields.push(sf);
286
            return true;
287
        },
288
289
        addSubfieldGrouped: function(sf) {
290
            function _kind( sc ) {
291
                if ( /[a-z]/.test( sc ) ) {
292
                    return 0;
293
                } else if ( /[0-9]/.test( sc ) ) {
294
                    return 1;
295
                } else {
296
                    return 2;
297
                }
298
            }
299
300
            for ( var i = this._subfields.length - 1; i >= 0; i-- ) {
301
                if ( i == 0 && _kind( sf[0] ) < _kind( this._subfields[i][0] ) ) {
302
                    this._subfields.splice( 0, 0, sf );
303
                    return true;
304
                } else if ( _kind( this._subfields[i][0] ) <= _kind( sf[0] )  ) {
305
                    this._subfields.splice( i + 1, 0, sf );
306
                    return true;
307
                }
308
            }
309
310
            this._subfields.push(sf);
311
            return true;
312
        },
313
314
        subfield: function(code, val) {
315
            var sf = '';
316
            for(var i = 0; i<this._subfields.length; i++) {
317
                if( this._subfields[i][0] == code ) {
318
                    sf = this._subfields[i];
319
                    if( val != null ) {
320
                        sf[1] = val;
321
                    }
322
                    return sf[1];
323
                }
324
            }
325
            return false;
326
        },
327
328
        toXML: function() {
329
            // decide if it's controlfield of datafield
330
            if( this._tagnumber == '000') {
331
                return '<leader>' + _escape( this._subfields[0][1] ) + '</leader>';
332
            } else if ( this._tagnumber < '010' ) {
333
                return '<controlfield tag="' + this._tagnumber + '">' + _escape( this._subfields[0][1] ) + '</controlfield>';
334
            } else {
335
                var result = '<datafield tag="' + this._tagnumber + '"';
336
                result += ' ind1="' + this._indicators[0] + '"';
337
                result += ' ind2="' + this._indicators[1] + '">';
338
                for( var i = 0; i< this._subfields.length; i++) {
339
                    result += '<subfield code="' + this._subfields[i][0] + '">';
340
                    result += _escape( this._subfields[i][1] );
341
                    result += '</subfield>';
342
                }
343
                result += '</datafield>';
344
345
                return result;
346
            }
347
        }
348
    } );
349
350
    return MARC;
351
} );
(-)a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/preferences.js (+28 lines)
Line 0 Link Here
1
define( function() {
2
    var Preferences = {
3
        Load: function( borrowernumber ) {
4
            if ( !borrowernumber ) return;
5
            var saved_prefs;
6
            try {
7
                saved_prefs = JSON.parse( localStorage[ 'cateditor_preferences_' + borrowernumber ] );
8
            } catch (e) {}
9
10
            Preferences.user = $.extend( {
11
                // Preference defaults
12
                fieldWidgets: true,
13
                font: 'monospace',
14
                fontSize: '1em',
15
                macros: {},
16
            }, saved_prefs );
17
        },
18
19
        Save: function( borrowernumber ) {
20
            if ( !borrowernumber ) return;
21
            if ( !Preferences.user ) Preferences.Load(borrowernumber);
22
23
            localStorage[ 'cateditor_preferences_' + borrowernumber ] = JSON.stringify(Preferences.user);
24
        },
25
    };
26
27
    return Preferences;
28
} );
(-)a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/search.js (+87 lines)
Line 0 Link Here
1
define( [ 'marc-record', 'pz2' ], function( MARC, Pazpar2 ) {
2
    //var _pz;
3
    var _onresults;
4
    var _recordCache = {};
5
    var _options;
6
7
    var Search = {
8
        Init: function( targets, options ) {
9
            var initOpts = {};
10
11
            _onresults = options.onresults;
12
13
            $.each( targets, function ( url, info ) {
14
                initOpts[ 'pz:name[' + url + ']' ] = info.name;
15
                initOpts[ 'pz:queryencoding[' + url + ']' ] = info.encoding;
16
                initOpts[ 'pz:xslt[' + url + ']' ] = info.kohasyntax.toLowerCase() + '-work-groups.xsl';
17
                initOpts[ 'pz:requestsyntax[' + url + ']' ] = info.syntax;
18
19
                // Load in default CCL mappings
20
                // Pazpar2 seems to have a bug where wildcard cclmaps are ignored.
21
                // What an incredible surprise.
22
                initOpts[ 'pz:cclmap:term[' + url + ']' ] = 'u=1016 t=l,r s=al';
23
                initOpts[ 'pz:cclmap:Author-name[' + url + ']' ] = 'u=1004 s=al';
24
                initOpts[ 'pz:cclmap:Classification-Dewey[' + url + ']' ] = 'u=13';
25
                initOpts[ 'pz:cclmap:Classification-LC[' + url + ']' ] = 'u=16';
26
                initOpts[ 'pz:cclmap:Date[' + url + ']' ] = 'u=30 r=r';
27
                initOpts[ 'pz:cclmap:Identifier-ISBN[' + url + ']' ] = 'u=7';
28
                initOpts[ 'pz:cclmap:Identifier-ISSN[' + url + ']' ] = 'u=8';
29
                initOpts[ 'pz:cclmap:Identifier-publisher-for-music[' + url + ']' ] = 'u=51';
30
                initOpts[ 'pz:cclmap:Identifier-standard[' + url + ']' ] = 'u=1007';
31
                initOpts[ 'pz:cclmap:LC-card-number[' + url + ']' ] = 'u=9';
32
                initOpts[ 'pz:cclmap:Local-number[' + url + ']' ] = 'u=12';
33
                initOpts[ 'pz:cclmap:Subject[' + url + ']' ] = 'u=21 s=al';
34
                initOpts[ 'pz:cclmap:Title[' + url + ']' ] = 'u=4 s=al';
35
36
                if ( info.authentication ) initOpts[ 'pz:authentication[' + url + ']' ] = info.authentication;
37
            } );
38
39
            _options =  $.extend( {
40
                initopts: initOpts,
41
                onshow: Search._onshow,
42
                errorhandler: options.onerror,
43
            }, options );
44
45
            _pz = new Pazpar2( _options );
46
        },
47
        Reconnect: function() {
48
            _pz.reset();
49
            _pz = new Pazpar2( _options );
50
        },
51
        Start: function( targets, q, limit ) {
52
            Search.includedTargets = [];
53
            recordcache = {};
54
55
            $.each( targets, function ( url, info ) {
56
                if ( !info.disabled ) Search.includedTargets.push( url );
57
            } );
58
59
            _pz.search( q, limit, 'relevance:0', 'pz:id=' + Search.includedTargets.join( '|' ) );
60
        },
61
        Fetch: function( offset ) {
62
            _pz.show( offset );
63
        },
64
        GetDetailedRecord: function( recid, callback ) {
65
            if ( _recordCache[recid] ) {
66
                callback( _recordCache[recid] );
67
                return;
68
            }
69
70
            _pz.record( recid, 0, undefined, { callback: function(data) {
71
                var record = _recordCache[recid] = new MARC.Record();
72
                record.loadMARCXML(data.xmlDoc);
73
74
                callback(record);
75
            } } );
76
        },
77
        _onshow: function( data ) {
78
            $.each( data.hits, function( undef, hit ) {
79
                hit.id = 'search:' + encodeURIComponent( hit.recid[0] );
80
            } );
81
82
            _onresults( data );
83
        },
84
    };
85
86
    return Search;
87
} );
(-)a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/text-marc.js (+78 lines)
Line 0 Link Here
1
define( [ 'marc-record' ], function( MARC ) {
2
    return {
3
        RecordToText: function( record ) {
4
            var lines = [];
5
            var fields = record.fields();
6
7
            for ( var i = 0; i < fields.length; i++ ) {
8
                var field = fields[i];
9
10
                if ( field.isControlField() ) {
11
                    lines.push( field.tagnumber() + ' ' + field.subfield( '@' ) );
12
                } else {
13
                    var result = [ field.tagnumber() + ' ' ];
14
15
                    result.push( field.indicator(0) == ' ' ? '_' : field.indicator(0), ' ' );
16
                    result.push( field.indicator(1) == ' ' ? '_' : field.indicator(1), ' ' );
17
18
                    $.each( field.subfields(), function( i, subfield ) {
19
                        result.push( '$' + subfield[0] + ' ' + subfield[1] );
20
                    } );
21
22
                    lines.push( result.join('') );
23
                }
24
            }
25
26
            return lines.join('\n');
27
        },
28
29
        TextToRecord: function( text ) {
30
            var record = new MARC.Record();
31
            var errors = [];
32
33
            $.each( text.split('\n'), function( i, line ) {
34
                var tagNumber = line.match( /^([A-Za-z0-9]{3}) / );
35
36
                if ( !tagNumber ) {
37
                    errors.push( { type: 'noTag', line: i } );
38
                    return;
39
                }
40
                tagNumber = tagNumber[1];
41
42
                if ( tagNumber < '010' ) {
43
                    var field = new MARC.Field( tagNumber, ' ', ' ', [ [ '@', line.substring( 4 ) ] ] );
44
                    field.sourceLine = i;
45
                    record.addField( field );
46
                } else {
47
                    var indicators = line.match( /^... ([0-9A-Za-z_]) ([0-9A-Za-z_])/ );
48
                    if ( !indicators ) {
49
                        errors.push( { type: 'noIndicators', line: i } );
50
                        return;
51
                    }
52
53
                    var field = new MARC.Field( tagNumber, ( indicators[1] == '_' ? ' ' : indicators[1] ), ( indicators[2] == '_' ? ' ' : indicators[2] ), [] );
54
55
                    var matcher = /[$|ǂ‡]([a-z0-9%]) /g;
56
                    var match;
57
58
                    var subfields = [];
59
60
                    while ( ( match = matcher.exec(line) ) ) {
61
                        subfields.push( { code: match[1], ch: match.index } );
62
                    }
63
64
                    $.each( subfields, function( i, subfield ) {
65
                        var next = subfields[ i + 1 ];
66
67
                        field.addSubfield( [ subfield.code, line.substring( subfield.ch + 3, next ? next.ch : line.length ) ] );
68
                    } );
69
70
                    field.sourceLine = i;
71
                    record.addField( field );
72
                }
73
            } );
74
75
            return errors.length ? { errors: errors } : record;
76
        }
77
    };
78
} );
(-)a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/widget.js (+169 lines)
Line 0 Link Here
1
define( function() {
2
    var Widget = {
3
        Base: {
4
            // Marker utils
5
            clearToText: function() {
6
                var range = this.mark.find();
7
                this.mark.doc.replaceRange( this.text, range.from, range.to, 'marcAware' );
8
            },
9
10
            reCreate: function() {
11
                this.postCreate( this.node, this.mark );
12
            },
13
14
            // Fixed field utils
15
            bindFixed: function( sel, start, end ) {
16
                var $node = $( this.node ).find( sel );
17
                $node.val( this.getFixed( start, end ) );
18
19
                $node.change( $.proxy( function() {
20
                    this.setFixed( start, end, $node.val(), '+input' );
21
                }, this ) );
22
            },
23
24
            getFixed: function( start, end ) {
25
                return this.text.substring( start, end );
26
            },
27
28
            setFixed: function( start, end, value, source ) {
29
                this.setText( this.text.substring( 0, start ) + this.padString( value.toString().substr( 0, end - start ), end - start ) + this.text.substring( end ), source );
30
            },
31
32
            setText: function( text, source ) {
33
                if ( source == '+input' ) this.mark.doc.cm.addLineClass( this.mark.find().from.line, 'wrapper', 'modified-line' );
34
                this.text = text;
35
                this.editor.startNotify();
36
            },
37
38
            // Template utils
39
            insertTemplate: function( sel ) {
40
                var wsOnly = /^\s*$/;
41
                $( sel ).contents().clone().each( function() {
42
                    if ( this.nodeType == Node.TEXT_NODE ) {
43
                        this.data = this.data.replace( /^\s+|\s+$/g, '' );
44
                    }
45
                } ).appendTo( this.node );
46
            },
47
48
            padNum: function( number, length ) {
49
                var result = number.toString();
50
51
                while ( result.length < length ) result = '0' + result;
52
53
                return result;
54
            },
55
56
            padString: function( result, length ) {
57
                while ( result.length < length ) result = ' ' + result;
58
59
                return result;
60
            }
61
        },
62
63
        ActivateAt: function( editor, cur, idx ) {
64
            var marks = editor.findMarksAt( cur );
65
            if ( !marks.length ) return false;
66
67
            var $input = $(marks[0].widget.node).find('input, select').eq(idx || 0);
68
            if ( !$input.length ) return false;
69
70
            $input.focus();
71
            return true;
72
        },
73
74
        Notify: function( editor ) {
75
            $.each( editor.cm.getAllMarks(), function( undef, mark ) {
76
                if ( mark.widget && mark.widget.notify ) mark.widget.notify();
77
            } );
78
        },
79
80
        UpdateLine: function( editor, line ) {
81
            var info = editor.getLineInfo( { line: line, ch: 0 } );
82
            var lineh = editor.cm.getLineHandle( line );
83
            if ( !lineh ) return;
84
85
            if ( !info ) {
86
                if ( lineh.markedSpans ) {
87
                    $.each( lineh.markedSpans, function ( undef, span ) {
88
                        var mark = span.marker;
89
                        if ( !mark.widget ) return;
90
91
                        mark.widget.clearToText();
92
                    } );
93
                }
94
                return;
95
            }
96
97
            var subfields = [];
98
99
            var end = editor.cm.getLine( line ).length;
100
            if ( info.tagNumber < '010' ) {
101
                if ( end >= 4 ) subfields.push( { code: '@', from: 4, to: end } );
102
            } else {
103
                for ( var i = 0; i < info.subfields.length; i++ ) {
104
                    var next = ( i < info.subfields.length - 1 ) ? info.subfields[i + 1].ch : end;
105
                    subfields.push( { code: info.subfields[i].code, from: info.subfields[i].ch + 3, to: next } );
106
                }
107
            }
108
109
            $.each( subfields, function ( undef, subfield ) {
110
                var id = info.tagNumber + subfield.code;
111
                var marks = editor.cm.findMarksAt( { line: line, ch: subfield.from } );
112
113
                if ( marks.length ) {
114
                    if ( marks[0].id == id ) {
115
                        return;
116
                    } else {
117
                        marks[0].widget.clearToText();
118
                    }
119
                }
120
121
                if ( !editorWidgets[id] ) return;
122
                var fullBase = $.extend( Object.create( Widget.Base ), editorWidgets[id] );
123
                var widget = Object.create( fullBase );
124
125
                if ( subfield.from == subfield.to ) {
126
                    editor.cm.replaceRange( widget.makeTemplate ? widget.makeTemplate() : '<empty>', { line: line, ch: subfield.from }, null, 'marcWidgetPrefill' );
127
                    return; // We'll do the actual work when the change event is triggered again
128
                }
129
130
                var text = editor.cm.getRange( { line: line, ch: subfield.from }, { line: line, ch: subfield.to } );
131
132
                widget.text = text;
133
                var node = widget.init();
134
135
                var mark = editor.cm.markText( { line: line, ch: subfield.from }, { line: line, ch: subfield.to }, {
136
                    inclusiveLeft: false,
137
                    inclusiveRight: false,
138
                    replacedWith: node,
139
                } );
140
141
                mark.id = id;
142
                mark.widget = widget;
143
144
                widget.node = node;
145
                widget.mark = mark;
146
                widget.editor = editor;
147
148
                if ( widget.postCreate ) {
149
                    widget.postCreate();
150
                    mark.changed();
151
                }
152
153
                var $lastInput = $(widget.node).find('input, select').eq(-1);
154
                if ( $lastInput.length ) {
155
                    $lastInput.bind( 'keypress', 'tab', function() {
156
                        var cur = editor.cm.getCursor();
157
                        editor.cm.setCursor( { line: cur.line } );
158
                        // FIXME: ugly hack
159
                        editor.cm.options.extraKeys.Tab( editor.cm );
160
                        editor.focus();
161
                        return false;
162
                    } );
163
                }
164
            } );
165
        },
166
    };
167
168
    return Widget;
169
} );
(-)a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/xslt.js (+68 lines)
Line 0 Link Here
1
define( function() {
2
    var XSLT = {
3
        TransformToFragment: function( xmlDoc, xslDoc ) {
4
            if ( window.XSLTProcessor ) {
5
                var proc = new XSLTProcessor();
6
                proc.importStylesheet( xslDoc );
7
                proc.setParameter( null, 'showAvailability', false );
8
                return (new XMLSerializer).serializeToString( proc.transformToFragment( xmlDoc, document ) );
9
            } else if ( window.ActiveXObject ) {
10
                var xslt = new ActiveXObject( "Msxml2.XSLTemplate" );
11
                xslt.stylesheet = xslDoc;
12
                var xslProc = xslt.createProcessor();
13
                xslProc.input = xmlDoc;
14
                xslProc.addParameter( 'showAvailability', false );
15
                xslProc.transform();
16
                return xslProc.output;
17
            } else {
18
                return null;
19
            }
20
        },
21
22
        Get: ( function() {
23
            // Horrible browser hack, but required due to the following hard-to-detect and long-unfixed bug:
24
            // https://bugs.webkit.org/show_bug.cgi?id=60276
25
            var isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
26
            var isSafari = /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor);
27
28
            if ( !isChrome && !isSafari ) return $.get;
29
30
            return function( url ) {
31
                var result = new jQuery.Deferred();
32
                var basepath = url.match( /(.*\/)*/ )[0];
33
34
                $.get( url ).done( function( xslDoc ) {
35
                    var xslImports = xslDoc.getElementsByTagNameNS( 'http://www.w3.org/1999/XSL/Transform', 'import' );
36
                    var importsRemaining = xslImports.length;
37
38
                    if ( importsRemaining == 0 ) {
39
                        result.resolve( xslDoc );
40
                        return;
41
                    }
42
43
                    $.each( xslImports, function( i, importElem ) {
44
                        var path = $( importElem ).attr( 'href' );
45
                        if ( !/^(\/|https?:)/.test( path ) ) path = basepath + path;
46
47
                        XSLT.Get( path ).done( function( subDoc ) {
48
                            importsRemaining--;
49
                            $( importElem ).replaceWith( subDoc.documentElement.childNodes );
50
51
                            if ( importsRemaining == 0 ) result.resolve( xslDoc );
52
                        } ).fail( function() {
53
                            importsRemaining = -1;
54
55
                            result.reject();
56
                        } );
57
                    } );
58
                } ).fail( function() {
59
                    result.reject();
60
                } );
61
62
                return result;
63
            };
64
        } )(),
65
    };
66
67
    return XSLT;
68
} );
(-)a/koha-tmpl/intranet-tmpl/prog/en/css/cateditor.css (+332 lines)
Line 0 Link Here
1
/*> Infrastructure */
2
body {
3
    padding: 0;
4
}
5
6
#loading {
7
    background-color: #FFF;
8
    cursor: wait;
9
    height: 100%;
10
    left: 0;
11
    opacity: .7;
12
    position: fixed;
13
    top: 0;
14
    width: 100%;
15
    z-index: 1000;
16
}
17
18
#loading div {
19
    background : transparent url(../../img/loading.gif) top left no-repeat;
20
    font-size : 175%;
21
    font-weight: bold;
22
    height: 2em;
23
    left: 50%;
24
    margin: -1em 0 0 -2.5em;
25
    padding-left : 50px;
26
    position: absolute;
27
    top: 50%;
28
    width: 15em;
29
}
30
31
/*> MARC editor */
32
#editor .CodeMirror {
33
    line-height: 1.2;
34
}
35
36
.cm-tagnumber {
37
    color: #080;
38
    font-weight: bold;
39
}
40
41
.cm-bad-tagnumber {
42
    color: #A20;
43
    font-weight: bold;
44
}
45
46
.cm-indicator {
47
    color: #884;
48
}
49
50
.cm-subfieldcode {
51
    background-color: #F4F4F4;
52
    color: #187848;
53
    border-radius: 3px 8px 8px 3px;
54
    border-right: 2px solid white;
55
    font-weight: bold;
56
    margin-right: -2px;
57
}
58
59
.cm-bad-subfieldcode {
60
    background-color: #FFD9D9;
61
    color: #482828;
62
    border-radius: 3px 8px 8px 3px;
63
    font-weight: bold;
64
}
65
66
#editor .modified-line-gutter {
67
    width: 10px;
68
}
69
70
#editor .modified-line {
71
    background: #F8F8F8;
72
    border-left: 5px solid black;
73
    margin-left: -10px;
74
    padding-left: 5px;
75
}
76
77
#editor .CodeMirror-gutters {
78
    background: transparent;
79
    border-right: none;
80
}
81
82
/*> MARC editor widgets */
83
84
#editor .subfield-widget {
85
    color: #538200;
86
    border: solid 2px #538200;
87
    border-radius: 6px;
88
    font-family: inherit;
89
    line-height: 2.75;
90
    margin: 3px 0;
91
    padding: 4px;
92
}
93
94
#editor .subfield-widget select {
95
    height: 1.5em;
96
    vertical-align: middle;
97
}
98
99
#editor .subfield-widget select:focus {
100
    outline: 1px #83A230 solid;
101
}
102
103
#editor .fixed-widget select {
104
    width: 3em;
105
}
106
107
#editor .hidden-widget {
108
    color: #999999;
109
    border: solid 2px #AAAAAA;
110
    line-height: 2;
111
    padding: 2px;
112
}
113
114
.structure-error {
115
    background: #FFEEEE;
116
    font-size: 0.9em;
117
    line-height: 1.5;
118
    margin: .5em;
119
    padding: 0 .5em;
120
}
121
122
.structure-error i {
123
    vertical-align: text-bottom;
124
}
125
126
#statusbar {
127
    background-color: #F4F8F9;
128
    border: solid 2px #b9d8d9;
129
    border-bottom-style: none;
130
    border-radius: 6px 6px 0 0;
131
    height: 18px;
132
    margin-bottom: -32px;
133
    overflow: auto;
134
    padding: 4px;
135
    padding-bottom: 0;
136
}
137
138
#statusbar #status-tag-info, #statusbar #status-subfield-info {
139
    float: left;
140
    overflow: hidden;
141
    padding-right: 2%;
142
    width: 48%;
143
}
144
145
#record-info .label {
146
    float: none;
147
}
148
149
#record-info .label + span {
150
    display: block;
151
    padding-left: 1em;
152
}
153
154
/*> Search */
155
156
#advanced-search-ui, #search-results-ui, #macro-ui {
157
    background-color: white;
158
    border: solid 2px #444;
159
    border-radius: 6px 6px 6px 6px;
160
    height: 80%;
161
    overflow: auto;
162
    padding: 10px;
163
    width: 90%;
164
}
165
166
#quicksearch input, #quicksearch a {
167
    font-size: 1.2em;
168
    padding: 3px 0;
169
    width: 96%; // I have no idea why this is necessary
170
}
171
172
#show-advanced-search {
173
    display: block;
174
    margin-top: .3em;
175
}
176
177
#advanced-search-fields {
178
    margin: 0;
179
    padding: 0;
180
}
181
182
#advanced-search-fields li {
183
    display: inline-block;
184
    list-style-type: none;
185
}
186
187
#advanced-search-fields label {
188
    display: inline-block;
189
    font-weight: bold;
190
    padding: 1em 1em 1em 0;
191
    width: 10em;
192
    text-align: right;
193
}
194
195
#advanced-search-fields input {
196
    display: inline-block;
197
    margin: 0px auto;
198
    width: 14em;
199
}
200
201
.icon-loading {
202
    display: inline-block;
203
    height: 16px;
204
    width: 16px;
205
    background: transparent url("../../img/spinner-small.gif") top left no-repeat;
206
    padding: -1px;
207
    vertical-align: text-top;
208
}
209
210
/*> Search results */
211
212
#searchresults table {
213
    width: 100%;
214
}
215
216
.sourcecol {
217
    width: 50px;
218
}
219
220
.results-info {
221
    height: 100px;
222
    overflow: auto;
223
}
224
225
.toolscol {
226
    padding: 0;
227
    width: 100px;
228
}
229
230
.toolscol ul {
231
    margin: 0;
232
    padding: 0;
233
}
234
235
#searchresults .toolscol li {
236
    list-style-type: none;
237
    list-style-image: none;
238
}
239
240
.toolscol a {
241
    border-bottom: 1px solid #BCBCBC;
242
    display: block;
243
    padding: 0 1em;
244
    line-height: 24px;
245
}
246
247
.results-marc {
248
    font-family: monospace;
249
    height: auto;
250
    white-space: pre-wrap;
251
}
252
253
#searchresults {
254
    position: relative;
255
}
256
257
#search-overlay {
258
    background: white;
259
    bottom: 0;
260
    font-size: 2em;
261
    left: 0;
262
    opacity: .7;
263
    padding: 2em;
264
    position: absolute;
265
    right: 0;
266
    text-align: center;
267
    top: 0;
268
    z-index: 9001;
269
}
270
271
/*> Macros */
272
273
#macro-ui .CodeMirror {
274
    height: 400px;
275
    width: 100%;
276
}
277
278
#macro-save-message {
279
    color: #666;
280
    font-size: 13px;
281
    float: right;
282
    line-height: 26px;
283
}
284
285
#macro-list > li {
286
    border: 2px solid #F0F0F0;
287
    border-radius: 6px;
288
    display: block;
289
    font-size: 115%;
290
}
291
292
#macro-list > li + li {
293
    margin-top: -2px;
294
}
295
296
#macro-list .active {
297
    background: #EDF4F6;
298
    border-color: none;
299
}
300
301
#macro-list a {
302
    display: block;
303
    padding: 6px;
304
}
305
306
#macro-list a:focus {
307
    outline: none;
308
}
309
310
.macro-info {
311
    background-color: #F4F4F4;
312
    display: none;
313
    margin: 0;
314
    padding: 10px;
315
    text-align: right;
316
}
317
318
.macro-info li {
319
    color: #666;
320
    font-size: 75%;
321
    list-style-type: none;
322
}
323
324
.macro-info .label {
325
    clear: left;
326
    font-weight: bold;
327
    float: left;
328
}
329
330
#macro-list .active .macro-info {
331
    display: block;
332
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc (+804 lines)
Line 0 Link Here
1
<script src="/intranet-tmpl/lib/codemirror/codemirror.js"></script>
2
<script src="/intranet-tmpl/lib/codemirror/lib/runmode.js"></script>
3
<script src="/intranet-tmpl/lib/filesaver.js"></script>
4
<script src="/intranet-tmpl/lib/koha/cateditor/marc-mode.js"></script>
5
<script src="/intranet-tmpl/lib/require.js"></script>
6
<script src="/intranet-tmpl/lib/jquery/plugins/jquery.lightbox_me.js"></script>
7
<script>
8
require.config( {
9
    baseUrl: '/intranet-tmpl/lib/koha/cateditor/',
10
    paths: {
11
        pz2: '../../pz2',
12
    },
13
    shim: {
14
        pz2: { exports: 'pz2' },
15
    },
16
} );
17
</script>
18
19
[% IF marcflavour == 'MARC21' %]
20
[% PROCESS 'cateditor-widgets-marc21.inc' %]
21
[% ELSE %]
22
<script>var editorWidgets = {};</script>
23
[% END %]
24
25
<script>
26
require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'text-marc', 'widget', 'xslt' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, TextMARC, Widget, XSLT ) {
27
    var xsltResultStylesheets = {
28
        [% FOREACH stylesheet IN xslt_result_stylesheets %]
29
        '[% stylesheet.syntax %]': XSLT.Get( '[% stylesheet.url %]' ),
30
        [% END %]
31
    };
32
33
    var xsltDetailStylesheets = {
34
        [% FOREACH stylesheet IN xslt_detail_stylesheets %]
35
        '[% stylesheet.syntax %]': XSLT.Get( '[% stylesheet.url %]' ),
36
        [% END %]
37
    };
38
39
    var z3950Targets = {
40
        [% FOREACH target = z3950_targets %]
41
            '[% target.host %]:[% target.port %]/[% target.db %]': {
42
                'name': '[% target.name %]',
43
                'authentication': '[% target.userid %]:[% target.password %]',
44
                'syntax': '[% target.syntax %]',
45
                'kohasyntax': '[% target.syntax == 'USMARC' ? 'MARC21' : target.syntax %]',
46
                'encoding': '[% target.encoding %]',
47
                'checked': [% target.checked ? 'true' : 'false' %],
48
            },
49
        [% END %]
50
    };
51
52
    // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
53
    var z3950Labels = [
54
		[ "md-work-title", "Title" ],
55
		[ "md-series-title", "Series-title" ],
56
		[ "md-work-author", "Author" ],
57
		[ "md-lccn", "LCCN" ],
58
		[ "md-isbn", "ISBN" ],
59
		[ "md-issn", "ISSN" ],
60
		[ "md-medium", "Medium" ],
61
		[ "md-edition", "Edition" ],
62
		[ "md-description", "Description" ],
63
    ];
64
65
    var state = {
66
        backend: '',
67
        saveBackend: 'catalog',
68
        recordID: undefined
69
    };
70
71
    var editor;
72
    var macroEditor;
73
74
    function makeAuthorisedValueWidgets( frameworkCode ) {
75
        $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
76
            $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
77
                if ( !subfieldInfo.authorised_value ) return;
78
79
                var authorisedWidget = {
80
                    init: function() {
81
                        var $result = $( '<span class="subfield-widget"></span>' );
82
83
                        return $result[0];
84
                    },
85
                    postCreate: function() {
86
                        this.setText( subfieldInfo.defaultvalue );
87
88
                        $( '<select></select>' ).appendTo( this.node );
89
                        var $node = $( this.node ).find( 'select' );
90
                        $.each( KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value ), function( undef, authval ) {
91
                            $node.append( '<option value="' + authval.value + '">' + authval.lib + '</option>' );
92
                        } );
93
                        $node.val( this.text );
94
95
                        $node.change( $.proxy( function() {
96
                            this.setText( $node.val() );
97
                        }, this ) );
98
                    }
99
                };
100
101
                editorWidgets[ tag + subfield ] = authorisedWidget;
102
            } );
103
        } );
104
    }
105
106
    function bindGlobalKeys() {
107
        function bindKey( key, handler ) {
108
            $( document ).bind( 'keydown', key, handler );
109
            $( document ).bind( 'keypress', key, handler );
110
            $( '#sidebar input' ).bind( 'keydown', key, handler );
111
            $( '#editor textarea' ).bind( 'keydown', key, handler );
112
        }
113
114
        shortcut.add( 'ctrl+s', function(event) {
115
            $( '#save-record' ).click();
116
117
            event.preventDefault();
118
        } );
119
120
        shortcut.add( 'alt+ctrl+k', function(event) {
121
            $( '#search-by-keywords' ).focus();
122
123
            return false;
124
        } );
125
126
        shortcut.add( 'alt+ctrl+a', function(event) {
127
            $( '#search-by-author' ).focus();
128
129
            return false;
130
        } );
131
132
        shortcut.add( 'alt+ctrl+i', function(event) {
133
            $( '#search-by-isbn' ).focus();
134
135
            return false;
136
        } );
137
138
        shortcut.add( 'alt+ctrl+t', function(event) {
139
            $( '#search-by-title' ).focus();
140
141
            return false;
142
        } );
143
144
        $('#quicksearch .search-box').each( function() {
145
            shortcut.add( 'enter', $.proxy( function() {
146
                var q = this.value;
147
                if (!q) return false;
148
149
                showResultsBox();
150
                Search.Start( z3950Targets, $(this).data('qualifier') + q, 20 );
151
152
                return false;
153
            }, this), { target: this, type: 'keypress' } );
154
        } );
155
    }
156
157
    // Record loading
158
    var backends = {
159
       'new': {
160
            recordLabel: _("new record"),
161
            get: function( id, callback ) {
162
                record = new MARC.Record();
163
                KohaBackend.FillRecord( '', record );
164
165
                callback( record );
166
            },
167
        },
168
        'new-full': {
169
            recordLabel: _("new full record"),
170
            get: function( id, callback ) {
171
                record = new MARC.Record();
172
                KohaBackend.FillRecord( '', record, true );
173
174
                callback( record );
175
            },
176
        },
177
        'catalog': {
178
            recordLabel: _("catalog record #{ID}"),
179
            saveLabel: _("to catalog"),
180
            get: function( id, callback ) {
181
                if ( !id ) return false;
182
183
                KohaBackend.GetRecord( id, callback );
184
            },
185
            save: function( id, record, done ) {
186
                function finishCb( data ) {
187
                    done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
188
                }
189
190
                if ( id ) {
191
                    KohaBackend.SaveRecord( id, record, finishCb );
192
                } else {
193
                    KohaBackend.CreateRecord( record, finishCb );
194
                }
195
            }
196
        },
197
        'iso2709': {
198
            saveLabel: _("to ISO2709 (.mrc) file"),
199
            save: function( id, record, done ) {
200
                saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
201
202
                done( {} );
203
            }
204
        },
205
        'marcxml': {
206
            saveLabel: _("to MARCXML (.xml) file"),
207
            save: function( id, record, done ) {
208
                saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
209
210
                done( {} );
211
            }
212
        },
213
        'search': {
214
            recordLabel: _("search result"),
215
            get: function( id, callback ) {
216
                if ( !id ) return false;
217
218
                Search.GetDetailedRecord( decodeURIComponent(id), callback );
219
            },
220
        },
221
    };
222
223
    function setSource(parts) {
224
        state.backend = parts[0];
225
        state.recordID = parts[1];
226
        state.canSave = backends[ state.backend ].save != null;
227
        state.saveBackend = state.canSave ? state.backend : 'catalog';
228
229
        document.location.hash = '#' + parts[0] + ':' + parts[1];
230
        $( '#title' ).text( _("Editing ") + backends[ state.backend ].recordLabel.replace( '{ID}', parts[1] ) );
231
        $( 'title', document.head ).html( _("Koha &rsaquo; Cataloging &rsaquo; Editing ") + backends[ state.backend ].recordLabel.replace( '{ID}', parts[1] ) );
232
        $( '#save-record span' ).text( _("Save ") + backends[ state.saveBackend ].saveLabel );
233
    }
234
235
    function saveRecord( recid, editor, callback ) {
236
        var parts = recid.split(':');
237
        if ( parts.length != 2 ) return false;
238
239
        if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
240
241
        editor.removeErrors();
242
        var record = editor.getRecord();
243
244
        if ( record.errors ) {
245
            state.saving = false;
246
            callback( { error: 'syntax', errors: record.errors } );
247
            return;
248
        }
249
250
        var errors = KohaBackend.ValidateRecord( '', record );
251
        if ( errors.length ) {
252
            state.saving = false;
253
            callback( { error: 'invalid', errors: errors } );
254
            return;
255
        }
256
257
        backends[ parts[0] ].save( parts[1], record, function(data) {
258
            state.saving = false;
259
260
            if (data.newRecord) {
261
                var record = new MARC.Record();
262
                record.loadMARCXML(data.newRecord);
263
                editor.displayRecord( record );
264
            }
265
266
            if (data.newId) {
267
                setSource(data.newId);
268
                } else {
269
                setSource( [ state.backend, state.recordID ] );
270
            }
271
272
            if (callback) callback( data );
273
        } );
274
    }
275
276
    function loadRecord( recid, editor, callback ) {
277
        var parts = recid.split(':');
278
        if ( parts.length != 2 ) return false;
279
280
        if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
281
282
        backends[ parts[0] ].get( parts[1], function( record ) {
283
            editor.displayRecord( record );
284
            editor.focus();
285
286
            if (callback) callback(record);
287
        } );
288
289
        return true;
290
    }
291
292
    function openRecord( recid, editor, callback ) {
293
        return loadRecord( recid, editor, function ( record ) {
294
            setSource( recid.split(':') );
295
296
            if (callback) callback( record );
297
        } );
298
    }
299
300
    // Search functions
301
    function showAdvancedSearch() {
302
        $('#advanced-search-ui').lightbox_me();
303
    }
304
305
    function startAdvancedSearch() {
306
        var search = '';
307
308
        $('#advanced-search-ui input').each( function() {
309
            if (!this.value) return;
310
            if (search) search += ' and ';
311
            search += $(this).data('qualifier') + this.value;
312
        } );
313
314
        if (!search) return;
315
316
        $('#advanced-search-ui').trigger('close');
317
        showResultsBox();
318
        Search.Start( z3950Targets, search, 20 );
319
    }
320
321
    function showResultsBox(data) {
322
        $('#searchresults thead tr').empty();
323
        $('#searchresults tbody').empty();
324
        $('#search-targetsinfo').empty().append('<li>' + _("Loading...") + '</li>');
325
        $('#search-results-ui').lightbox_me();
326
    }
327
328
    function showDetailedResult( hit, $tr, fetchOnly ) {
329
        Search.GetDetailedRecord( hit.recid, function( record ) {
330
            if ( fetchOnly ) return;
331
332
            xsltResultStylesheets[ z3950Targets[ hit.location[0]['@id'] ].kohasyntax ].done( function( xslDoc ) {
333
                $tr.find( '.results-info' ).html( XSLT.TransformToFragment( record.xmlSource, xslDoc ) );
334
            } );
335
        } );
336
    }
337
338
    function showSearchResults( editor, data ) {
339
        $('#searchresults thead tr').empty();
340
        $('#searchresults tbody').empty();
341
342
        var seenColumns = {};
343
344
        $.each( data.hits, function( undef, hit ) {
345
            for ( key in hit ) {
346
                if ( /^md-/.test(key) ) seenColumns[key] = true;
347
            }
348
349
            $.each( hit.location, function( undef, location ) {
350
                for ( key in location ) {
351
                    if ( /^md-/.test(key) ) seenColumns[key] = true;
352
                }
353
            } );
354
        } );
355
356
        $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
357
358
        $.each( z3950Labels, function( undef, label ) {
359
            if ( seenColumns[ label[0] ] ) {
360
                $('#searchresults thead tr').append( '<th>' + label[1] + '</th>' );
361
            }
362
        } );
363
364
        $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
365
366
        $.each( data.hits, function( undef, hit ) {
367
            var result = '<tr>';
368
            result += '<td class="sourcecol">' + hit.location[0]['@name'] + '</td>';
369
370
            $.each( z3950Labels, function( undef, label ) {
371
                if ( !seenColumns[ label[0] ] ) return;
372
373
                if ( hit[ label[0] ] ) {
374
                    result += '<td class="infocol">' + hit[ label[0] ].join('<br/>') + '</td>';
375
                } else if ( hit.location[0][ label[0] ] ) {
376
                    result += '<td class="infocol">' + hit.location[0][ label[0] ].join('<br/>') + '</td>';
377
                } else {
378
                    result += '<td class="infocol">&nbsp;</td>';
379
                }
380
            } );
381
382
            result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
383
            result += '<li><a href="#" class="open-link">' + _("Import") + '</a></li>';
384
            if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
385
            // REMOVE: (vim syntax highlighting bug) "</a></a>"
386
            result += '</ul></td></tr>';
387
388
            var $tr = $( result );
389
            $tr.find( '.marc-link' ).click( function() {
390
                Search.GetDetailedRecord( hit.recid, function( record ) {
391
                    var $columns = $tr.find( '.infocol' );
392
                    CodeMirror.runMode( TextMARC.RecordToText( record ), 'marc', $( '<td class="infocol results-marc" colspan="' + $columns.length + '"></td>' ).replaceAll( $columns.slice(1).remove().end()[0] )[0] );
393
                } );
394
395
                return false;
396
            } );
397
            $tr.find( '.open-link' ).click( function() {
398
                $( '#search-results-ui' ).trigger( 'close' );
399
                openRecord( hit.id, editor );
400
401
                return false;
402
            } );
403
            $tr.find( '.substitute-link' ).click( function() {
404
                $( '#search-results-ui' ).trigger( 'close' );
405
                loadRecord( hit.id, editor );
406
407
                return false;
408
            } );
409
            $('#searchresults tbody').append( $tr );
410
411
            //showDetailedResult( hit, $tr, !!data.activeclients );
412
        } );
413
414
        var $overlay = $('#search-overlay');
415
        $overlay.find('span').text(_("Loading"));
416
        $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedTargets.length ) + '%' } );
417
418
        if ( data.activeclients ) {
419
            $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedTargets.length ) + '%' } );
420
            $overlay.show();
421
        } else {
422
            $overlay.find('.bar').css( { display: 'block', width: '100%' } );
423
            $overlay.fadeOut();
424
        }
425
    }
426
427
    function invalidateSearchResults() {
428
        var $overlay = $('#search-overlay');
429
        $overlay.find('span').text(_("Search expired, please try again"));
430
        $overlay.find('.bar').css( { display: 'none' } );
431
        $overlay.show();
432
    }
433
434
    function showSearchTargets(data) {
435
        $('#search-targetsinfo').empty();
436
437
        $.each( data, function( undef, target ) {
438
            $('#search-targetsinfo').append( '<li>' + target.name + ' (' + target.hits + ')' + '</li>' );
439
        } );
440
    }
441
442
    function handleSearchError(error) {
443
        if (error.code == 1) {
444
            invalidateSearchResults();
445
            Search.Reconnect();
446
        } else {
447
            humanMsg.displayMsg( _("<h3>Internal search error</h3>") + '<p>' + error + '</p>' + _("<p>Please <b>refresh</b> the page and try again."), { className: 'humanError' } );
448
        }
449
    }
450
451
    // Preference functions
452
    function showPreference( pref ) {
453
        var value = Preferences.user[pref];
454
455
        switch (pref) {
456
            case 'fieldWidgets':
457
                $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
458
                break;
459
            case 'font':
460
                $( '#editor .CodeMirror' ).css( { fontFamily: value } );
461
                editor.refresh();
462
                break;
463
            case 'fontSize':
464
                $( '#editor .CodeMirror' ).css( { fontSize: value } );
465
                editor.refresh();
466
                break;
467
            case 'macros':
468
                showSavedMacros();
469
                break;
470
        }
471
    }
472
473
    function bindPreference( editor, pref ) {
474
        function _addHandler( sel, event, handler ) {
475
            $( sel ).on( event, function (e) {
476
                e.preventDefault();
477
                handler( e, Preferences.user[pref] );
478
                Preferences.Save( [% USER_INFO.0.borrowernumber %] );
479
                showPreference(pref);
480
            } );
481
        }
482
483
        switch (pref) {
484
            case 'fieldWidgets':
485
                _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
486
                    editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
487
                } );
488
                break;
489
            case 'font':
490
                _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
491
                    Preferences.user.font = $( e.target ).css( 'font-family' );
492
                } );
493
                break;
494
            case 'fontSize':
495
                _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
496
                    Preferences.user.fontSize = $( e.target ).css( 'font-size' );
497
                } );
498
                break;
499
        }
500
    }
501
502
    function displayPreferences( editor ) {
503
        $.each( Preferences.user, function( pref, value ) {
504
            showPreference( pref );
505
            bindPreference( editor, pref );
506
        } );
507
    }
508
509
    //> Macro functions
510
    function loadMacro( name ) {
511
        $( '#macro-list li' ).removeClass( 'active' );
512
        macroEditor.activeMacro = name;
513
514
        if ( !name ) {
515
            macroEditor.setValue( '' );
516
            return;
517
        }
518
519
        $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
520
        var macro = Preferences.user.macros[name];
521
        macroEditor.setValue( macro.contents );
522
        if ( macro.history ) macroEditor.setHistory( macro.history );
523
    }
524
525
    function storeMacro( name, macro ) {
526
        if ( macro ) {
527
            Preferences.user.macros[name] = macro;
528
        } else {
529
            delete Preferences.user.macros[name];
530
        }
531
532
        Preferences.Save( [% USER_INFO.0.borrowernumber %] );
533
    }
534
535
    function showSavedMacros( macros ) {
536
        var scrollTop = $('#macro-list').scrollTop();
537
        $( '#macro-list' ).empty();
538
        var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
539
            return $.extend( { name: name }, macro );
540
        } );
541
        macro_list.sort( function( a, b ) {
542
            return a.name.localeCompare(b.name);
543
        } );
544
        $.each( macro_list, function( undef, macro ) {
545
            var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
546
            $li.click( function() {
547
                loadMacro(macro.name);
548
                return false;
549
            } );
550
            if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
551
            var modified = macro.modified && new Date(macro.modified);
552
            $li.find( '.macro-info' ).append(
553
                '<li><span class="label">' + _("Last changed:") + '</span>' +
554
                ( modified ? modified.toLocaleFormat() : _("never") ) + '</li>'
555
            );
556
            $('#macro-list').append($li);
557
        } );
558
        var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
559
        $new_li.click( function() {
560
            // TODO: make this a bit less retro
561
            var name = prompt(_("Please enter the name for the new macro:"));
562
            if (!name) return;
563
564
            if ( !Preferences.user.macros[name] ) storeMacro( name, { contents: "" } );
565
            showSavedMacros();
566
            loadMacro( name );
567
        } );
568
        $('#macro-list').append($new_li);
569
        $('#macro-list').scrollTop(scrollTop);
570
    }
571
572
    function saveMacro() {
573
        var name = macroEditor.activeMacro;
574
575
        if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
576
577
        macroEditor.savedGeneration = macroEditor.changeGeneration();
578
        storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory() } );
579
        $('#macro-save-message').text(_("Saved"));
580
        showSavedMacros();
581
    }
582
583
    $(document).ready( function() {
584
        // Editor setup
585
        editor = new MARCEditor( function (elt) { $(elt).insertAfter('#toolbar') } );
586
587
        macroEditor = CodeMirror(
588
            $('#macro-editor')[0],
589
            {
590
                mode: 'null',
591
                lineNumbers: true,
592
            }
593
        );
594
595
        var resizeTimer = null;
596
        $( window ).resize( function() {
597
            if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
598
                resizeTimer = null;
599
600
                var pos = $('#editor .CodeMirror').position();
601
                $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 );
602
            }, 100);
603
        } ).resize();
604
605
        var saveableBackends = [];
606
        $.each( backends, function( id, backend ) {
607
            if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
608
        } );
609
        saveableBackends.sort();
610
        $.each( saveableBackends, function( undef, backend ) {
611
            $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + _("Save ") + backend[0] + '</a></li>' );
612
        } );
613
614
        // Click bindings
615
        $( '#save-record, #save-dropdown a' ).click( function() {
616
            $( '#save-record' ).find('i').attr( 'class', 'icon-loading' ).siblings( 'span' ).text( _("Saving...") );
617
618
            function finishCb(result) {
619
                if ( result.error == 'syntax' ) {
620
                    humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
621
                } else if ( result.error == 'invalid' ) {
622
                    humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
623
                } else if ( !result.error ) {
624
                    humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
625
                }
626
627
                $.each( result.errors || [], function( undef, error ) {
628
                    switch ( error.type ) {
629
                        case 'noTag':
630
                            editor.addError( error.line, _("Invalid tag number") );
631
                            break;
632
                        case 'noIndicators':
633
                            editor.addError( error.line, _("Invalid indicators") );
634
                            break;
635
                        case 'missingTag':
636
                            editor.addError( null, _("Missing mandatory tag: ") + error.tag );
637
                            break;
638
                        case 'missingSubfield':
639
                            if ( error.subfield == '@' ) {
640
                                editor.addError( error.line, _("Missing control field contents") );
641
                            } else {
642
                                editor.addError( error.line, _("Missing mandatory subfield: $") + error.subfield );
643
                            }
644
                            break;
645
                        case 'unrepeatableTag':
646
                            editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
647
                            break;
648
                        case 'unrepeatableSubfield':
649
                            editor.addError( error.line, _("Subfield $") + error.subfield + _(" cannot be repeated") );
650
                            break;
651
                    }
652
                } );
653
654
                $( '#save-record' ).find('i').attr( 'class', 'icon-hdd' );
655
656
                if ( result.error ) {
657
                    // Reset backend info
658
                    setSource( [ state.backend, state.recordID ] );
659
                }
660
            }
661
662
            var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
663
            if ( state.backend == backend ) {
664
                saveRecord( backend + ':' + state.recordID, editor, finishCb );
665
            } else {
666
                saveRecord( backend + ':', editor, finishCb );
667
            }
668
669
            return false;
670
        } );
671
672
        $('#import-records').click( function() {
673
            $('#import-records-input')
674
                .off('change')
675
                .change( function() {
676
                    if ( !this.files || !this.files.length ) return;
677
678
                    var file = this.files[0];
679
                    var reader = new FileReader();
680
681
                    reader.onload = function() {
682
                        var record = new MARC.Record();
683
684
                        if ( /\.mrc$/.test( file.name ) ) {
685
                            record.loadISO2709( reader.result );
686
                        } else if ( /\.xml$/.test( file.name ) ) {
687
                            record.loadMARCXML( reader.result );
688
                        } else {
689
                            humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
690
                            return;
691
                        }
692
693
                       editor.displayRecord( record );
694
                    };
695
696
                    reader.readAsText( file );
697
                } )
698
                .click();
699
700
            return false;
701
        } );
702
703
        $('#open-macros').click( function() {
704
            $('#macro-ui').lightbox_me();
705
706
            return false;
707
        } );
708
709
        $('#run-macro').click( function() {
710
            var result = Macros.Run( editor, macroEditor.getValue() );
711
712
            if ( !result.errors.length ) return false;
713
714
            var errors = [];
715
            $.each( result.errors, function() {
716
                var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
717
718
                switch ( this.error ) {
719
                    case 'failed': error += _("failed to run"); break;
720
                    case 'unrecognized': error += _("unrecognized command"); break;
721
                }
722
723
                errors.push(error);
724
            } );
725
726
            humanMsg.displayMsg( _("<h3>Failed to run macro:</h3>") + '<ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
727
728
            return false;
729
        } );
730
731
        $('#delete-macro').click( function() {
732
            if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
733
734
            storeMacro( macroEditor.activeMacro, undefined );
735
            showSavedMacros();
736
            loadMacro( undefined );
737
738
            return false;
739
        } );
740
741
        var saveTimeout;
742
        macroEditor.on( 'change', function( cm, change ) {
743
            $('#macro-save-message').empty();
744
            if ( change.origin == 'setValue' ) return;
745
746
            if ( saveTimeout ) clearTimeout( saveTimeout );
747
            saveTimeout = setTimeout( function() {
748
                saveMacro();
749
750
                saveTimeout = null;
751
            }, 500 );
752
        } );
753
754
        $( '#switch-editor' ).click( function() {
755
            if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
756
757
            $.cookie( 'catalogue_editor_[% USER_INFO.0.borrowernumber %]', 'basic', { expires: 365, path: '/' } );
758
759
            if ( state.backend == 'catalog' ) {
760
                window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
761
            } else if ( state.backend == 'new' ) {
762
                window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
763
            } else {
764
                humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
765
            }
766
        } );
767
768
        $( '#show-advanced-search' ).click( function() {
769
            showAdvancedSearch();
770
771
            return false;
772
        } );
773
774
        $('#advanced-search').submit( function() {
775
            startAdvancedSearch();
776
777
            return false;
778
        } );
779
780
        // Key bindings
781
        bindGlobalKeys();
782
783
        // Start editor
784
        Preferences.Load( [% USER_INFO.0.borrowernumber %] );
785
        displayPreferences(editor);
786
        makeAuthorisedValueWidgets( '' );
787
        Search.Init( z3950Targets, { onresults: function(data) { showSearchResults( editor, data ) }, onbytarget: showSearchTargets, onerror: handleSearchError } );
788
789
        function finishCb() {
790
            $("#loading").hide();
791
            editor.focus();
792
        }
793
794
        if ( "[% auth_forwarded_hash %]" ) {
795
            document.location.hash = "[% auth_forwarded_hash %]";
796
        }
797
798
        if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
799
            openRecord( 'new:', editor, finishCb );
800
        }
801
    } );
802
} )();
803
804
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-widgets-marc21.inc (+155 lines)
Line 0 Link Here
1
<div id="editor-widget-templates" style="display:none">
2
    <div id="widget-leader">
3
        Leader:&nbsp;<span title="Record length (autogenerated)">#####</span>
4
        <select name="f5" title="Record status">
5
            <option value="a">a - Increase in encoding level</option>
6
            <option value="c">c - Corrected or revised</option>
7
            <option value="d">d - Deleted</option>
8
            <option value="n">n - New</option>
9
            <option value="p">p - Increase in encoding level from prepublication</option>
10
        </select>
11
        <select name="f6" title="Type of record">
12
            <option value="a">a - Language material</option>
13
            <option value="c">c - Notated music</option>
14
            <option value="d">d - Manuscript notated music</option>
15
            <option value="e">e - Cartographic material</option>
16
            <option value="f">f - Manuscript cartographic material</option>
17
            <option value="g">g - Projected medium</option>
18
            <option value="i">i - Nonmusical sound recording</option>
19
            <option value="j">j - Musical sound recording</option>
20
            <option value="k">k - Two-dimensional nonprojectable graphic</option>
21
            <option value="m">m - Computer file</option>
22
            <option value="o">o - Kit</option>
23
            <option value="p">p - Mixed materials</option>
24
            <option value="r">r - Three-dimensional artifact or naturally occurring object</option>
25
            <option value="t">t - Manuscript language material</option>
26
        </select>
27
        <select name="f7" title="Bibliographic level">
28
            <option value="a">a - Monographic component part</option>
29
            <option value="b">b - Serial component part</option>
30
            <option value="c">c - Collection</option>
31
            <option value="d">d - Subunit</option>
32
            <option value="i">i - Integrating resource</option>
33
            <option value="m">m - Monograph/item</option>
34
            <option value="s">s - Serial</option>
35
        </select>
36
        <select name="f8" title="Type of control">
37
                <option value=" ">_ - No specific type</option>
38
                <option value="a">a - Archival</option>
39
        </select>
40
        <span title="Encoding (forced Unicode)">a</span>
41
        <span title="Indicator/subfield lengths">22</span>
42
        <span title="Data base address (autogenerated)">#####</span>
43
        <select name="f17" title="Encoding level">
44
            <option value=" ">_ - Full level</option>
45
            <option value="1">1 - Full level, material not examined</option>
46
            <option value="2">2 - Less-than-full level, material not examined</option>
47
            <option value="3">3 - Abbreviated level</option>
48
            <option value="4">4 - Core level</option>
49
            <option value="5">5 - Partial (preliminary) level</option>
50
            <option value="7">7 - Minimal level</option>
51
            <option value="8">8 - Prepublication level</option>
52
            <option value="u">u - Unknown</option>
53
            <option value="z">z - Not applicable</option>
54
        </select>
55
        <select name="f18" title="Descriptive cataloging form">
56
            <option value=" ">_ - Non-ISBD</option>
57
            <option value="a">a - AACR 2</option>
58
            <option value="c">c - ISBD punctuation omitted</option>
59
            <option value="i">i - ISBD punctuation included</option>
60
            <option value="u">u - Unknown</option>
61
        </select>
62
        <select name="f19" title="Multipart record resource level">
63
            <option value=" ">_ - Not specified or not applicable</option>
64
            <option value="a">a - Set</option>
65
            <option value="b">b - Part with independent title</option>
66
            <option value="c">c - Part with dependent title</option>
67
        </select>
68
        <span title="Length of directory elements">4500</span>
69
    </div>
70
</div>
71
72
<script>
73
74
/**
75
 * Each widget should provide one to three methods:
76
 *   init( text ): Returns the DOM node for this widget.
77
 *   postCreate( node, mark ): Optional, called once the mark has been created
78
 *                             and the node shown. Bind event handlers here.
79
 *   makeTemplate(): Optional, should return some sane default contents for a
80
 *                   newly created field/subfield. '<empty>' will be used if this
81
 *                   method is unset.
82
 *
83
 * Following the Koha convention, control fields are defined as tags with a
84
 * single subfield, '@'.
85
 */
86
87
var editorWidgets = {
88
    '000@': {
89
        makeTemplate: function() {
90
            return '     nam a22     7a 4500';
91
        },
92
        init: function() {
93
            var $result = $( '<span class="subfield-widget fixed-widget"></span>' );
94
95
            return $result[0];
96
        },
97
        postCreate: function() {
98
            // Clear the length and directory start fields; these are unnecessary for MARCXML and will be filled in upon USMARC export
99
            this.setFixed( 0, 5, '     ' );
100
            this.setFixed( 9, 17, 'a22     ' );
101
            this.setFixed( 20, 24, '4500' );
102
103
            this.insertTemplate( '#widget-leader' );
104
105
            this.bindFixed( '[name=f5]', 5, 6 );
106
            this.bindFixed( '[name=f6]', 6, 7 );
107
            this.bindFixed( '[name=f7]', 7, 8 );
108
            this.bindFixed( '[name=f8]', 8, 9 );
109
            this.bindFixed( '[name=f17]', 17, 18 );
110
            this.bindFixed( '[name=f18]', 18, 19 );
111
            this.bindFixed( '[name=f19]', 19, 20 );
112
        },
113
    },
114
    '005@': {
115
        init: function() {
116
            var $result = $( '<span class="subfield-widget fixed-widget">Updated: </span>' );
117
118
            return $result[0];
119
        },
120
        postCreate: function( node, mark ) {
121
            var parts = this.text.match( /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d)/ );
122
123
            if ( parts ) {
124
                var dateVal = new Date(
125
                    parseInt( parts[1] ), // Year
126
                    parseInt( parts[2] ) - 1, // Month (0-11)
127
                    parseInt( parts[3] ), // Day
128
                    parseInt( parts[4] ), // Hour
129
                    parseInt( parts[5] ), // Minute
130
                    parseInt( parts[6] ), // Second
131
                    parseInt( parts[7] ) * 100 // Millisecond
132
                );
133
134
                $( this.node ).append( dateVal.toLocaleString() );
135
            } else {
136
                $( this.node ).append( '<span class="hint">unset</span>' );
137
            }
138
        }
139
    },
140
    '008@': {
141
        makeTemplate: function() {
142
            var now = new Date();
143
            return this.padNum( now.getYear() % 100, 2 ) + this.padNum( now.getMonth() + 1, 2 ) + this.padNum( now.getDate(), 2 ) + "b        xxu||||| |||| 00| 0 [% DefaultLanguageField008 %] d";
144
        },
145
        init: function() {
146
            var $result = $( '<span class="subfield-widget fixed-widget">Fixed data: <span class="hint">under construction</span></span>' );
147
148
            return $result[0];
149
        },
150
        postCreate: function( node, mark ) {
151
        }
152
    }
153
};
154
155
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt (+15 lines)
Lines 74-79 Link Here
74
            return false;
74
            return false;
75
        });
75
        });
76
76
77
        $( '#switcheditor' ).click( function() {
78
            if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
79
80
            $.cookie( 'catalogue_editor_[% USER_INFO.0.borrowernumber %]', 'advanced', { expires: 365, path: '/' } );
81
82
            var biblionumber = [% biblionumber || "null" %];
83
84
            if ( biblionumber ) {
85
                window.location = '/cgi-bin/koha/cataloguing/editor.pl#catalog:' + biblionumber;
86
            } else {
87
                window.location = '/cgi-bin/koha/cataloguing/editor.pl';
88
            }
89
        } );
90
77
	});
91
	});
78
92
79
function redirect(dest){
93
function redirect(dest){
Lines 402-407 function Changefwk(FwkList) { Link Here
402
416
403
    [% UNLESS (circborrowernumber) %][%# Hide in fast cataloging %]
417
    [% UNLESS (circborrowernumber) %][%# Hide in fast cataloging %]
404
        <div class="btn-group"><a class="btn btn-small" href="#" id="z3950search"><i class="icon-search"></i> Z39.50 search</a></div>
418
        <div class="btn-group"><a class="btn btn-small" href="#" id="z3950search"><i class="icon-search"></i> Z39.50 search</a></div>
419
        <div class="btn-group"><a href="#" id="switcheditor" class="btn btn-small">Switch to advanced editor</a></div>
405
        [% IF (biblionumber) %]
420
        [% IF (biblionumber) %]
406
            [% IF ( BiblioDefaultViewmarc ) %]
421
            [% IF ( BiblioDefaultViewmarc ) %]
407
                <div class="btn-group">
422
                <div class="btn-group">
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/editor.tt (+201 lines)
Line 0 Link Here
1
[% INCLUDE 'doc-head-open.inc' %]
2
<title>Koha &rsaquo; Cataloging &rsaquo; Editor</title>
3
[% INCLUDE 'doc-head-close.inc' %]
4
<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.fixFloat.js"></script>
5
<link type="text/css" rel="stylesheet" href="[% themelang %]/css/cateditor.css" />
6
<link type="text/css" rel="stylesheet" href="/intranet-tmpl/lib/codemirror/codemirror.css" />
7
<link rel="stylesheet" type="text/css" href="[% themelang %]/css/humanmsg.css" />
8
<script src="[% themelang %]/lib/jquery/plugins/humanmsg.js" type="text/javascript"></script>
9
[% IF ( bidi ) %]
10
   <link rel="stylesheet" type="text/css" href="[% themelang %]/css/right-to-left.css" />
11
[% END %]
12
</head>
13
<body id="cat_addbiblio" class="cat">
14
15
   <div id="loading">
16
       <div>Loading, please wait...</div>
17
   </div>
18
19
[% INCLUDE 'header.inc' %]
20
21
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/cataloguing/addbooks.pl">Cataloging</a> &rsaquo; Editor</div>
22
23
<div id="doc3" class="yui-t2">
24
<div id="bd">
25
26
<h1 id="title">Cataloging editor</h1>
27
28
<div id="yui-main"><div class="yui-b">
29
30
<div id="editor">
31
    <input id="import-records-input" type="file" style="display: none">
32
    <div id="toolbar" class="btn-toolbar">
33
        <div class="btn-group">
34
            <button class="btn btn-small" id="save-record" title="Save current record (Ctrl-S)"><i class="icon-hdd"></i> <span>Save</span></button>
35
            <button class="btn btn-small dropdown-toggle" data-toggle="dropdown">
36
            <span class="caret"></span>
37
            </button>
38
            <ul class="dropdown-menu" id="save-dropdown">
39
            </ul>
40
        </div>
41
        <button class="btn btn-small" id="import-records" title="Import an ISO2709 or MARCXML record"><i class="icon-upload"></i> <span>Import record...</span></button>
42
        <button class="btn btn-small" id="open-macros" title="Run and edit macros"><i class="icon-play"></i> <span>Macros...</span></button>
43
        <div class="btn-group">
44
            <button class="btn btn-small dropdown-toggle" data-toggle="dropdown"><i class="icon-cog"></i> Settings <span class="caret"></span></button>
45
            <ul id="prefs-menu" class="dropdown-menu">
46
                <li><a id="switch-editor" href="#">Switch to basic editor</a></li>
47
                <li><a id="set-field-widgets" href="#"></a></li>
48
                <li class="divider"></li>
49
                <li><a class="set-fontSize" style="font-size: .92em" href="#">Small text</a></li>
50
                <li><a class="set-fontSize" style="font-size: 1em" href="#">Normal text</a></li>
51
                <li><a class="set-fontSize" style="font-size: 1.08em" href="#">Large text</a></li>
52
                <li><a class="set-fontSize" style="font-size: 1.18em" href="#">Huge text</a></li>
53
                <li class="divider"></li>
54
                <li><a class="set-font" style="font-family: monospace" href="#">Default font</a></li>
55
                <li><a class="set-font" style="font-family: 'Courier New'" href="#">Courier New</a></li>
56
                <li><a class="set-font" style="font-family: peep" href="#">peep</a></li>
57
            </ul>
58
        </div>
59
    </div>
60
    [%# CodeMirror instance will be inserted here %]
61
    <div id="statusbar">
62
        <div id="status-tag-info">
63
        </div>
64
        <div id="status-subfield-info">
65
        </div>
66
    </div>
67
</div>
68
69
</div></div>
70
71
<div class="yui-b" id="sidebar">
72
73
<h3>Search</h3>
74
<form id="quicksearch">
75
    <fieldset class="brief">
76
    <ol>
77
        <li><label for="search-by-keywords">Keywords:</label></li>
78
        <li><input class="search-box" data-qualifier="term=" id="search-by-keywords" placeholder="(Ctrl-Alt-K)" /></li>
79
        <li><label for="search-by-author">Author:</label></li>
80
        <li><input class="search-box" data-qualifier="Author-name=" id="search-by-author" placeholder="(Ctrl-Alt-A)" /></li>
81
        <li><label for="search-by-isbn">ISBN:</label></li>
82
        <li><input class="search-box" data-qualifier="Identifier-ISBN=" id="search-by-isbn" placeholder="(Ctrl-Alt-I)" /></li>
83
        <li><label for="search-by-title">Title:</label></li>
84
        <li><input class="search-box" data-qualifier="Title=" id="search-by-title" placeholder="(Ctrl-Alt-T)" /></li>
85
        <li><a href="#" id="show-advanced-search" title="Show advanced search (Ctrl-Alt-S)">Advanced &raquo;</a></li>
86
    </fieldset>
87
</form>
88
89
</div>
90
91
</div>
92
</div>
93
94
<div id="advanced-search-ui" style="display: none">
95
96
<h1>Advanced search</h1>
97
98
<form id="advanced-search">
99
    <div id="toolbar" class="btn-toolbar">
100
        <button class="btn btn-small" type="submit"><i class="icon-search"></i> <span>Search</span></button>
101
        <button class="btn btn-small" type="reset"><i class="icon-remove"></i> <span>Clear</span></button>
102
    </div>
103
    <ul id="advanced-search-fields">
104
        <li>
105
            <label for="advanced-search-by-author">Author:</label>
106
            <input class="search-box" data-qualifier="Author-name=" id="advanced-search-by-author" />
107
        </li>
108
        <li>
109
            <label for="advanced-search-by-control-number">Control number:</label>
110
            <input class="search-box" data-qualifier="Local-number=" id="advanced-search-by-control-number" />
111
        </li>
112
        <li>
113
            <label for="advanced-search-by-dewey">Dewey number:</label>
114
            <input class="search-box" data-qualifier="Classification-Dewey=" id="advanced-search-by-dewey" />
115
        </li>
116
        <li>
117
            <label for="advanced-search-by-isbn">ISBN:</label>
118
            <input class="search-box" data-qualifier="Identifier-ISBN=" id="advanced-search-by-isbn" />
119
        </li>
120
        <li>
121
            <label for="advanced-search-by-issn">ISSN:</label>
122
            <input class="search-box" data-qualifier="Identifier-ISSN=" id="advanced-search-by-issn" />
123
        </li>
124
        <li>
125
            <label for="advanced-search-by-lccn">LCCN:</label>
126
            <input class="search-box" data-qualifier="LC-card-number=" id="advanced-search-by-lccn" />
127
        </li>
128
        <li>
129
            <label for="advanced-search-by-lc-number">LC call number:</label>
130
            <input class="search-box" data-qualifier="Classification-LC=" id="advanced-search-by-lc-number" />
131
        </li>
132
        <li>
133
            <label for="advanced-search-by-publisher-number">Publisher number:</label>
134
            <input class="search-box" data-qualifier="Identifier-publisher-for-music=" id="advanced-search-by-publisher-number" />
135
        </li>
136
        <li>
137
            <label for="advanced-search-by-standard-number">Standard number:</label>
138
            <input class="search-box" data-qualifier="Identifier-standard=" id="advanced-search-by-standard-number" />
139
        </li>
140
        <li>
141
            <label for="advanced-search-by-subject">Subject:</label>
142
            <input class="search-box" data-qualifier="Subject=" id="advanced-search-by-subject" />
143
        </li>
144
        <li>
145
            <label for="advanced-search-by-publication-date">Publication date:</label>
146
            <input class="search-box" data-qualifier="Date=" id="advanced-search-by-publication-date" />
147
        </li>
148
        <li>
149
            <label for="advanced-search-by-title">Title:</label>
150
            <input class="search-box" data-qualifier="Title=" id="advanced-search-by-title" />
151
        </li>
152
    </ul>
153
</form>
154
155
</div>
156
157
<div id="search-results-ui" style="display: none">
158
159
<h1>Results</h1>
160
<div class="yui-gf">
161
    <div class="yui-u first">
162
        <div id="search-facets">
163
            <ul>
164
                <li>Targets:<ul id="search-targetsinfo"></ul></li>
165
            </ul>
166
        </div>
167
    </div>
168
    <div class="yui-u">
169
        <div id="searchresults">
170
            <table>
171
                <thead>
172
                    <tr></tr>
173
                </thead>
174
                <tbody></tbody>
175
            </table>
176
            <div id="search-overlay"><span>Loading...</span><div class="progress progress-striped active"><div class="bar" style="width: 0"></div></div></div>
177
        </div>
178
    </div>
179
</div>
180
181
</div>
182
183
<div id="macro-ui" style="display: none">
184
185
<h1>Macros</h1>
186
<div class="yui-gf">
187
    <div class="yui-u first"><ul id="macro-list"></ul></div>
188
    <div class="yui-u" id="macro-editor">
189
        <div id="macro-toolbar" class="btn-toolbar">
190
            <button class="btn btn-small" id="run-macro"itle="Run and edit macros"><i class="icon-play"></i> Run macro</button>
191
            <button class="btn btn-small" id="delete-macro" title="Delete macro"><i class="icon-remove"></i> Delete macro</button>
192
            <div id="macro-save-message"></div>
193
        </div>
194
    </div>
195
</div>
196
197
</div>
198
199
[% PROCESS 'cateditor-ui.inc' %]
200
201
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/svc/cataloguing/framework (-1 / +73 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
use Modern::Perl '2009';
4
5
use CGI;
6
use C4::Branch;
7
use C4::ClassSource;
8
use C4::Context;
9
use C4::Biblio;
10
use C4::Service;
11
12
my ( $query, $response ) = C4::Service->init( editcatalogue => 'edit_catalogue' );
13
14
my ( $frameworkcode ) = C4::Service->require_params( 'frameworkcode' );
15
16
my $tagslib = GetMarcStructure( 1, $frameworkcode );
17
18
my @tags;
19
20
foreach my $tag ( sort keys %$tagslib ) {
21
    my $taglib = $tagslib->{$tag};
22
    my $taginfo = { map { $_, $taglib->{$_} } grep { length $_ > 1 } keys %$taglib };
23
    $taginfo->{subfields} = [ map { [ $_, $taglib->{$_} ] } grep { length $_ == 1 } sort keys %$taglib ];
24
25
    push @tags, [ $tag, $taginfo ];
26
}
27
28
my $dbh = C4::Context->dbh;
29
my $authorised_values = {};
30
31
$authorised_values->{branches} = [];
32
my $onlymine=C4::Context->preference('IndependentBranches') &&
33
        C4::Context->userenv &&
34
        C4::Context->userenv->{flags} % 2 == 0 &&
35
        C4::Context->userenv->{branch};
36
my $branches = GetBranches($onlymine);
37
foreach my $thisbranch ( sort keys %$branches ) {
38
    push @{ $authorised_values->{branches} }, { value => $thisbranch, lib => $branches->{$thisbranch}->{'branchname'} };
39
}
40
41
$authorised_values->{itemtypes} = $dbh->selectall_arrayref( q{
42
    SELECT itemtype AS value, description AS lib FROM itemtypes ORDER BY description
43
}, { Slice => {} } );
44
45
my $class_sources = GetClassSources();
46
47
my $default_source = C4::Context->preference("DefaultClassificationSource");
48
49
foreach my $class_source (sort keys %$class_sources) {
50
    next unless $class_sources->{$class_source}->{'used'} or
51
                ($class_source eq $default_source);
52
    push @{ $authorised_values->{cn_source} }, { value => $class_source, lib => $class_sources->{$class_source}->{'description'} };
53
}
54
55
my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
56
my $auth_query = "SELECT category, authorised_value, lib
57
            FROM authorised_values";
58
$auth_query .= qq{ LEFT JOIN authorised_values_branches ON ( id = av_id )} if $branch_limit;
59
$auth_query .= " AND ( branchcode = ? OR branchcode IS NULL )" if $branch_limit;
60
$auth_query .= " GROUP BY lib ORDER BY lib, lib_opac";
61
my $authorised_values_sth = $dbh->prepare( $auth_query );
62
$authorised_values_sth->execute(
63
    $branch_limit ? $branch_limit : (),
64
);
65
66
while ( my ( $category, $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
67
    $authorised_values->{$category} ||= [];
68
    push @{ $authorised_values->{$category} }, { value => $lib, lib => $lib };
69
}
70
71
$response->param( framework => \@tags, authorised_values => $authorised_values );
72
73
C4::Service->return_success( $response );

Return to bug 11559