Bugzilla – Attachment 4956 Details for
Bug 6707
Add alternative, textual MARC editor
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 6707 - Add textual MARC editor
Bug-6707---Add-textual-MARC-editor.patch (text/plain), 228.15 KB, created by
Jesse Weaver
on 2011-08-11 06:50:45 UTC
(
hide
)
Description:
Bug 6707 - Add textual MARC editor
Filename:
MIME Type:
Creator:
Jesse Weaver
Created:
2011-08-11 06:50:45 UTC
Size:
228.15 KB
patch
obsolete
>From 69f0a1c5e5d9b283a2d851e6069acdec025bf5bf Mon Sep 17 00:00:00 2001 >From: Jesse Weaver <pianohacker@gmail.com> >Date: Thu, 15 Jul 2010 15:44:50 -0600 >Subject: [PATCH] Bug 6707 - Add textual MARC editor > >Add an alternate MARC editor, which allows entering biblios in a textual >format. This allows a faster workflow for experienced catalogers. > >Possible improvements: > * Remove untranslateable strings from code > * Replace parser with simpler version, if possible > * Remove ugly redirect-hack > >Known problems: > * Does not support authorities. Adding this would be quite complicated. > * Also not authorized values. Would be tough, but doable. >--- > C4/Biblio.pm | 253 +++++ > C4/Koha.pm | 18 +- > cataloguing/addbiblio-text.pl | 635 +++++++++++ > cataloguing/addbiblio.pl | 6 +- > cataloguing/framework-jsonp.pl | 60 + > installer/data/mysql/en/mandatory/sysprefs.sql | 1 + > installer/data/mysql/updatedatabase.pl | 7 + > .../intranet-tmpl/prog/en/css/staff-global.css | 5 + > .../prog/en/includes/doc-head-close.inc | 3 + > koha-tmpl/intranet-tmpl/prog/en/js/marc.js | 194 ++++ > .../prog/en/js/pages/addbiblio-text.js | 82 ++ > .../intranet-tmpl/prog/en/lib/codemirror/LICENSE | 23 + > .../prog/en/lib/codemirror/css/csscolors.css | 47 + > .../prog/en/lib/codemirror/css/docs.css | 42 + > .../prog/en/lib/codemirror/css/jscolors.css | 55 + > .../prog/en/lib/codemirror/css/marccolors.css | 24 + > .../prog/en/lib/codemirror/css/people.jpg | Bin 0 -> 14122 bytes > .../prog/en/lib/codemirror/css/sparqlcolors.css | 39 + > .../prog/en/lib/codemirror/css/xmlcolors.css | 51 + > .../prog/en/lib/codemirror/js/codemirror.js | 219 ++++ > .../prog/en/lib/codemirror/js/editor.js | 1176 ++++++++++++++++++++ > .../prog/en/lib/codemirror/js/mirrorframe.js | 81 ++ > .../prog/en/lib/codemirror/js/parsecss.js | 155 +++ > .../prog/en/lib/codemirror/js/parsehtmlmixed.js | 73 ++ > .../prog/en/lib/codemirror/js/parsejavascript.js | 322 ++++++ > .../prog/en/lib/codemirror/js/parsemarc.js | 102 ++ > .../prog/en/lib/codemirror/js/parsesparql.js | 162 +++ > .../prog/en/lib/codemirror/js/parsexml.js | 286 +++++ > .../prog/en/lib/codemirror/js/select.js | 584 ++++++++++ > .../prog/en/lib/codemirror/js/stringstream.js | 131 +++ > .../prog/en/lib/codemirror/js/tokenize.js | 57 + > .../en/lib/codemirror/js/tokenizejavascript.js | 176 +++ > .../prog/en/lib/codemirror/js/undo.js | 388 +++++++ > .../prog/en/lib/codemirror/js/util.js | 123 ++ > .../en/modules/admin/preferences/cataloguing.pref | 7 + > .../prog/en/modules/cataloguing/addbiblio-text.tt | 155 +++ > 36 files changed, 5740 insertions(+), 2 deletions(-) > create mode 100755 cataloguing/addbiblio-text.pl > create mode 100755 cataloguing/framework-jsonp.pl > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/js/marc.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/js/pages/addbiblio-text.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/LICENSE > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/csscolors.css > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/docs.css > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/jscolors.css > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/marccolors.css > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/people.jpg > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/sparqlcolors.css > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/xmlcolors.css > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/codemirror.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/editor.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/mirrorframe.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsecss.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsehtmlmixed.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsejavascript.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsemarc.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsesparql.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsexml.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/select.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/stringstream.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/tokenize.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/tokenizejavascript.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/undo.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/util.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio-text.tt > >diff --git a/C4/Biblio.pm b/C4/Biblio.pm >index 96baaef..1734c43 100644 >--- a/C4/Biblio.pm >+++ b/C4/Biblio.pm >@@ -126,6 +126,7 @@ BEGIN { > &TransformHtmlToMarc2 > &TransformHtmlToMarc > &TransformHtmlToXml >+ &TransformTextToMarc > &PrepareItemrecordDisplay > &GetNoZebraIndexes > ); >@@ -2097,6 +2098,258 @@ sub TransformHtmlToMarc { > return $record; > } > >+=item TransformTextToMarc >+ >+ $record = TransformTextToMarc($text[, existing_record => $existing_record, debug => $debug]); >+ >+Parses a textual representation of MARC data into a MARC::Record. If an error >+occurs, will die(); this can be caught with eval { ... } if ($@) { ... } >+ >+$text should be a series of lines with the following format: >+ >+Control fields: 005 20080303040352.1 >+Data fields: 245 10 $a The $1,000,000 problem / $c Robert Biggs. >+ >+Indicators are optional. Subfields are delimited by | or $, and both of these >+characters are allowed in subfield contents as long as they are not followed by >+a number/digit and a space. >+ >+If $existing_record is defined as a MARC::Record, TransformTextToMarc will place >+parsed fields into it and return it, rather than creating a new MARC::Record. >+ >+If $debug is true, then the parser will output very verbose debugging >+information to stdout. >+ >+=cut >+ >+sub TransformTextToMarc { >+ # A non-deterministic-finite-state-machine based parser for a textual MARC >+ # format. >+ # >+ # Allowable contents of tag numbers, indicators and subfield codes are >+ # based on the MARCXML standard. >+ # >+ # While this is a mostly conventional FSM, it has two major peculiarities: >+ # * A buffer, separate from the current character, that is manually added >+ # to by each state. >+ # * Two methods of transitioning between states; jumping, which preserves >+ # the buffer, and switching, which does not. >+ >+ our ($text, %options) = @_; >+ >+ %options = (( >+ existing_record => MARC::Record->new(), >+ debug => 0, >+ strip_whitespace => 1, >+ ), %options); >+ >+ my $record = $options{'existing_record'}; >+ >+ $text =~ s/(\r\n)|\r/\n/g; >+ >+ our $state = 'start'; >+ our $last_state = ''; >+ our $char = ''; >+ our $line = 1; >+ >+ our $field = undef; >+ our $buffer = ''; >+ our $tag = ''; >+ our $indicator = ''; >+ our $subfield_code = ''; >+ >+ my %states = ( >+ start => sub { >+ # Start of line. All buffers are empty. >+ if ($char =~ /[0-9]/) { >+ $buffer .= $char; >+ jump_state('tag_id'); >+ } elsif ($char ne "\n") { >+ error("expected MARC tag number at start of line, got '$char'"); >+ } >+ }, >+ tag_id => sub { >+ # Jumped to from start, so buffer has first character of tag >+ # Allows letters in second and third digits of tag number >+ if (length($buffer) < 3) { >+ if ($char =~ /[0-9a-zA-Z]/) { >+ $buffer .= $char; >+ } else { >+ error("expected digit or letter, got '$char' in tag number"); >+ } >+ } elsif ($char eq ' ') { >+ $tag = $buffer; >+ if ($tag =~ /^00/) { >+ set_state('control_field_content'); >+ } else { >+ set_state('indicator'); >+ } >+ } else { >+ error("expected whitespace after tag number, got '$char'"); >+ } >+ }, >+ indicator => sub { >+ # Parses optional indicator, composed of digits or lowercase letters >+ # Will consume leading $ or | of subfield if no indicator; otherwise >+ # expecting_subfield will do so >+ if (length($buffer) == 0) { >+ if ($char =~ /[\$\|]/) { >+ $indicator = ' '; >+ set_state('expecting_subfield_code'); >+ } elsif ($char =~ /[0-9a-z_ ]/) { >+ $buffer .= $char; >+ } else { >+ error("expected either subfield or indicator after tag number, got '$char'"); >+ } >+ } elsif (length($buffer) < 2) { >+ if ($char =~ /[0-9a-z_ ]/) { >+ $buffer .= $char; >+ } else { >+ error("expected digit, letter or blank in indicator, got '$char'"); >+ } >+ } elsif ($char eq ' ') { >+ $indicator = $buffer; >+ $indicator =~ s/_/ /g; >+ set_state('expecting_subfield'); >+ } else { >+ error("expected space after indicator, got '$char'"); >+ } >+ }, >+ expecting_subfield => sub { >+ if ($char =~ /[\$\|]/) { >+ set_state('expecting_subfield_code'); >+ } else { >+ error("expected \$ or | after indicator or tag number, got '$char'"); >+ } >+ }, >+ expecting_subfield_code => sub { >+ if ($char =~ /[a-z0-9]/) { >+ $subfield_code = $char; >+ set_state('expecting_subfield_space'); >+ } else { >+ error("expected number or letter in subfield code, got '$char'"); >+ } >+ }, >+ expecting_subfield_space => sub { >+ if ($char eq ' ') { >+ set_state('subfield_content'); >+ } else { >+ error("expected space after subfield code, got '$char'"); >+ } >+ }, >+ control_field_content => sub { >+ if ($char eq "\n") { >+ if ($tag eq '000') { >+ $record->leader($buffer); >+ } else { >+ $record->append_fields(MARC::Field->new($tag, $buffer)); >+ } >+ $tag = ''; >+ set_state('start'); >+ } else { >+ $buffer .= $char; >+ } >+ }, >+ subfield_content => sub { >+ # Handles both additional subfields and inserting last subfield >+ if ($char =~ /[\$\|]/) { >+ $buffer .= $char; >+ jump_state('subfield_code'); >+ } elsif ($char eq "\n") { >+ $buffer =~ s/(^\s+|\s+$)//g if ($options{'strip_whitespace'}); >+ if ($field) { >+ $field->add_subfields($subfield_code, $buffer); >+ } else { >+ $field = MARC::Field->new($tag, substr($indicator, 0, 1), substr($indicator, 1), $subfield_code, $buffer); >+ } >+ $record->append_fields($field); >+ >+ undef $field; >+ $tag = ''; >+ $line++; >+ >+ set_state('start'); >+ } else { >+ $buffer .= $char; >+ } >+ }, >+ # subfield_code and subfield_space both jump to subfield_content if >+ # they do not find the expected format, allowing strings like >+ # '245 $a The meaning of the $ sign' and '020 $a ... $c $10.00' to >+ # parse correctly >+ subfield_code => sub { >+ $buffer .= $char; >+ >+ if ($char =~ /[a-z0-9]/) { >+ jump_state('subfield_space'); >+ } elsif ($char eq "\n") { >+ error("Unexpected newline in subfield code"); >+ } else { >+ jump_state('subfield_content'); >+ } >+ }, >+ subfield_space => sub { >+ # This has to do some manipulation of the buffer to ensure that the >+ # ending '$[a-z0-9] ' does not get inserted into the subfield >+ # contents >+ if ($char eq ' ') { >+ my $contents = substr($buffer, 0, -3); >+ $contents =~ s/(^\s+|\s+$)//g if ($options{'strip_whitespace'}); >+ if ($field) { >+ $field->add_subfields($subfield_code, $contents); >+ } else { >+ $field = MARC::Field->new($tag, substr($indicator, 0, 1), substr($indicator, 1), $subfield_code, $contents); >+ } >+ >+ $subfield_code = substr($buffer, -1); >+ set_state('subfield_content'); >+ } else { >+ $buffer .= $char; >+ jump_state('subfield_content'); >+ } >+ } >+ ); >+ >+ sub set_state { >+ my $new_state = shift; >+ >+ print STDERR "$state -> $new_state (buffer was '$buffer'[" . length($buffer) . "])\n" if ($options{'debug'}); >+ >+ $buffer = ''; >+ $last_state = $state; >+ $state = $new_state; >+ } >+ >+ sub jump_state { >+ my $new_state = shift; >+ >+ print STDERR "$state -- $new_state (buffer is '$buffer'[" . length($buffer) . "])\n" if ($options{'debug'}); >+ >+ $last_state = $state; >+ $state = $new_state; >+ } >+ >+ sub error { >+ my $text = shift; >+ $text =~ s/\n/newline/gm; >+ >+ die "Error on line $line: $text\n"; >+ } >+ >+ for $char (split '', $text) { >+ print STDERR "running $state with " . ($char eq "\n" ? "line-break" : "'$char'") . " and buffer '$buffer' (" . length($buffer) . " chars)\n" if ($options{'debug'} >= 2); >+ $states{$state}->(); >+ } >+ >+ if ($char ne "\n") { >+ print STDERR "running $state at end\n" if ($options{'debug'}); >+ $char = "\n"; >+ $states{$state}->(); >+ } >+ >+ return $record; >+} >+ > # cache inverted MARC field map > our $inverted_field_map; > >diff --git a/C4/Koha.pm b/C4/Koha.pm >index 06b2ec5..0b03773 100644 >--- a/C4/Koha.pm >+++ b/C4/Koha.pm >@@ -38,7 +38,7 @@ BEGIN { > &slashifyDate > &subfield_is_koha_internal_p > &GetPrinters &GetPrinter >- &GetItemTypes &getitemtypeinfo >+ &GetItemTypes &GetItemTypeList &getitemtypeinfo > &GetCcodes > &GetSupportName &GetSupportList > &get_itemtypeinfos_of >@@ -253,6 +253,22 @@ sub GetItemTypes { > return ( \%itemtypes ); > } > >+sub GetItemTypeList { >+ my ( $selected ) = @_; >+ my $itemtypes = GetItemTypes; >+ my @itemtypesloop; >+ >+ foreach my $itemtype ( sort { $itemtypes->{$a}->{'description'} cmp $itemtypes->{$b}->{'description'} } keys( %$itemtypes ) ) { >+ push @itemtypesloop, { >+ value => $itemtype, >+ selected => ( $itemtype eq $selected ), >+ description => $itemtypes->{$itemtype}->{'description'}, >+ }; >+ } >+ >+ return \@itemtypesloop; >+} >+ > sub get_itemtypeinfos_of { > my @itemtypes = @_; > >diff --git a/cataloguing/addbiblio-text.pl b/cataloguing/addbiblio-text.pl >new file mode 100755 >index 0000000..a504ad0 >--- /dev/null >+++ b/cataloguing/addbiblio-text.pl >@@ -0,0 +1,635 @@ >+#!/usr/bin/perl >+ >+# Copyright 2008 LibLime >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it under the >+# terms of the GNU General Public License as published by the Free Software >+# Foundation; either version 2 of the License, or (at your option) any later >+# version. >+# >+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY >+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR >+# A PARTICULAR PURPOSE. See the GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License along with >+# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place, >+# Suite 330, Boston, MA 02111-1307 USA >+ >+use strict; >+use CGI; >+use C4::Output qw(:html :ajax); >+use C4::Output::JSONStream; >+use JSON; >+use C4::Auth; >+use C4::Biblio; >+use C4::Search; >+use C4::AuthoritiesMarc; >+use C4::Context; >+use MARC::Record; >+use MARC::Field; >+use C4::Log; >+use C4::Koha; # XXX subfield_is_koha_internal_p >+use C4::Branch; # XXX subfield_is_koha_internal_p >+use C4::ClassSource; >+use C4::ImportBatch; >+use C4::Charset; >+ >+use Date::Calc qw(Today); >+use MARC::File::USMARC; >+use MARC::File::XML; >+ >+if ( C4::Context->preference('marcflavour') eq 'UNIMARC' ) { >+ MARC::File::XML->default_record_format('UNIMARC'); >+} >+ >+our($tagslib,$authorised_values_sth,$is_a_modif,$usedTagsLib,$mandatory_z3950); >+ >+our ($sec, $min, $hour, $mday, $mon, $year, undef, undef, undef) = localtime(time); >+$year +=1900; >+$mon +=1; >+ >+our %creators = ( >+ '000@' => sub { ' nam a22 7a 4500' }, >+ '005@' => sub { sprintf('%4d%02d%02d%02d%02d%02d.0', $year, $mon, $mday, $hour, $min, $sec) }, >+ '008@' => sub { substr($year,2,2) . sprintf("%02d%02d", $mon, $mday) . 't xxu||||| |||| 00| 0 eng d' }, >+); >+ >+=item MARCfindbreeding >+ >+ $record = MARCfindbreeding($breedingid); >+ >+Look up the import record repository for the record with >+record with id $breedingid. If found, returns the decoded >+MARC::Record; otherwise, -1 is returned (FIXME). >+Returns as second parameter the character encoding. >+ >+=cut >+ >+sub MARCfindbreeding { >+ my ( $id ) = @_; >+ my ($marc, $encoding) = GetImportRecordMarc($id); >+ # remove the - in isbn, koha store isbn without any - >+ if ($marc) { >+ my $record = MARC::Record->new_from_usmarc($marc); >+ my ($isbnfield,$isbnsubfield) = GetMarcFromKohaField('biblioitems.isbn',''); >+ if ( $record->field($isbnfield) ) { >+ foreach my $field ( $record->field($isbnfield) ) { >+ foreach my $subfield ( $field->subfield($isbnsubfield) ) { >+ my $newisbn = $field->subfield($isbnsubfield); >+ $newisbn =~ s/-//g; >+ $field->update( $isbnsubfield => $newisbn ); >+ } >+ } >+ } >+ # fix the unimarc 100 coded field (with unicode information) >+ if (C4::Context->preference('marcflavour') eq 'UNIMARC' && $record->subfield(100,'a')) { >+ my $f100a=$record->subfield(100,'a'); >+ my $f100 = $record->field(100); >+ my $f100temp = $f100->as_string; >+ $record->delete_field($f100); >+ if ( length($f100temp) > 28 ) { >+ substr( $f100temp, 26, 2, "50" ); >+ $f100->update( 'a' => $f100temp ); >+ my $f100 = MARC::Field->new( '100', '', '', 'a' => $f100temp ); >+ $record->insert_fields_ordered($f100); >+ } >+ } >+ >+ if ( !defined(ref($record)) ) { >+ return -1; >+ } >+ else { >+ # normalize author : probably UNIMARC specific... >+ if ( C4::Context->preference("z3950NormalizeAuthor") >+ and C4::Context->preference("z3950AuthorAuthFields") ) >+ { >+ my ( $tag, $subfield ) = GetMarcFromKohaField("biblio.author"); >+ >+ # my $summary = C4::Context->preference("z3950authortemplate"); >+ my $auth_fields = >+ C4::Context->preference("z3950AuthorAuthFields"); >+ my @auth_fields = split /,/, $auth_fields; >+ my $field; >+ >+ if ( $record->field($tag) ) { >+ foreach my $tmpfield ( $record->field($tag)->subfields ) { >+ >+ # foreach my $subfieldcode ($tmpfield->subfields){ >+ my $subfieldcode = shift @$tmpfield; >+ my $subfieldvalue = shift @$tmpfield; >+ if ($field) { >+ $field->add_subfields( >+ "$subfieldcode" => $subfieldvalue ) >+ if ( $subfieldcode ne $subfield ); >+ } >+ else { >+ $field = >+ MARC::Field->new( $tag, "", "", >+ $subfieldcode => $subfieldvalue ) >+ if ( $subfieldcode ne $subfield ); >+ } >+ } >+ } >+ $record->delete_field( $record->field($tag) ); >+ foreach my $fieldtag (@auth_fields) { >+ next unless ( $record->field($fieldtag) ); >+ my $lastname = $record->field($fieldtag)->subfield('a'); >+ my $firstname = $record->field($fieldtag)->subfield('b'); >+ my $title = $record->field($fieldtag)->subfield('c'); >+ my $number = $record->field($fieldtag)->subfield('d'); >+ if ($title) { >+ >+# $field->add_subfields("$subfield"=>"[ ".ucfirst($title).ucfirst($firstname)." ".$number." ]"); >+ $field->add_subfields( >+ "$subfield" => ucfirst($title) . " " >+ . ucfirst($firstname) . " " >+ . $number ); >+ } >+ else { >+ >+# $field->add_subfields("$subfield"=>"[ ".ucfirst($firstname).", ".ucfirst($lastname)." ]"); >+ $field->add_subfields( >+ "$subfield" => ucfirst($firstname) . ", " >+ . ucfirst($lastname) ); >+ } >+ } >+ $record->insert_fields_ordered($field); >+ } >+ return $record, $encoding; >+ } >+ } >+ return -1; >+} >+ >+# Borrowed from MARC::Record::JSON, due to its lack of availability on CPAN >+ >+sub MARC::Record::as_json_record_structure { >+ my $self = shift; >+ my $data = { leader => $self->leader }; >+ my @fields; >+ foreach my $field ($self->fields) { >+ my $json_field = { tag => $field->tag }; >+ >+ if ($field->is_control_field) { >+ $json_field->{contents} = $field->data; >+ } else { >+ $json_field->{indicator1} = $field->indicator(1); >+ $json_field->{indicator2} = $field->indicator(2); >+ >+ $json_field->{subfields} = [ $field->subfields ]; >+ } >+ >+ push @fields, $json_field; >+ } >+ >+ $data->{fields} = \@fields; >+ >+ return $data; >+} >+ >+=item GetMandatoryFieldZ3950 >+ >+ This function return an hashref which containts all mandatory field >+ to search with z3950 server. >+ >+=cut >+ >+sub GetMandatoryFieldZ3950($){ >+ my $frameworkcode = shift; >+ my @isbn = GetMarcFromKohaField('biblioitems.isbn',$frameworkcode); >+ my @title = GetMarcFromKohaField('biblio.title',$frameworkcode); >+ my @author = GetMarcFromKohaField('biblio.author',$frameworkcode); >+ my @issn = GetMarcFromKohaField('biblioitems.issn',$frameworkcode); >+ my @lccn = GetMarcFromKohaField('biblioitems.lccn',$frameworkcode); >+ >+ return { >+ $isbn[0].$isbn[1] => 'isbn', >+ $title[0].$title[1] => 'title', >+ $author[0].$author[1] => 'author', >+ $issn[0].$issn[1] => 'issn', >+ $lccn[0].$lccn[1] => 'lccn', >+ }; >+} >+ >+sub build_tabs ($$$$$) { >+ my($template, $record, $dbh,$encoding, $input) = @_; >+ # fill arrays >+ my @loop_data =(); >+ my $tag; >+ my $i=0; >+ my $authorised_values_sth = $dbh->prepare("select authorised_value,lib >+ from authorised_values >+ where category=? order by lib"); >+ >+ # in this array, we will push all the 10 tabs >+ # to avoid having 10 tabs in the template : they will all be in the same BIG_LOOP >+ my @BIG_LOOP; >+ my @HIDDEN_LOOP; >+ >+# loop through each tab 0 through 9 >+ foreach my $tag (sort(keys (%{$tagslib}))) { >+ my $taglib = $tagslib->{$tag}; >+ my $indicator; >+# if MARC::Record is not empty => use it as master loop, then add missing subfields that should be in the tab. >+# if MARC::Record is empty => use tab as master loop. >+ if ($record ne -1 && ($record->field($tag) || $tag eq '000')) { >+ my @fields; >+ if ($tag ne '000') { >+ @fields = $record->field($tag); >+ } else { >+ push @fields,$record->leader(); >+ } >+ foreach my $field (@fields) { >+ my $tag_writeout = "$tag "; >+ $tag_writeout .= ($field->indicator(1) eq ' ' ? '_' : $field->indicator(1)) . ($field->indicator(1) eq ' ' ? '_' : $field->indicator(1)) . ' ' if ($tag>=10); >+ my $tag_index = int(rand(1000000)); >+ my @subfields_data; >+ if ($tag<10) { >+ my ($value,$subfield); >+ if ($tag ne '000') { >+ $value=$field->data(); >+ $subfield="@"; >+ } else { >+ $value = $field; >+ $subfield='@'; >+ } >+ my $subfieldlib = $taglib->{$subfield}; >+ next if ($subfieldlib->{kohafield} eq 'biblio.biblionumber'); >+ >+ push(@subfields_data, "$value"); >+ $i++; >+ } else { >+ my @subfields=$field->subfields(); >+ foreach my $subfieldcount (0..$#subfields) { >+ my $subfield=$subfields[$subfieldcount][0]; >+ my $value=$subfields[$subfieldcount][1]; >+ my $subfieldlib = $taglib->{$subfield}; >+ next if (length $subfield !=1); >+ next if ($subfieldlib->{tab} > 9 or $subfieldlib->{tab} == -1); >+ push(@subfields_data, "\$$subfield $value"); >+ $i++; >+ } >+ } >+# now, loop again to add parameter subfield that are not in the MARC::Record >+ foreach my $subfield (sort( keys %{$tagslib->{$tag}})) { >+ my $subfieldlib = $taglib->{$subfield}; >+ next if (length $subfield !=1); >+ next if ($tag<10); >+ next if (!$subfieldlib->{mandatory}); >+ next if ($subfieldlib->{tab} > 9 or $subfieldlib->{tab} == -1); >+ next if (defined($field->subfield($subfield))); >+ push(@subfields_data, "\$$subfield"); >+ $i++; >+ } >+ if (@subfields_data) { >+ $tag_writeout .= join(' ', @subfields_data); >+ push (@BIG_LOOP, $tag_writeout); >+ } >+# If there is more than 1 field, add an empty hidden field as separator. >+ } >+# if breeding is empty >+ } else { >+ my $tag_writeout = "$tag "; >+ $tag_writeout .= '__ ' if ($tag>=10); >+ my @subfields_data; >+ foreach my $subfield (sort(keys %{$tagslib->{$tag}})) { >+ my $subfieldlib = $taglib->{$subfield}; >+ next if (length $subfield !=1); >+ next if (!$subfieldlib->{mandatory}); >+ next if ($subfieldlib->{tab} > 9); >+ >+ if (ref($creators{$tag . $subfield}) eq 'CODE') { >+ if (($subfieldlib->{hidden} <= -4) or ($subfieldlib->{hidden}>=5) or ($taglib->{tab} == -1)) { >+ my %row = ( >+ tag => $tag, >+ index => int(rand(1000000)), >+ index_subfield => int(rand(1000000)), >+ random => int(rand(1000000)), >+ subfield => ($subfield eq '@' ? '00' : $subfield), >+ subfield_value => $creators{$tag . $subfield}(), >+ ); >+ push @HIDDEN_LOOP, \%row; >+ next; >+ } else { >+ push @subfields_data, $creators{$tag . $subfield}(); >+ next; >+ } >+ } >+ >+ if ($tag >= 10) { >+ push @subfields_data, "\$$subfield"; >+ } else { >+ push @subfields_data, ""; >+ } >+ $i++; >+ } >+ next if (!@subfields_data); >+ push (@BIG_LOOP, $tag_writeout . join(' ', @subfields_data)); >+ } >+ } >+# $template->param($tabloop."XX" =>\@loop_data); >+ $template->param( >+ BIG_LOOP => join("\n", @BIG_LOOP), >+ HIDDEN_LOOP => \@HIDDEN_LOOP, >+ record_length => $#BIG_LOOP, >+ ); >+} >+ >+# >+# sub that tries to find authorities linked to the biblio >+# the sub : >+# - search in the authority DB for the same authid (in $9 of the biblio) >+# - search in the authority DB for the same 001 (in $3 of the biblio in UNIMARC) >+# - search in the authority DB for the same values (exactly) (in all subfields of the biblio) >+# if the authority is found, the biblio is modified accordingly to be connected to the authority. >+# if the authority is not found, it's added, and the biblio is then modified to be connected to the authority. >+# >+ >+sub BiblioAddAuthorities{ >+ my ( $record, $frameworkcode ) = @_; >+ my $dbh=C4::Context->dbh; >+ my $query=$dbh->prepare(qq| >+SELECT authtypecode,tagfield >+FROM marc_subfield_structure >+WHERE frameworkcode=? >+AND (authtypecode IS NOT NULL AND authtypecode<>\"\")|); >+# SELECT authtypecode,tagfield >+# FROM marc_subfield_structure >+# WHERE frameworkcode=? >+# AND (authtypecode IS NOT NULL OR authtypecode<>\"\")|); >+ $query->execute($frameworkcode); >+ my ($countcreated,$countlinked); >+ while (my $data=$query->fetchrow_hashref){ >+ foreach my $field ($record->field($data->{tagfield})){ >+ next if ($field->subfield('3')||$field->subfield('9')); >+ # No authorities id in the tag. >+ # Search if there is any authorities to link to. >+ my $query='at='.$data->{authtypecode}.' '; >+ map {$query.= ' and he,ext="'.$_->[1].'"' if ($_->[0]=~/[A-z]/)} $field->subfields(); >+ my ($error, $results, $total_hits)=SimpleSearch( $query, undef, undef, [ "authorityserver" ] ); >+ # there is only 1 result >+ if ( $error ) { >+ warn "BIBLIOADDSAUTHORITIES: $error"; >+ return (0,0) ; >+ } >+ if ($results && scalar(@$results)==1) { >+ my $marcrecord = MARC::File::USMARC::decode($results->[0]); >+ $field->add_subfields('9'=>$marcrecord->field('001')->data); >+ $countlinked++; >+ } elsif (scalar(@$results)>1) { >+ #More than One result >+ #This can comes out of a lack of a subfield. >+# my $marcrecord = MARC::File::USMARC::decode($results->[0]); >+# $record->field($data->{tagfield})->add_subfields('9'=>$marcrecord->field('001')->data); >+ $countlinked++; >+ } else { >+ #There are no results, build authority record, add it to Authorities, get authid and add it to 9 >+ ###NOTICE : This is only valid if a subfield is linked to one and only one authtypecode >+ ###NOTICE : This can be a problem. We should also look into other types and rejected forms. >+ my $authtypedata=GetAuthType($data->{authtypecode}); >+ next unless $authtypedata; >+ my $marcrecordauth=MARC::Record->new(); >+ my $authfield=MARC::Field->new($authtypedata->{auth_tag_to_report},'','',"a"=>"".$field->subfield('a')); >+ map { $authfield->add_subfields($_->[0]=>$_->[1]) if ($_->[0]=~/[A-z]/ && $_->[0] ne "a" )} $field->subfields(); >+ $marcrecordauth->insert_fields_ordered($authfield); >+ >+ # bug 2317: ensure new authority knows it's using UTF-8; currently >+ # only need to do this for MARC21, as MARC::Record->as_xml_record() handles >+ # automatically for UNIMARC (by not transcoding) >+ # FIXME: AddAuthority() instead should simply explicitly require that the MARC::Record >+ # use UTF-8, but as of 2008-08-05, did not want to introduce that kind >+ # of change to a core API just before the 3.0 release. >+ if (C4::Context->preference('marcflavour') eq 'MARC21') { >+ SetMarcUnicodeFlag($marcrecordauth, 'MARC21'); >+ } >+ >+# warn "AUTH RECORD ADDED : ".$marcrecordauth->as_formatted; >+ >+ my $authid=AddAuthority($marcrecordauth,'',$data->{authtypecode}); >+ $countcreated++; >+ $field->add_subfields('9'=>$authid); >+ } >+ } >+ } >+ return ($countlinked,$countcreated); >+} >+ >+# ======================== >+# MAIN >+#========================= >+my $input = new CGI; >+my $error = $input->param('error'); >+my $biblionumber = $input->param('biblionumber'); # if biblionumber exists, it's a modif, not a new biblio. >+my $breedingid = $input->param('breedingid'); >+my $z3950 = $input->param('z3950'); >+my $op = $input->param('op'); >+my $mode = $input->param('mode'); >+my $record_text = $input->param('record'); >+my $frameworkcode = $input->param('frameworkcode'); >+my $dbh = C4::Context->dbh; >+ >+my ( $template, $loggedinuser, $cookie ) = get_template_and_user( >+ { >+ template_name => "cataloguing/addbiblio-text.tmpl", >+ query => $input, >+ type => "intranet", >+ authnotrequired => 0, >+ flagsrequired => { editcatalogue => 1 }, >+ } >+); >+ >+if (is_ajax() && $op eq 'try_parse') { >+ my @params = $input->param(); >+ my $record = TransformHtmlToMarc( \@params , $input ); >+ my $response = new C4::Output::JSONStream; >+ >+ eval { >+ $record = TransformTextToMarc( $record_text, existing_record => $record ) >+ }; >+ if ( $@ ) { >+ chomp $@; >+ $response->param( type => 'input', error => 'parse_failed', message => $@ ); >+ >+ output_with_http_headers $input, $cookie, $response->output, 'json'; >+ exit; >+ } >+ >+ $response->param( record => $record->as_json_record_structure ); >+ >+ output_with_http_headers $input, $cookie, $response->output, 'json'; >+ exit; >+} >+ >+$frameworkcode = &GetFrameworkCode($biblionumber) >+ if ( $biblionumber and not($frameworkcode) ); >+ >+$frameworkcode = '' if ( $frameworkcode eq 'Default' ); >+ >+# Getting the list of all frameworks >+# get framework list >+my $frameworks = getframeworks; >+my @frameworkcodeloop; >+foreach my $thisframeworkcode ( keys %$frameworks ) { >+ my %row = ( >+ value => $thisframeworkcode, >+ frameworktext => $frameworks->{$thisframeworkcode}->{'frameworktext'}, >+ ); >+ if ($frameworkcode eq $thisframeworkcode){ >+ $row{'selected'}="selected=\"selected\""; >+ } >+ push @frameworkcodeloop, \%row; >+} >+$template->param( frameworkcodeloop => \@frameworkcodeloop, >+ breedingid => $breedingid ); >+ >+# ++ Global >+$tagslib = &GetMarcStructure( 1, $frameworkcode ); >+$usedTagsLib = &GetUsedMarcStructure( $frameworkcode ); >+$mandatory_z3950 = GetMandatoryFieldZ3950($frameworkcode); >+# -- Global >+ >+my $record = -1; >+my $encoding = ""; >+my ( >+ $biblionumbertagfield, >+ $biblionumbertagsubfield, >+ $biblioitemnumtagfield, >+ $biblioitemnumtagsubfield, >+ $bibitem, >+ $biblioitemnumber >+); >+ >+if (($biblionumber) && !($breedingid)){ >+ $record = GetMarcBiblio($biblionumber); >+} >+if ($breedingid) { >+ ( $record, $encoding ) = MARCfindbreeding( $breedingid ) ; >+} >+ >+$is_a_modif = 0; >+ >+if ($biblionumber) { >+ $is_a_modif = 1; >+ $template->param( title => $record->title(), ); >+ >+ # if it's a modif, retrieve bibli and biblioitem numbers for the future modification of old-DB. >+ ( $biblionumbertagfield, $biblionumbertagsubfield ) = >+ &GetMarcFromKohaField( "biblio.biblionumber", $frameworkcode ); >+ ( $biblioitemnumtagfield, $biblioitemnumtagsubfield ) = >+ &GetMarcFromKohaField( "biblioitems.biblioitemnumber", $frameworkcode ); >+ >+ # search biblioitems value >+ my $sth = $dbh->prepare("select biblioitemnumber from biblioitems where biblionumber=?"); >+ $sth->execute($biblionumber); >+ ($biblioitemnumber) = $sth->fetchrow; >+} >+ >+#------------------------------------------------------------------------------------- >+if ( $op eq "addbiblio" ) { >+#------------------------------------------------------------------------------------- >+ # getting html input >+ my @params = $input->param(); >+ $record = TransformHtmlToMarc( \@params , $input ); >+ eval { >+ $record = TransformTextToMarc( $record_text, existing_record => $record ) >+ }; >+ # check for a duplicate >+ my ($duplicatebiblionumber,$duplicatetitle) = FindDuplicate($record) if (!$is_a_modif); >+ my $confirm_not_duplicate = $input->param('confirm_not_duplicate'); >+ # it is not a duplicate (determined either by Koha itself or by user checking it's not a duplicate) >+ if ( !$duplicatebiblionumber or $confirm_not_duplicate ) { >+ my $oldbibnum; >+ my $oldbibitemnum; >+ if (C4::Context->preference("BiblioAddsAuthorities")){ >+ my ($countlinked,$countcreated)=BiblioAddAuthorities($record,$frameworkcode); >+ } >+ if ( $is_a_modif ) { >+ ModBiblioframework( $biblionumber, $frameworkcode ); >+ ModBiblio( $record, $biblionumber, $frameworkcode ); >+ } >+ else { >+ ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode ); >+ } >+ >+ if ($mode ne "popup"){ >+ print $input->redirect( >+ "/cgi-bin/koha/cataloguing/additem.pl?biblionumber=$biblionumber&frameworkcode=$frameworkcode" >+ ); >+ exit; >+ } else { >+ $template->param( >+ biblionumber => $biblionumber, >+ done =>1, >+ popup =>1 >+ ); >+ $template->param( title => $record->subfield('200',"a") ) if ($record ne "-1" && C4::Context->preference('marcflavour') =~/unimarc/i); >+ $template->param( title => $record->title() ) if ($record ne "-1" && C4::Context->preference('marcflavour') eq "usmarc"); >+ $template->param( >+ popup => $mode, >+ itemtype => $frameworkcode, >+ ); >+ output_html_with_http_headers $input, $cookie, $template->output; >+ exit; >+ } >+ } else { >+ # it may be a duplicate, warn the user and do nothing >+ build_tabs ($template, $record, $dbh,$encoding,$input); >+ $template->param( >+ biblionumber => $biblionumber, >+ biblioitemnumber => $biblioitemnumber, >+ duplicatebiblionumber => $duplicatebiblionumber, >+ duplicatebibid => $duplicatebiblionumber, >+ duplicatetitle => $duplicatetitle, >+ ); >+ } >+} >+elsif ( $op eq "delete" ) { >+ >+ my $error = &DelBiblio($biblionumber); >+ if ($error) { >+ warn "ERROR when DELETING BIBLIO $biblionumber : $error"; >+ print "Content-Type: text/html\n\n<html><body><h1>ERROR when DELETING BIBLIO $biblionumber : $error</h1></body></html>"; >+ exit; >+ } >+ >+ print $input->redirect('/cgi-bin/koha/catalogue/search.pl'); >+ exit; >+ >+} else { >+ #---------------------------------------------------------------------------- >+ # If we're in a duplication case, we have to set to "" the biblionumber >+ # as we'll save the biblio as a new one. >+ if ( $op eq "duplicate" ) { >+ $biblionumber = ""; >+ } >+ >+#FIXME: it's kind of silly to go from MARC::Record to MARC::File::XML and then back again just to fix the encoding >+ eval { >+ my $uxml = $record->as_xml; >+ MARC::Record::default_record_format("UNIMARC") >+ if ( C4::Context->preference("marcflavour") eq "UNIMARC" ); >+ my $urecord = MARC::Record::new_from_xml( $uxml, 'UTF-8' ); >+ $record = $urecord; >+ }; >+ build_tabs( $template, $record, $dbh, $encoding,$input ); >+ $template->param( >+ biblionumber => $biblionumber, >+ biblionumbertagfield => $biblionumbertagfield, >+ biblionumbertagsubfield => $biblionumbertagsubfield, >+ biblioitemnumtagfield => $biblioitemnumtagfield, >+ biblioitemnumtagsubfield => $biblioitemnumtagsubfield, >+ biblioitemnumber => $biblioitemnumber, >+ ); >+} >+ >+$template->param( title => $record->title() ) if ( $record ne "-1" ); >+$template->param( >+ popup => $mode, >+ frameworkcode => $frameworkcode, >+ itemtype => $frameworkcode, >+ itemtypes => GetItemTypeList(), >+); >+ >+output_html_with_http_headers $input, $cookie, $template->output; >diff --git a/cataloguing/addbiblio.pl b/cataloguing/addbiblio.pl >index e912c66..06fefda 100755 >--- a/cataloguing/addbiblio.pl >+++ b/cataloguing/addbiblio.pl >@@ -823,7 +823,7 @@ AND (authtypecode IS NOT NULL AND authtypecode<>\"\")|); > > # ======================== > # MAIN >-#========================= >+#======================== > my $input = new CGI; > my $error = $input->param('error'); > my $biblionumber = $input->param('biblionumber'); # if biblionumber exists, it's a modif, not a new biblio. >@@ -836,6 +836,10 @@ my $redirect = $input->param('redirect'); > my $dbh = C4::Context->dbh; > > my $userflags = ($frameworkcode eq 'FA') ? "fast_cataloging" : "edit_catalogue"; >+if (C4::Context->preference('MARCEditor') eq 'text') { >+ print $input->redirect('/cgi-bin/koha/cataloguing/addbiblio-text.pl?' . $ENV{'QUERY_STRING'}); >+ exit; >+} > > $frameworkcode = &GetFrameworkCode($biblionumber) > if ( $biblionumber and not($frameworkcode) and $op ne 'addbiblio' ); >diff --git a/cataloguing/framework-jsonp.pl b/cataloguing/framework-jsonp.pl >new file mode 100755 >index 0000000..1d8c3fd >--- /dev/null >+++ b/cataloguing/framework-jsonp.pl >@@ -0,0 +1,60 @@ >+#!/usr/bin/perl >+ >+use CGI; >+use C4::Context; >+use C4::Biblio; >+ >+my $input = new CGI; >+our $dbh = C4::Context->dbh; >+ >+my $frameworkcode = $input->param('frameworkcode') || ''; >+my $info = $input->param('info') || 'kohalinks'; >+my $prepend = $input->param('prepend') || ''; >+my $append = $input->param('append') || ''; >+ >+my $tagslib = GetMarcStructure(1, $frameworkcode); >+ >+print $input->header('text/javascript'); >+ >+print $prepend . "{"; >+ >+if ($info eq 'kohalinks') { >+ foreach my $tag (sort(keys (%{$tagslib}))) { >+ my $taglib = $tagslib->{$tag}; >+ foreach my $subfield (sort(keys %{$taglib})) { >+ my $subfieldlib = $taglib->{$subfield}; >+ if ($subfieldlib->{kohafield}) { >+ print "'" . $subfieldlib->{kohafield} . "':['$tag','$subfield'],"; >+ } >+ } >+ } >+} elsif ($info eq 'mandatory') { >+ my @mandatory_tags; >+ my @mandatory_subfields; >+ >+ foreach my $tag (sort(keys (%{$tagslib}))) { >+ my $taglib = $tagslib->{$tag}; >+ push @mandatory_tags, $tag if ($taglib->{mandatory}); >+ foreach my $subfield (sort(keys %{$taglib})) { >+ my $subfieldlib = $taglib->{$subfield}; >+ push @mandatory_subfields, "['$tag','$subfield']" if ($subfieldlib->{mandatory} && $subfieldlib->{tab} != -1 && $subfieldlib->{tab} != 10); >+ } >+ } >+ >+ print "tags:["; >+ foreach my $tag (@mandatory_tags) { print "'$tag',"; } >+ print "],"; >+ >+ print "subfields:["; >+ foreach my $subfield (@mandatory_subfields) { print "$subfield,"; } >+ print "]"; >+} elsif ($info eq 'itemtypes') { >+ my $sth=$dbh->prepare("select itemtype,description from itemtypes order by description"); >+ $sth->execute; >+ >+ while (my ($itemtype,$description) = $sth->fetchrow_array) { >+ print "'$itemtype':'$description',"; >+ } >+} >+ >+print "}" . $append; >diff --git a/installer/data/mysql/en/mandatory/sysprefs.sql b/installer/data/mysql/en/mandatory/sysprefs.sql >index 8407505..1f12879 100755 >--- a/installer/data/mysql/en/mandatory/sysprefs.sql >+++ b/installer/data/mysql/en/mandatory/sysprefs.sql >@@ -317,3 +317,4 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ( > INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('BasketConfirmations', '1', 'When closing or reopening a basket,', 'always ask for confirmation.|do not ask for confirmation.', 'Choice'); > INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn | a|a d', NULL, NULL, 'Textarea'); > INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0,'If ON Openlibrary book covers will be show',NULL,'YesNo'); >+INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('MARCEditor','normal','Use the normal or textual MARC editor','normal|text','Choice'); >diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl >index 720bd8c..75f5a2b 100755 >--- a/installer/data/mysql/updatedatabase.pl >+++ b/installer/data/mysql/updatedatabase.pl >@@ -4399,6 +4399,13 @@ if (C4::Context->preference("Version") < TransformToNum($DBversion)) { > SetVersion($DBversion); > } > >+$DBversion = "3.05.00.XXX"; >+if (C4::Context->preference("Version") < TransformToNum($DBversion)) { >+ $dbh->do("INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('MARCEditor','normal','Use the normal or textual MARC editor','normal|text','Choice');"); >+ print "Upgrade to $DBversion done (Add syspref MARCEditor)\n"; >+ SetVersion($DBversion); >+} >+ > =head1 FUNCTIONS > > =head2 DropAllForeignKeys($table) >diff --git a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css >index 3c5d257..78822c2 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css >+++ b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css >@@ -2081,3 +2081,8 @@ fieldset.rows+h3 {clear:both;padding-top:.5em;} > color : #cc0000; > } > >+#embedded_z3950 { >+ width: 100%; >+ height: 500px; >+ border: none; >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/doc-head-close.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/doc-head-close.inc >index 6a2dae0..c3ca8d9 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/doc-head-close.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/doc-head-close.inc >@@ -72,6 +72,9 @@ > <script type="text/javascript" src="[% yuipath %]/container/container_core-min.js"></script> > <script type="text/javascript" src="[% yuipath %]/menu/menu-min.js"></script> > >+<script type="text/javascript"> >+var koha = { themelang: '[% themelang %]' }; >+</script> > <!-- koha core js --> > <script type="text/javascript" src="[% themelang %]/js/staff-global.js"></script> > [% IF ( intranetuserjs ) %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/js/marc.js b/koha-tmpl/intranet-tmpl/prog/en/js/marc.js >new file mode 100644 >index 0000000..3e3a568 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/js/marc.js >@@ -0,0 +1,194 @@ >+/* From MARC::Record::JSON: http://code.google.com/p/marc-json/downloads/list */ >+/* Modified by Jesse Weaver */ >+ >+/*=========================================== >+ MARC.Field(fdata) >+ >+A MARC Field, as pulled from the json data. >+ >+You can usually get what you want using MARCRecord.subfield(tag, sub) >+but may need this for more advanced usage >+ >+ f = new MARC.Field(data); >+ isbn = f.subfield('a'); // if it's an 020, of course >+ isbn = f.as_string('a'); // same thing >+ >+ alltitlestuff = f.as_string(); // if it's a 245 >+ propertitle = f.as_string('anp'); // just the a, n, and p subfields >+ >+ subfield('a', sep=' ') -- returns: >+ '' iff there is no subfield a >+ 'value' iff there is exactly one subfield a >+ 'value1|value2' iff there are more than on subfield a's >+ >+ as_string(spec, sep, includesftags) -- where spec is either empty or a string of concat'd subfields. >+ spec is either null (all subfields) or a string listing the subfields (e.g., 'a' or 'abh') >+ sep is the string used to separate the values; a single space is the default >+ includesftags is a boolean that determines if the subfield tags will be included (e.g, $$a data $$h moredata) >+ >+ It returns the found data joined by the string in 'sep', or an empty string if nothing is found. >+ >+ >+===============================================*/ >+ >+marc = {} >+ >+marc.field = function ( tag, ind1, ind2, subfields ) { >+ this.tag = tag; >+ >+ if (tag < 10) { >+ this.is_control_field = true; >+ this.data = ind1; >+ return; >+ } >+ >+ this._subfields = subfields; >+ >+ this._subfield_map = {}; >+ >+ if ( ind1 == '' ) ind1 = ' '; >+ if ( ind2 == '' ) ind2 = ' '; >+ >+ this._indicators = [ ind1, ind2 ]; >+ >+ var field = this; >+ >+ $.each( subfields, function( i, subfield ) { >+ var code = subfield[0]; >+ >+ if (!(code in field._subfield_map)) field._subfield_map[code] = []; >+ >+ field._subfield_map[code].push(subfield[1]); >+ } ); >+} >+ >+$.extend( marc.field.prototype, { >+ indicator: function(ind) { >+ if (this.is_control_field) throw TypeError('indicator() called on control field'); >+ if (ind != 1 && ind != 2) return null; >+ >+ return this._indicators[ind - 1]; >+ }, >+ >+ subfield: function(code) { >+ if (this.is_control_field) throw TypeError('subfield() called on control field'); >+ if (!(code in this._subfield_map)) return null; >+ >+ return this._subfield_map[code][0]; >+ }, >+ >+ subfields: function(code) { >+ if (this.is_control_field) throw TypeError('subfields() called on control field'); >+ if (code === undefined) { >+ return self._subfields; >+ } else { >+ if (!(code in this._subfield_map)) return null; >+ >+ return this._subfield_map[code]; >+ } >+ }, >+ >+ as_string: function() { >+ var buffer = [ this.tag, ' ' ]; >+ >+ if ( this.is_control_field ) { >+ buffer.push( this.data ); >+ } else { >+ buffer.push( this._indicators[0], this._indicators[1], ' ' ); >+ >+ $.each( this.subfields, function( i, subfield ) { >+ buffer.push( '$', subfield[0], ' ', subfield[1] ); >+ } ); >+ } >+ }, >+}); >+ >+ >+/*=========================================== >+MARCRecord -- a MARC::Record-like object >+ >+ r.cfield('008') -- the contents of the 008 control field >+ r.cfield('LDR') -- ditto with the leader >+ >+ array = r.controlFieldTags(); -- a list of the control field tags, for feeding into cfield >+ >+ array = r.dfield('022') -- all the ISSN fields >+ r.dfield('022')[0].as_string -- the first 022 as a string >+ r.dfield('245')[0].as_string(); -- the title as a string >+ r.dfield('FAK') -- returns an empty array >+ >+ r.dfields() -- return an array of all dfields >+ >+ r.field('245')[0] -- 'field' is an alias for 'dfield' >+ >+ r.subfield('245', 'a') -- the first 245/a >+ r.subfield('100', 'a') -- the author? >+ >+ // Convenience functions >+ >+ str = r.title(); >+ str = r.author(); // Looks in 100, 110, and 111 in that order; returns '' on fail >+ edition = r.edition(); // from the 250/a >+ >+ >+===========================================*/ >+ >+marc.record = function(structure) { >+ this.leader = new Array(25).join(' '); // Poor man's ' ' x 24 >+ this._fields = []; >+ this._field_map = {}; >+ >+ if (structure) { >+ this.leader = structure.leader; >+ var record = this; >+ >+ $.each( structure.fields, function( i, field ) { >+ var tag = field.tag; >+ >+ if ( !( tag in record._field_map ) ) record._field_map[tag] = []; >+ >+ var f = field.contents ? new marc.field( tag, field.contents ) : new marc.field( tag, field.indicator1, field.indicator2, field.subfields ); >+ >+ record._fields.push( f ); >+ record._field_map[tag].push( f ); >+ } ); >+ } >+} >+ >+$.extend( marc.record.prototype, { >+ subfield: function(tag, subfield) { >+ if ( !( tag in this._field_map ) ) return false; >+ >+ if ( subfield === undefined ) return true; >+ >+ var found = null; >+ >+ $.each( this._field_map[tag], function( i, field ) { >+ found = field.subfield( subfield ); >+ >+ if ( found ) return false; >+ } ); >+ >+ return found; >+ }, >+ >+ has: function( tag, subfield ) { >+ return Boolean( this.subfield( tag, subfield ) ); >+ }, >+ >+ field: function(tag) { >+ if (!(tag in this._field_map)) return null; >+ >+ return this._field_map[tag][0]; >+ }, >+ >+ fields: function(tag) { >+ if (tag === undefined) { >+ return self._fields; >+ } else { >+ if (!(tag in this._field_map)) return null; >+ >+ return this._field_map[tag]; >+ } >+ }, >+} ); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/js/pages/addbiblio-text.js b/koha-tmpl/intranet-tmpl/prog/en/js/pages/addbiblio-text.js >new file mode 100644 >index 0000000..9e40499 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/js/pages/addbiblio-text.js >@@ -0,0 +1,82 @@ >+addbiblio = {}; >+ >+$.extend( addbiblio, { >+ submit: function() { >+ $.ajax( { >+ url: '/cgi-bin/koha/cataloguing/addbiblio-text.pl', >+ type: 'POST', >+ dataType: 'json', >+ data: $( '#f input[name^="tag"]' ).serialize() + '&op=try_parse&record=' + escape(addbiblio.editor.getCode()), >+ success: addbiblio.submit.finished, >+ } ); >+ }, >+ insert_itemtype: function( event ) { >+ var iter = addbiblio.editor.cursorPosition(); >+ addbiblio.editor.insertIntoLine( iter.line, iter.character, $( '#itemtypes' ).val() ); >+ >+ return false; >+ }, >+ z3950_search: function() { >+ window.open( "/cgi-bin/koha/cataloguing/z3950_search.pl?biblionumber=" + addbiblio.biblionumber,"z3950search",'width=740,height=450,location=yes,toolbar=no,scrollbars=yes,resize=yes' ); >+ }, >+ not_duplicate: function() { >+ $( "#confirm_not_duplicate" ).attr( "value", "1" ); >+ $( "#f" ).get( 0 ).submit(); >+ }, >+} ); >+ >+$.extend( addbiblio.submit, { >+ finished: function( data, status_ ) { >+ if ( data.error ) { >+ humanMsg.displayMsg( '<strong>Watch your language:</strong> ' + data.message ); >+ return false; >+ } >+ >+ var record = new marc.record(data.record); >+ >+ var missing_tags = [], missing_subfields = []; >+ >+ $.each( addbiblio.mandatory.tags, function( i, tag ) { >+ if ( tag == '000' ) { >+ if ( !record.leader) missing_tags.push( 'leader' ); >+ } else if ( !record.has( tag ) ) { >+ missing_tags.push( tag ); >+ } >+ } ); >+ >+ $.each( addbiblio.mandatory.subfields, function( i, sf ) { >+ if ( sf[0].substring( 0, 2 ) != '00' && !record.has( sf[0], sf[1] ) ) { >+ missing_subfields.push( sf.join( '$' ) ); >+ } >+ } ); >+ >+ if ( missing_tags.length || missing_subfields.length ) { >+ message = []; >+ >+ if ( missing_tags.length ) { >+ message.push( missing_tags.join( ', ' ) + ' tags' ); >+ } >+ >+ if ( missing_subfields.length ) { >+ message.push( missing_subfields.join( ', ' ) + ' subfields' ); >+ } >+ >+ humanMsg.displayMsg( '<strong>Record is missing pieces:</strong> ' + message.join( ' and ' ) + ' are mandatory' ); >+ return; >+ } >+ >+ $( '#f' ).get( 0 ).submit(); >+ } >+} ); >+ >+$( function () { >+ $( '#insert-itemtype' ).click( addbiblio.insert_itemtype ); >+ >+ addbiblio.editor = CodeMirror.fromTextArea('record', { >+ height: "350px", >+ parserfile: "parsemarc.js", >+ stylesheet: koha.themelang + "/lib/codemirror/css/marccolors.css", >+ path: koha.themelang + "/lib/codemirror/js/", >+ autoMatchParens: true >+ }); >+} ); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/LICENSE b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/LICENSE >new file mode 100644 >index 0000000..cee9537 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/LICENSE >@@ -0,0 +1,23 @@ >+ Copyright (c) 2007-2008 Marijn Haverbeke >+ >+ This software is provided 'as-is', without any express or implied >+ warranty. In no event will the authors be held liable for any >+ damages arising from the use of this software. >+ >+ Permission is granted to anyone to use this software for any >+ purpose, including commercial applications, and to alter it and >+ redistribute it freely, subject to the following restrictions: >+ >+ 1. The origin of this software must not be misrepresented; you must >+ not claim that you wrote the original software. If you use this >+ software in a product, an acknowledgment in the product >+ documentation would be appreciated but is not required. >+ >+ 2. Altered source versions must be plainly marked as such, and must >+ not be misrepresented as being the original software. >+ >+ 3. This notice may not be removed or altered from any source >+ distribution. >+ >+ Marijn Haverbeke >+ marijnh at gmail >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/csscolors.css b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/csscolors.css >new file mode 100644 >index 0000000..100c93f >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/csscolors.css >@@ -0,0 +1,47 @@ >+.editbox { >+ margin: .4em; >+ padding: 0; >+ font-family: monospace; >+ font-size: 10pt; >+ color: black; >+} >+ >+pre.code, .editbox { >+ color: #666666; >+} >+ >+.editbox p { >+ margin: 0; >+} >+ >+span.css-at { >+ color: #770088; >+} >+ >+span.css-unit { >+ color: #228811; >+} >+ >+span.css-value { >+ color: #770088; >+} >+ >+span.css-identifier { >+ color: black; >+} >+ >+span.css-important { >+ color: #0000FF; >+} >+ >+span.css-colorcode { >+ color: #004499; >+} >+ >+span.css-comment { >+ color: #AA7700; >+} >+ >+span.css-string { >+ color: #AA2222; >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/docs.css b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/docs.css >new file mode 100644 >index 0000000..ff7e4dc >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/docs.css >@@ -0,0 +1,42 @@ >+body { >+ margin: 0; >+ font-family: tahoma, arial, sans-serif; >+ padding: 3em 6em; >+ color: black; >+} >+ >+h1 { >+ font-size: 22pt; >+} >+ >+h2 { >+ font-size: 14pt; >+} >+ >+p.rel { >+ padding-left: 2em; >+ text-indent: -2em; >+} >+ >+div.border { >+ border: 1px solid black; >+ padding: 3px; >+} >+ >+code { >+ font-family: courier, monospace; >+ font-size: 90%; >+ color: #155; >+} >+ >+pre.code { >+ margin: 1.1em 12px; >+ border: 1px solid #CCCCCC; >+ color: black; >+ padding: .4em; >+ font-family: courier, monospace; >+} >+ >+.warn { >+ color: #C00; >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/jscolors.css b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/jscolors.css >new file mode 100644 >index 0000000..3067628 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/jscolors.css >@@ -0,0 +1,55 @@ >+.editbox { >+ margin: .4em; >+ padding: 0; >+ font-family: monospace; >+ font-size: 10pt; >+ color: black; >+} >+ >+pre.code, .editbox { >+ color: #666666; >+} >+ >+.editbox p { >+ margin: 0; >+} >+ >+span.js-punctuation { >+ color: #666666; >+} >+ >+span.js-operator { >+ color: #666666; >+} >+ >+span.js-keyword { >+ color: #770088; >+} >+ >+span.js-atom { >+ color: #228811; >+} >+ >+span.js-variable { >+ color: black; >+} >+ >+span.js-variabledef { >+ color: #0000FF; >+} >+ >+span.js-localvariable { >+ color: #004499; >+} >+ >+span.js-property { >+ color: black; >+} >+ >+span.js-comment { >+ color: #AA7700; >+} >+ >+span.js-string { >+ color: #AA2222; >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/marccolors.css b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/marccolors.css >new file mode 100644 >index 0000000..c1f292a >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/marccolors.css >@@ -0,0 +1,24 @@ >+ >+.editbox { >+ margin: .4em; >+ padding: 0; >+ font-family: monospace; >+ font-size: 10pt; >+ color: black; >+} >+ >+.editbox p { >+ margin: 0; >+} >+ >+span.marc-tag { >+ color: #880; >+} >+ >+span.marc-indicator { >+ color: #088; >+} >+ >+span.marc-subfield { >+ color: #808; >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/people.jpg b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/people.jpg >new file mode 100644 >index 0000000000000000000000000000000000000000..734789542b3533f464c3eb18908f06e48f44e55d >GIT binary patch >literal 14122 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 >B7;*pr > >literal 0 >HcmV?d00001 > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/sparqlcolors.css b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/sparqlcolors.css >new file mode 100644 >index 0000000..78d8ae0 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/sparqlcolors.css >@@ -0,0 +1,39 @@ >+.editbox { >+ margin: .4em; >+ padding: 0; >+ font-family: monospace; >+ font-size: 10pt; >+ color: black; >+} >+ >+.editbox p { >+ margin: 0; >+} >+ >+span.sp-keyword { >+ color: #708; >+} >+ >+span.sp-prefixed { >+ color: #5d1; >+} >+ >+span.sp-var { >+ color: #00c; >+} >+ >+span.sp-comment { >+ color: #a70; >+} >+ >+span.sp-literal { >+ color: #a22; >+} >+ >+span.sp-uri { >+ color: #292; >+} >+ >+span.sp-operator { >+ color: #088; >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/xmlcolors.css b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/xmlcolors.css >new file mode 100644 >index 0000000..aa26579 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/xmlcolors.css >@@ -0,0 +1,51 @@ >+.editbox { >+ margin: .4em; >+ padding: 0; >+ font-family: monospace; >+ font-size: 10pt; >+ color: black; >+} >+ >+.editbox p { >+ margin: 0; >+} >+ >+span.xml-tagname { >+ color: #A0B; >+} >+ >+span.xml-attribute { >+ color: #281; >+} >+ >+span.xml-punctuation { >+ color: black; >+} >+ >+span.xml-attname { >+ color: #00F; >+} >+ >+span.xml-comment { >+ color: #A70; >+} >+ >+span.xml-cdata { >+ color: #48A; >+} >+ >+span.xml-processing { >+ color: #999; >+} >+ >+span.xml-entity { >+ color: #A22; >+} >+ >+span.xml-error { >+ color: #F00; >+} >+ >+span.xml-text { >+ color: black; >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/codemirror.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/codemirror.js >new file mode 100644 >index 0000000..18d8bf7 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/codemirror.js >@@ -0,0 +1,219 @@ >+/* CodeMirror main module >+ * >+ * Implements the CodeMirror constructor and prototype, which take care >+ * of initializing the editor frame, and providing the outside interface. >+ */ >+ >+// The CodeMirrorConfig object is used to specify a default >+// configuration. If you specify such an object before loading this >+// file, the values you put into it will override the defaults given >+// below. You can also assign to it after loading. >+var CodeMirrorConfig = window.CodeMirrorConfig || {}; >+ >+var CodeMirror = (function(){ >+ function setDefaults(object, defaults) { >+ for (var option in defaults) { >+ if (!object.hasOwnProperty(option)) >+ object[option] = defaults[option]; >+ } >+ } >+ function forEach(array, action) { >+ for (var i = 0; i < array.length; i++) >+ action(array[i]); >+ } >+ >+ // These default options can be overridden by passing a set of >+ // options to a specific CodeMirror constructor. See manual.html for >+ // their meaning. >+ setDefaults(CodeMirrorConfig, { >+ stylesheet: "", >+ path: "", >+ parserfile: [], >+ basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js", "tokenize.js"], >+ linesPerPass: 15, >+ passDelay: 200, >+ continuousScanning: false, >+ saveFunction: null, >+ onChange: null, >+ undoDepth: 20, >+ undoDelay: 800, >+ disableSpellcheck: true, >+ textWrapping: true, >+ readOnly: false, >+ width: "100%", >+ height: "300px", >+ autoMatchParens: false, >+ parserConfig: null, >+ dumbTabs: false, >+ activeTokens: null, >+ cursorActivity: null >+ }); >+ >+ function CodeMirror(place, options) { >+ // Use passed options, if any, to override defaults. >+ this.options = options = options || {}; >+ setDefaults(options, CodeMirrorConfig); >+ >+ var frame = this.frame = document.createElement("IFRAME"); >+ frame.src = "javascript:false;"; >+ frame.style.border = "0"; >+ frame.style.width = options.width; >+ frame.style.height = options.height; >+ // display: block occasionally suppresses some Firefox bugs, so we >+ // always add it, redundant as it sounds. >+ frame.style.display = "block"; >+ >+ if (place.appendChild) >+ place.appendChild(frame); >+ else >+ place(frame); >+ >+ // Link back to this object, so that the editor can fetch options >+ // and add a reference to itself. >+ frame.CodeMirror = this; >+ this.win = frame.contentWindow; >+ >+ if (typeof options.parserfile == "string") >+ options.parserfile = [options.parserfile]; >+ if (typeof options.stylesheet == "string") >+ options.stylesheet = [options.stylesheet]; >+ >+ var html = ["<html><head>"]; >+ forEach(options.stylesheet, function(file) { >+ html.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + file + "\"/>"); >+ }); >+ forEach(options.basefiles.concat(options.parserfile), function(file) { >+ html.push("<script type=\"text/javascript\" src=\"" + options.path + file + "\"></script>"); >+ }); >+ html.push("</head><body style=\"border-width: 0;\" class=\"editbox\" spellcheck=\"" + >+ (options.disableSpellcheck ? "false" : "true") + "\"></body></html>"); >+ >+ var doc = this.win.document; >+ doc.open(); >+ doc.write(html.join("")); >+ doc.close(); >+ } >+ >+ CodeMirror.prototype = { >+ getCode: function() {return this.editor.getCode();}, >+ setCode: function(code) {this.editor.importCode(code);}, >+ selection: function() {return this.editor.selectedText();}, >+ reindent: function() {this.editor.reindent();}, >+ >+ focus: function() { >+ this.win.focus(); >+ if (this.editor.selectionSnapshot) // IE hack >+ this.win.select.selectCoords(this.win, this.editor.selectionSnapshot); >+ }, >+ replaceSelection: function(text) { >+ this.focus(); >+ this.editor.replaceSelection(text); >+ return true; >+ }, >+ replaceChars: function(text, start, end) { >+ this.editor.replaceChars(text, start, end); >+ }, >+ getSearchCursor: function(string, fromCursor) { >+ return this.editor.getSearchCursor(string, fromCursor); >+ }, >+ >+ cursorPosition: function(start) { >+ if (this.win.select.ie_selection) this.focus(); >+ return this.editor.cursorPosition(start); >+ }, >+ firstLine: function() {return this.editor.firstLine();}, >+ lastLine: function() {return this.editor.lastLine();}, >+ nextLine: function(line) {return this.editor.nextLine(line);}, >+ prevLine: function(line) {return this.editor.prevLine(line);}, >+ lineContent: function(line) {return this.editor.lineContent(line);}, >+ setLineContent: function(line, content) {this.editor.setLineContent(line, content);}, >+ insertIntoLine: function(line, position, content) {this.editor.insertIntoLine(line, position, content);}, >+ selectLines: function(startLine, startOffset, endLine, endOffset) { >+ this.win.focus(); >+ this.editor.selectLines(startLine, startOffset, endLine, endOffset); >+ }, >+ nthLine: function(n) { >+ var line = this.firstLine(); >+ for (; n > 1 && line !== false; n--) >+ line = this.nextLine(line); >+ return line; >+ }, >+ lineNumber: function(line) { >+ var num = 0; >+ while (line !== false) { >+ num++; >+ line = this.prevLine(line); >+ } >+ return num; >+ }, >+ >+ // Old number-based line interface >+ jumpToLine: function(n) { >+ this.selectLines(this.nthLine(n), 0); >+ this.win.focus(); >+ }, >+ currentLine: function() { >+ return this.lineNumber(this.cursorPosition().line); >+ } >+ }; >+ >+ CodeMirror.InvalidLineHandle = {toString: function(){return "CodeMirror.InvalidLineHandle";}}; >+ >+ CodeMirror.replace = function(element) { >+ if (typeof element == "string") >+ element = document.getElementById(element); >+ return function(newElement) { >+ element.parentNode.replaceChild(newElement, element); >+ }; >+ }; >+ >+ CodeMirror.fromTextArea = function(area, options) { >+ if (typeof area == "string") >+ area = document.getElementById(area); >+ >+ options = options || {}; >+ if (area.style.width) options.width = area.style.width; >+ if (area.style.height) options.height = area.style.height; >+ if (options.content == null) options.content = area.value; >+ >+ if (area.form) { >+ function updateField() { >+ area.value = mirror.getCode(); >+ } >+ if (typeof area.form.addEventListener == "function") >+ area.form.addEventListener("submit", updateField, false); >+ else >+ area.form.attachEvent("onsubmit", updateField); >+ } >+ >+ function insert(frame) { >+ if (area.nextSibling) >+ area.parentNode.insertBefore(frame, area.nextSibling); >+ else >+ area.parentNode.appendChild(frame); >+ } >+ >+ area.style.display = "none"; >+ var mirror = new CodeMirror(insert, options); >+ return mirror; >+ }; >+ >+ CodeMirror.isProbablySupported = function() { >+ // This is rather awful, but can be useful. >+ var match; >+ if (window.opera) >+ return Number(window.opera.version()) >= 9.52; >+ else if (/Apple Computers, Inc/.test(navigator.vendor) && (match = navigator.userAgent.match(/Version\/(\d+(?:\.\d+)?)\./))) >+ return Number(match[1]) >= 3; >+ else if (document.selection && window.ActiveXObject && (match = navigator.userAgent.match(/MSIE (\d+(?:\.\d*)?)\b/))) >+ return Number(match[1]) >= 6; >+ else if (match = navigator.userAgent.match(/gecko\/(\d{8})/i)) >+ return Number(match[1]) >= 20050901; >+ else if (/Chrome\//.test(navigator.userAgent)) >+ return true; >+ else >+ return null; >+ }; >+ >+ return CodeMirror; >+})(); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/editor.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/editor.js >new file mode 100644 >index 0000000..b2a96db >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/editor.js >@@ -0,0 +1,1176 @@ >+/* The Editor object manages the content of the editable frame. It >+ * catches events, colours nodes, and indents lines. This file also >+ * holds some functions for transforming arbitrary DOM structures into >+ * plain sequences of <span> and <br> elements >+ */ >+ >+var safeWhiteSpace, splitSpaces; >+function setWhiteSpaceModel(collapsing) { >+ safeWhiteSpace = collapsing ? >+ // Make sure a string does not contain two consecutive 'collapseable' >+ // whitespace characters. >+ function(n) { >+ var buffer = [], nb = true; >+ for (; n > 0; n--) { >+ buffer.push((nb || n == 1) ? nbsp : " "); >+ nb = !nb; >+ } >+ return buffer.join(""); >+ } : >+ function(n) { >+ var buffer = []; >+ for (; n > 0; n--) buffer.push(" "); >+ return buffer.join(""); >+ }; >+ splitSpaces = collapsing ? >+ // Create a set of white-space characters that will not be collapsed >+ // by the browser, but will not break text-wrapping either. >+ function(string) { >+ if (string.charAt(0) == " ") string = nbsp + string.slice(1); >+ return string.replace(/[\t \u00a0]{2,}/g, function(s) {return safeWhiteSpace(s.length);}); >+ } : >+ function(string) {return string;}; >+} >+ >+function makePartSpan(value, doc) { >+ var text = value; >+ if (value.nodeType == 3) text = value.nodeValue; >+ else value = doc.createTextNode(text); >+ >+ var span = doc.createElement("SPAN"); >+ span.isPart = true; >+ span.appendChild(value); >+ span.currentText = text; >+ return span; >+} >+ >+var Editor = (function(){ >+ // The HTML elements whose content should be suffixed by a newline >+ // when converting them to flat text. >+ var newlineElements = {"P": true, "DIV": true, "LI": true}; >+ >+ function asEditorLines(string) { >+ return splitSpaces(string.replace(/\t/g, " ").replace(/\u00a0/g, " ")).replace(/\r\n?/g, "\n").split("\n"); >+ } >+ >+ var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent); >+ >+ // Helper function for traverseDOM. Flattens an arbitrary DOM node >+ // into an array of textnodes and <br> tags. >+ function simplifyDOM(root) { >+ var doc = root.ownerDocument; >+ var result = []; >+ var leaving = false; >+ >+ function simplifyNode(node) { >+ if (node.nodeType == 3) { >+ var text = node.nodeValue = splitSpaces(node.nodeValue.replace(/[\n\r]/g, "")); >+ if (text.length) leaving = false; >+ result.push(node); >+ } >+ else if (node.nodeName == "BR" && node.childNodes.length == 0) { >+ leaving = true; >+ result.push(node); >+ } >+ else { >+ forEach(node.childNodes, simplifyNode); >+ if (!leaving && newlineElements.hasOwnProperty(node.nodeName)) { >+ leaving = true; >+ result.push(doc.createElement("BR")); >+ } >+ } >+ } >+ >+ simplifyNode(root); >+ return result; >+ } >+ >+ // Creates a MochiKit-style iterator that goes over a series of DOM >+ // nodes. The values it yields are strings, the textual content of >+ // the nodes. It makes sure that all nodes up to and including the >+ // one whose text is being yielded have been 'normalized' to be just >+ // <span> and <br> elements. >+ // See the story.html file for some short remarks about the use of >+ // continuation-passing style in this iterator. >+ function traverseDOM(start){ >+ function yield(value, c){cc = c; return value;} >+ function push(fun, arg, c){return function(){return fun(arg, c);};} >+ function stop(){cc = stop; throw StopIteration;}; >+ var cc = push(scanNode, start, stop); >+ var owner = start.ownerDocument; >+ var nodeQueue = []; >+ >+ // Create a function that can be used to insert nodes after the >+ // one given as argument. >+ function pointAt(node){ >+ var parent = node.parentNode; >+ var next = node.nextSibling; >+ return function(newnode) { >+ parent.insertBefore(newnode, next); >+ }; >+ } >+ var point = null; >+ >+ // Insert a normalized node at the current point. If it is a text >+ // node, wrap it in a <span>, and give that span a currentText >+ // property -- this is used to cache the nodeValue, because >+ // directly accessing nodeValue is horribly slow on some browsers. >+ // The dirty property is used by the highlighter to determine >+ // which parts of the document have to be re-highlighted. >+ function insertPart(part){ >+ var text = "\n"; >+ if (part.nodeType == 3) { >+ select.snapshotChanged(); >+ part = makePartSpan(part, owner); >+ text = part.currentText; >+ } >+ part.dirty = true; >+ nodeQueue.push(part); >+ point(part); >+ return text; >+ } >+ >+ // Extract the text and newlines from a DOM node, insert them into >+ // the document, and yield the textual content. Used to replace >+ // non-normalized nodes. >+ function writeNode(node, c){ >+ var toYield = []; >+ forEach(simplifyDOM(node), function(part) { >+ toYield.push(insertPart(part)); >+ }); >+ return yield(toYield.join(""), c); >+ } >+ >+ // Check whether a node is a normalized <span> element. >+ function partNode(node){ >+ if (node.nodeName == "SPAN" && node.childNodes.length == 1 && node.firstChild.nodeType == 3 && node.isPart) { >+ node.currentText = node.firstChild.nodeValue; >+ return !/[\n\t\r]/.test(node.currentText); >+ } >+ return false; >+ } >+ >+ // Handle a node. Add its successor to the continuation if there >+ // is one, find out whether the node is normalized. If it is, >+ // yield its content, otherwise, normalize it (writeNode will take >+ // care of yielding). >+ function scanNode(node, c){ >+ if (node.nextSibling) >+ c = push(scanNode, node.nextSibling, c); >+ >+ if (partNode(node)){ >+ nodeQueue.push(node); >+ return yield(node.currentText, c); >+ } >+ else if (node.nodeName == "BR") { >+ nodeQueue.push(node); >+ return yield("\n", c); >+ } >+ else { >+ point = pointAt(node); >+ removeElement(node); >+ return writeNode(node, c); >+ } >+ } >+ >+ // MochiKit iterators are objects with a next function that >+ // returns the next value or throws StopIteration when there are >+ // no more values. >+ return {next: function(){return cc();}, nodes: nodeQueue}; >+ } >+ >+ // Determine the text size of a processed node. >+ function nodeSize(node) { >+ if (node.nodeName == "BR") >+ return 1; >+ else >+ return node.currentText.length; >+ } >+ >+ // Search backwards through the top-level nodes until the next BR or >+ // the start of the frame. >+ function startOfLine(node) { >+ while (node && node.nodeName != "BR") node = node.previousSibling; >+ return node; >+ } >+ function endOfLine(node, container) { >+ if (!node) node = container.firstChild; >+ while (node && node.nodeName != "BR") node = node.nextSibling; >+ return node; >+ } >+ >+ function cleanText(text) { >+ return text.replace(/\u00a0/g, " "); >+ } >+ >+ // Client interface for searching the content of the editor. Create >+ // these by calling CodeMirror.getSearchCursor. To use, call >+ // findNext on the resulting object -- this returns a boolean >+ // indicating whether anything was found, and can be called again to >+ // skip to the next find. Use the select and replace methods to >+ // actually do something with the found locations. >+ function SearchCursor(editor, string, fromCursor) { >+ this.editor = editor; >+ this.history = editor.history; >+ this.history.commit(); >+ >+ // Are we currently at an occurrence of the search string? >+ this.atOccurrence = false; >+ // The object stores a set of nodes coming after its current >+ // position, so that when the current point is taken out of the >+ // DOM tree, we can still try to continue. >+ this.fallbackSize = 15; >+ var cursor; >+ // Start from the cursor when specified and a cursor can be found. >+ if (fromCursor && (cursor = select.cursorPos(this.editor.container))) { >+ this.line = cursor.node; >+ this.offset = cursor.offset; >+ } >+ else { >+ this.line = null; >+ this.offset = 0; >+ } >+ this.valid = !!string; >+ >+ // Create a matcher function based on the kind of string we have. >+ var target = string.split("\n"), self = this;; >+ this.matches = (target.length == 1) ? >+ // For one-line strings, searching can be done simply by calling >+ // indexOf on the current line. >+ function() { >+ var match = cleanText(self.history.textAfter(self.line).slice(self.offset)).indexOf(string); >+ if (match > -1) >+ return {from: {node: self.line, offset: self.offset + match}, >+ to: {node: self.line, offset: self.offset + match + string.length}}; >+ } : >+ // Multi-line strings require internal iteration over lines, and >+ // some clunky checks to make sure the first match ends at the >+ // end of the line and the last match starts at the start. >+ function() { >+ var firstLine = cleanText(self.history.textAfter(self.line).slice(self.offset)); >+ var match = firstLine.lastIndexOf(target[0]); >+ if (match == -1 || match != firstLine.length - target[0].length) >+ return false; >+ var startOffset = self.offset + match; >+ >+ var line = self.history.nodeAfter(self.line); >+ for (var i = 1; i < target.length - 1; i++) { >+ if (cleanText(self.history.textAfter(line)) != target[i]) >+ return false; >+ line = self.history.nodeAfter(line); >+ } >+ >+ if (cleanText(self.history.textAfter(line)).indexOf(target[target.length - 1]) != 0) >+ return false; >+ >+ return {from: {node: self.line, offset: startOffset}, >+ to: {node: line, offset: target[target.length - 1].length}}; >+ }; >+ } >+ >+ SearchCursor.prototype = { >+ findNext: function() { >+ if (!this.valid) return false; >+ this.atOccurrence = false; >+ var self = this; >+ >+ // Go back to the start of the document if the current line is >+ // no longer in the DOM tree. >+ if (this.line && !this.line.parentNode) { >+ this.line = null; >+ this.offset = 0; >+ } >+ >+ // Set the cursor's position one character after the given >+ // position. >+ function saveAfter(pos) { >+ if (self.history.textAfter(pos.node).length < pos.offset) { >+ self.line = pos.node; >+ self.offset = pos.offset + 1; >+ } >+ else { >+ self.line = self.history.nodeAfter(pos.node); >+ self.offset = 0; >+ } >+ } >+ >+ while (true) { >+ var match = this.matches(); >+ // Found the search string. >+ if (match) { >+ this.atOccurrence = match; >+ saveAfter(match.from); >+ return true; >+ } >+ this.line = this.history.nodeAfter(this.line); >+ this.offset = 0; >+ // End of document. >+ if (!this.line) { >+ this.valid = false; >+ return false; >+ } >+ } >+ }, >+ >+ select: function() { >+ if (this.atOccurrence) { >+ select.setCursorPos(this.editor.container, this.atOccurrence.from, this.atOccurrence.to); >+ select.scrollToCursor(this.editor.container); >+ } >+ }, >+ >+ replace: function(string) { >+ if (this.atOccurrence) { >+ var end = this.editor.replaceRange(this.atOccurrence.from, this.atOccurrence.to, string); >+ this.line = end.node; >+ this.offset = end.offset; >+ this.atOccurrence = false; >+ } >+ } >+ }; >+ >+ // The Editor object is the main inside-the-iframe interface. >+ function Editor(options) { >+ this.options = options; >+ this.parent = parent; >+ this.doc = document; >+ this.container = this.doc.body; >+ this.win = window; >+ this.history = new History(this.container, options.undoDepth, options.undoDelay, >+ this, options.onChange); >+ var self = this; >+ >+ if (!Editor.Parser) >+ throw "No parser loaded."; >+ if (options.parserConfig && Editor.Parser.configure) >+ Editor.Parser.configure(options.parserConfig); >+ >+ if (!options.textWrapping) >+ this.container.style.whiteSpace = "pre"; >+ setWhiteSpaceModel(options.textWrapping); >+ >+ if (!options.readOnly) >+ select.setCursorPos(this.container, {node: null, offset: 0}); >+ >+ this.dirty = []; >+ if (options.content) >+ this.importCode(options.content); >+ else // FF acts weird when the editable document is completely empty >+ this.container.appendChild(this.doc.createElement("BR")); >+ >+ if (!options.readOnly) { >+ if (options.continuousScanning !== false) { >+ this.scanner = this.documentScanner(options.linesPerPass); >+ this.delayScanning(); >+ } >+ >+ function setEditable() { >+ // In IE, designMode frames can not run any scripts, so we use >+ // contentEditable instead. >+ if (document.body.contentEditable != undefined && /MSIE/.test(navigator.userAgent)) >+ document.body.contentEditable = "true"; >+ else >+ document.designMode = "on"; >+ } >+ >+ // If setting the frame editable fails, try again when the user >+ // focus it (happens when the frame is not visible on >+ // initialisation, in Firefox). >+ try { >+ setEditable(); >+ } >+ catch(e) { >+ var focusEvent = addEventHandler(document, "focus", function() { >+ focusEvent(); >+ setEditable(); >+ }, true); >+ } >+ >+ addEventHandler(document, "keydown", method(this, "keyDown")); >+ addEventHandler(document, "keypress", method(this, "keyPress")); >+ addEventHandler(document, "keyup", method(this, "keyUp")); >+ >+ function cursorActivity() {self.cursorActivity(false);} >+ addEventHandler(document.body, "paste", cursorActivity); >+ addEventHandler(document.body, "cut", cursorActivity); >+ addEventHandler(document.body, "mouseup", cursorActivity); >+ >+ if (this.options.autoMatchParens) >+ addEventHandler(document.body, "click", method(this, "scheduleParenBlink")); >+ } >+ } >+ >+ function isSafeKey(code) { >+ return (code >= 16 && code <= 18) || // shift, control, alt >+ (code >= 33 && code <= 40); // arrows, home, end >+ } >+ >+ Editor.prototype = { >+ // Import a piece of code into the editor. >+ importCode: function(code) { >+ this.history.push(null, null, asEditorLines(code)); >+ this.history.reset(); >+ }, >+ >+ // Extract the code from the editor. >+ getCode: function() { >+ if (!this.container.firstChild) >+ return ""; >+ >+ var accum = []; >+ select.markSelection(this.win); >+ forEach(traverseDOM(this.container.firstChild), method(accum, "push")); >+ select.selectMarked(); >+ return cleanText(accum.join("")); >+ }, >+ >+ checkLine: function(node) { >+ if (node === false || !(node == null || node.parentNode == this.container)) >+ throw parent.CodeMirror.InvalidLineHandle; >+ }, >+ >+ cursorPosition: function(start) { >+ if (start == null) start = true; >+ var pos = select.cursorPos(this.container, start); >+ if (pos) return {line: pos.node, character: pos.offset}; >+ else return {line: null, character: 0}; >+ }, >+ >+ firstLine: function() { >+ return null; >+ }, >+ >+ lastLine: function() { >+ if (this.container.lastChild) return startOfLine(this.container.lastChild); >+ else return null; >+ }, >+ >+ nextLine: function(line) { >+ this.checkLine(line); >+ var end = endOfLine(line ? line.nextSibling : this.container.firstChild, this.container); >+ return end || false; >+ }, >+ >+ prevLine: function(line) { >+ this.checkLine(line); >+ if (line == null) return false; >+ return startOfLine(line.previousSibling); >+ }, >+ >+ selectLines: function(startLine, startOffset, endLine, endOffset) { >+ this.checkLine(startLine); >+ var start = {node: startLine, offset: startOffset}, end = null; >+ if (endOffset !== undefined) { >+ this.checkLine(endLine); >+ end = {node: endLine, offset: endOffset}; >+ } >+ select.setCursorPos(this.container, start, end); >+ }, >+ >+ lineContent: function(line) { >+ this.checkLine(line); >+ var accum = []; >+ for (line = line ? line.nextSibling : this.container.firstChild; >+ line && line.nodeName != "BR"; line = line.nextSibling) >+ accum.push(line.innerText || line.textContent || line.nodeValue || ""); >+ return cleanText(accum.join("")); >+ }, >+ >+ setLineContent: function(line, content) { >+ this.history.commit(); >+ this.replaceRange({node: line, offset: 0}, >+ {node: line, offset: this.history.textAfter(line).length}, >+ content); >+ this.addDirtyNode(line); >+ this.scheduleHighlight(); >+ }, >+ >+ insertIntoLine: function(line, position, content) { >+ var before = null; >+ if (position == "end") { >+ before = endOfLine(line ? line.nextSibling : this.container.firstChild, this.container); >+ } >+ else { >+ for (var cur = line ? line.nextSibling : this.container.firstChild; cur; cur = cur.nextSibling) { >+ if (position == 0) { >+ before = cur; >+ break; >+ } >+ var text = (cur.innerText || cur.textContent || cur.nodeValue || ""); >+ if (text.length > position) { >+ before = cur.nextSibling; >+ content = text.slice(0, position) + content + text.slice(position); >+ removeElement(cur); >+ break; >+ } >+ position -= text.length; >+ } >+ } >+ >+ var lines = asEditorLines(content), doc = this.container.ownerDocument; >+ for (var i = 0; i < lines.length; i++) { >+ if (i > 0) this.container.insertBefore(doc.createElement("BR"), before); >+ this.container.insertBefore(makePartSpan(lines[i], doc), before); >+ } >+ this.addDirtyNode(line); >+ this.scheduleHighlight(); >+ }, >+ >+ // Retrieve the selected text. >+ selectedText: function() { >+ var h = this.history; >+ h.commit(); >+ >+ var start = select.cursorPos(this.container, true), >+ end = select.cursorPos(this.container, false); >+ if (!start || !end) return ""; >+ >+ if (start.node == end.node) >+ return h.textAfter(start.node).slice(start.offset, end.offset); >+ >+ var text = [h.textAfter(start.node).slice(start.offset)]; >+ for (pos = h.nodeAfter(start.node); pos != end.node; pos = h.nodeAfter(pos)) >+ text.push(h.textAfter(pos)); >+ text.push(h.textAfter(end.node).slice(0, end.offset)); >+ return cleanText(text.join("\n")); >+ }, >+ >+ // Replace the selection with another piece of text. >+ replaceSelection: function(text) { >+ this.history.commit(); >+ var start = select.cursorPos(this.container, true), >+ end = select.cursorPos(this.container, false); >+ if (!start || !end) return; >+ >+ end = this.replaceRange(start, end, text); >+ select.setCursorPos(this.container, start, end); >+ }, >+ >+ replaceRange: function(from, to, text) { >+ var lines = asEditorLines(text); >+ lines[0] = this.history.textAfter(from.node).slice(0, from.offset) + lines[0]; >+ var lastLine = lines[lines.length - 1]; >+ lines[lines.length - 1] = lastLine + this.history.textAfter(to.node).slice(to.offset); >+ var end = this.history.nodeAfter(to.node); >+ this.history.push(from.node, end, lines); >+ return {node: this.history.nodeBefore(end), >+ offset: lastLine.length}; >+ }, >+ >+ getSearchCursor: function(string, fromCursor) { >+ return new SearchCursor(this, string, fromCursor); >+ }, >+ >+ // Re-indent the whole buffer >+ reindent: function() { >+ if (this.container.firstChild) >+ this.indentRegion(null, this.container.lastChild); >+ }, >+ >+ // Intercept enter and tab, and assign their new functions. >+ keyDown: function(event) { >+ // Don't scan when the user is typing. >+ this.delayScanning(); >+ // Schedule a paren-highlight event, if configured. >+ if (this.options.autoMatchParens) >+ this.scheduleParenBlink(); >+ >+ if (event.keyCode == 13) { // enter >+ if (event.ctrlKey) { >+ this.reparseBuffer(); >+ } >+ else { >+ select.insertNewlineAtCursor(this.win); >+ this.indentAtCursor(); >+ select.scrollToCursor(this.container); >+ } >+ event.stop(); >+ } >+ else if (event.keyCode == 9) { // tab >+ this.handleTab(!event.ctrlKey && !event.shiftKey); >+ event.stop(); >+ } >+ else if (event.ctrlKey || event.metaKey) { >+ if (event.keyCode == 90 || event.keyCode == 8) { // Z, backspace >+ this.history.undo(); >+ event.stop(); >+ } >+ else if (event.keyCode == 89) { // Y >+ this.history.redo(); >+ event.stop(); >+ } >+ else if (event.keyCode == 83 && this.options.saveFunction) { // S >+ this.options.saveFunction(); >+ event.stop(); >+ } >+ } >+ }, >+ >+ // Check for characters that should re-indent the current line, >+ // and prevent Opera from handling enter and tab anyway. >+ keyPress: function(event) { >+ var electric = Editor.Parser.electricChars; >+ // Hack for Opera, and Firefox on OS X, in which stopping a >+ // keydown event does not prevent the associated keypress event >+ // from happening, so we have to cancel enter and tab again >+ // here. >+ if (event.code == 13 || event.code == 9) >+ event.stop(); >+ else if ((event.character == "[" || event.character == "]") && event.ctrlKey) >+ event.stop(), this.blinkParens(); >+ else if (electric && electric.indexOf(event.character) != -1) >+ this.parent.setTimeout(method(this, "indentAtCursor"), 0); >+ }, >+ >+ // Mark the node at the cursor dirty when a non-safe key is >+ // released. >+ keyUp: function(event) { >+ this.cursorActivity(isSafeKey(event.keyCode)); >+ }, >+ >+ // Indent the line following a given <br>, or null for the first >+ // line. If given a <br> element, this must have been highlighted >+ // so that it has an indentation method. Returns the whitespace >+ // element that has been modified or created (if any). >+ indentLineAfter: function(start, direction) { >+ // whiteSpace is the whitespace span at the start of the line, >+ // or null if there is no such node. >+ var whiteSpace = start ? start.nextSibling : this.container.firstChild; >+ if (whiteSpace && !hasClass(whiteSpace, "whitespace")) >+ whiteSpace = null; >+ >+ // Sometimes the start of the line can influence the correct >+ // indentation, so we retrieve it. >+ var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild); >+ var nextChars = (start && firstText && firstText.currentText) ? firstText.currentText : ""; >+ >+ // Ask the lexical context for the correct indentation, and >+ // compute how much this differs from the current indentation. >+ var newIndent = 0, curIndent = whiteSpace ? whiteSpace.currentText.length : 0; >+ if (start) newIndent = start.indentation(nextChars, curIndent, direction); >+ else if (Editor.Parser.firstIndentation) newIndent = Editor.Parser.firstIndentation(nextChars, curIndent, direction); >+ var indentDiff = newIndent - curIndent; >+ >+ // If there is too much, this is just a matter of shrinking a span. >+ if (indentDiff < 0) { >+ if (newIndent == 0) { >+ if (firstText) select.snapshotMove(whiteSpace.firstChild, firstText.firstChild, 0); >+ removeElement(whiteSpace); >+ whiteSpace = null; >+ } >+ else { >+ select.snapshotMove(whiteSpace.firstChild, whiteSpace.firstChild, indentDiff, true); >+ whiteSpace.currentText = safeWhiteSpace(newIndent); >+ whiteSpace.firstChild.nodeValue = whiteSpace.currentText; >+ } >+ } >+ // Not enough... >+ else if (indentDiff > 0) { >+ // If there is whitespace, we grow it. >+ if (whiteSpace) { >+ whiteSpace.currentText = safeWhiteSpace(newIndent); >+ whiteSpace.firstChild.nodeValue = whiteSpace.currentText; >+ } >+ // Otherwise, we have to add a new whitespace node. >+ else { >+ whiteSpace = makePartSpan(safeWhiteSpace(newIndent), this.doc); >+ whiteSpace.className = "whitespace"; >+ if (start) insertAfter(whiteSpace, start); >+ else this.container.insertBefore(whiteSpace, this.container.firstChild); >+ } >+ if (firstText) select.snapshotMove(firstText.firstChild, whiteSpace.firstChild, curIndent, false, true); >+ } >+ if (indentDiff != 0) this.addDirtyNode(start); >+ return whiteSpace; >+ }, >+ >+ // Re-highlight the selected part of the document. >+ highlightAtCursor: function() { >+ var pos = select.selectionTopNode(this.container, true); >+ var to = select.selectionTopNode(this.container, false); >+ if (pos === false || !to) return; >+ // Skip one node ahead to make sure the cursor itself is >+ // *inside* a highlighted line. >+ if (to.nextSibling) to = to.nextSibling; >+ >+ select.markSelection(this.win); >+ var toIsText = to.nodeType == 3; >+ if (!toIsText) to.dirty = true; >+ >+ // Highlight lines as long as to is in the document and dirty. >+ while (to.parentNode == this.container && (toIsText || to.dirty)) { >+ var result = this.highlight(pos, 1, true); >+ if (result) pos = result.node; >+ if (!result || result.left) break; >+ } >+ select.selectMarked(); >+ }, >+ >+ // When tab is pressed with text selected, the whole selection is >+ // re-indented, when nothing is selected, the line with the cursor >+ // is re-indented. >+ handleTab: function(direction) { >+ if (this.options.dumbTabs) { >+ select.insertTabAtCursor(this.win); >+ } >+ else if (!select.somethingSelected(this.win)) { >+ this.indentAtCursor(direction); >+ } >+ else { >+ var start = select.selectionTopNode(this.container, true), >+ end = select.selectionTopNode(this.container, false); >+ if (start === false || end === false) return; >+ this.indentRegion(start, end, direction); >+ } >+ }, >+ >+ // Delay (or initiate) the next paren blink event. >+ scheduleParenBlink: function() { >+ if (this.parenEvent) this.parent.clearTimeout(this.parenEvent); >+ this.parenEvent = this.parent.setTimeout(method(this, "blinkParens"), 300); >+ }, >+ >+ isNearParsedNode: function(node) { >+ var distance = 0; >+ while (node && (!node.parserFromHere || node.dirty)) { >+ distance += (node.textContent || node.innerText || "-").length; >+ if (distance > 800) return false; >+ node = node.previousSibling; >+ } >+ return true; >+ }, >+ >+ // Take the token before the cursor. If it contains a character in >+ // '()[]{}', search for the matching paren/brace/bracket, and >+ // highlight them in green for a moment, or red if no proper match >+ // was found. >+ blinkParens: function() { >+ // Clear the event property. >+ if (this.parenEvent) this.parent.clearTimeout(this.parenEvent); >+ this.parenEvent = null; >+ >+ // Extract a 'paren' from a piece of text. >+ function paren(node) { >+ if (node.currentText) { >+ var match = node.currentText.match(/^[\s\u00a0]*([\(\)\[\]{}])[\s\u00a0]*$/); >+ return match && match[1]; >+ } >+ } >+ // Determine the direction a paren is facing. >+ function forward(ch) { >+ return /[\(\[\{]/.test(ch); >+ } >+ >+ var ch, self = this, cursor = select.selectionTopNode(this.container, true); >+ if (!cursor || !this.isNearParsedNode(cursor)) return; >+ this.highlightAtCursor(); >+ cursor = select.selectionTopNode(this.container, true); >+ if (!cursor || !(ch = paren(cursor))) return; >+ // We only look for tokens with the same className. >+ var className = cursor.className, dir = forward(ch), match = matching[ch]; >+ >+ // Since parts of the document might not have been properly >+ // highlighted, and it is hard to know in advance which part we >+ // have to scan, we just try, and when we find dirty nodes we >+ // abort, parse them, and re-try. >+ function tryFindMatch() { >+ var stack = [], ch, ok = true;; >+ for (var runner = cursor; runner; runner = dir ? runner.nextSibling : runner.previousSibling) { >+ if (runner.className == className && runner.nodeName == "SPAN" && (ch = paren(runner))) { >+ if (forward(ch) == dir) >+ stack.push(ch); >+ else if (!stack.length) >+ ok = false; >+ else if (stack.pop() != matching[ch]) >+ ok = false; >+ if (!stack.length) break; >+ } >+ else if (runner.dirty || runner.nodeName != "SPAN" && runner.nodeName != "BR") { >+ return {node: runner, status: "dirty"}; >+ } >+ } >+ return {node: runner, status: runner && ok}; >+ } >+ // Temporarily give the relevant nodes a colour. >+ function blink(node, ok) { >+ node.style.fontWeight = "bold"; >+ node.style.color = ok ? "#8F8" : "#F88"; >+ self.parent.setTimeout(function() {node.style.fontWeight = ""; node.style.color = "";}, 500); >+ } >+ >+ while (true) { >+ var found = tryFindMatch(); >+ if (found.status == "dirty") { >+ this.highlight(found.node, 1); >+ // Needed because in some corner cases a highlight does not >+ // reach a node. >+ found.node.dirty = false; >+ continue; >+ } >+ else { >+ blink(cursor, found.status); >+ if (found.node) blink(found.node, found.status); >+ break; >+ } >+ } >+ }, >+ >+ // Adjust the amount of whitespace at the start of the line that >+ // the cursor is on so that it is indented properly. >+ indentAtCursor: function(direction) { >+ if (!this.container.firstChild) return; >+ // The line has to have up-to-date lexical information, so we >+ // highlight it first. >+ this.highlightAtCursor(); >+ var cursor = select.selectionTopNode(this.container, false); >+ // If we couldn't determine the place of the cursor, >+ // there's nothing to indent. >+ if (cursor === false) >+ return; >+ var lineStart = startOfLine(cursor); >+ var whiteSpace = this.indentLineAfter(lineStart, direction); >+ if (cursor == lineStart && whiteSpace) >+ cursor = whiteSpace; >+ // This means the indentation has probably messed up the cursor. >+ if (cursor == whiteSpace) >+ select.focusAfterNode(cursor, this.container); >+ }, >+ >+ // Indent all lines whose start falls inside of the current >+ // selection. >+ indentRegion: function(current, end, direction) { >+ select.markSelection(this.win); >+ current = startOfLine(current); >+ end = endOfLine(end, this.container); >+ >+ do { >+ this.highlight(current); >+ var hl = this.highlight(current, 1); >+ this.indentLineAfter(current, direction); >+ current = hl ? hl.node : null; >+ } while (current != end); >+ select.selectMarked(); >+ }, >+ >+ // Find the node that the cursor is in, mark it as dirty, and make >+ // sure a highlight pass is scheduled. >+ cursorActivity: function(safe) { >+ if (internetExplorer) { >+ this.container.createTextRange().execCommand("unlink"); >+ this.selectionSnapshot = select.selectionCoords(this.win); >+ } >+ >+ var activity = this.options.cursorActivity; >+ if (!safe || activity) { >+ var cursor = select.selectionTopNode(this.container, false); >+ if (cursor === false || !this.container.firstChild) return; >+ cursor = cursor || this.container.firstChild; >+ if (activity) activity(cursor); >+ if (!safe) { >+ this.scheduleHighlight(); >+ this.addDirtyNode(cursor); >+ } >+ } >+ }, >+ >+ reparseBuffer: function() { >+ forEach(this.container.childNodes, function(node) {node.dirty = true;}); >+ if (this.container.firstChild) >+ this.addDirtyNode(this.container.firstChild); >+ }, >+ >+ // Add a node to the set of dirty nodes, if it isn't already in >+ // there. >+ addDirtyNode: function(node) { >+ node = node || this.container.firstChild; >+ if (!node) return; >+ >+ for (var i = 0; i < this.dirty.length; i++) >+ if (this.dirty[i] == node) return; >+ >+ if (node.nodeType != 3) >+ node.dirty = true; >+ this.dirty.push(node); >+ }, >+ >+ // Cause a highlight pass to happen in options.passDelay >+ // milliseconds. Clear the existing timeout, if one exists. This >+ // way, the passes do not happen while the user is typing, and >+ // should as unobtrusive as possible. >+ scheduleHighlight: function() { >+ // Timeouts are routed through the parent window, because on >+ // some browsers designMode windows do not fire timeouts. >+ var self = this; >+ this.parent.clearTimeout(this.highlightTimeout); >+ this.highlightTimeout = this.parent.setTimeout(function(){self.highlightDirty();}, this.options.passDelay); >+ }, >+ >+ // Fetch one dirty node, and remove it from the dirty set. >+ getDirtyNode: function() { >+ while (this.dirty.length > 0) { >+ var found = this.dirty.pop(); >+ // IE8 sometimes throws an unexplainable 'invalid argument' >+ // exception for found.parentNode >+ try { >+ // If the node has been coloured in the meantime, or is no >+ // longer in the document, it should not be returned. >+ while (found && found.parentNode != this.container) >+ found = found.parentNode >+ if (found && (found.dirty || found.nodeType == 3)) >+ return found; >+ } catch (e) {} >+ } >+ return null; >+ }, >+ >+ // Pick dirty nodes, and highlight them, until >+ // options.linesPerPass lines have been highlighted. The highlight >+ // method will continue to next lines as long as it finds dirty >+ // nodes. It returns an object indicating the amount of lines >+ // left, and information about the place where it stopped. If >+ // there are dirty nodes left after this function has spent all >+ // its lines, it shedules another highlight to finish the job. >+ highlightDirty: function(force) { >+ var lines = force ? Infinity : this.options.linesPerPass; >+ if (!this.options.readOnly) select.markSelection(this.win); >+ var start; >+ while (lines > 0 && (start = this.getDirtyNode())){ >+ var result = this.highlight(start, lines); >+ if (result) { >+ lines = result.left; >+ if (result.node && result.dirty) >+ this.addDirtyNode(result.node); >+ } >+ } >+ if (!this.options.readOnly) select.selectMarked(); >+ if (start) >+ this.scheduleHighlight(); >+ return this.dirty.length == 0; >+ }, >+ >+ // Creates a function that, when called through a timeout, will >+ // continuously re-parse the document. >+ documentScanner: function(linesPer) { >+ var self = this, pos = null; >+ return function() { >+ // If the current node is no longer in the document... oh >+ // well, we start over. >+ if (pos && pos.parentNode != self.container) >+ pos = null; >+ select.markSelection(self.win); >+ var result = self.highlight(pos, linesPer, true); >+ select.selectMarked(); >+ var newPos = result ? (result.node && result.node.nextSibling) : null; >+ pos = (pos == newPos) ? null : newPos; >+ self.delayScanning(); >+ }; >+ }, >+ >+ // Starts the continuous scanning process for this document after >+ // a given interval. >+ delayScanning: function() { >+ if (this.scanner) { >+ this.parent.clearTimeout(this.documentScan); >+ this.documentScan = this.parent.setTimeout(this.scanner, this.options.continuousScanning); >+ } >+ }, >+ >+ // The function that does the actual highlighting/colouring (with >+ // help from the parser and the DOM normalizer). Its interface is >+ // rather overcomplicated, because it is used in different >+ // situations: ensuring that a certain line is highlighted, or >+ // highlighting up to X lines starting from a certain point. The >+ // 'from' argument gives the node at which it should start. If >+ // this is null, it will start at the beginning of the frame. When >+ // a number of lines is given with the 'lines' argument, it will >+ // colour no more than that amount. If at any time it comes across >+ // a 'clean' line (no dirty nodes), it will stop, except when >+ // 'cleanLines' is true. >+ highlight: function(from, lines, cleanLines){ >+ var container = this.container, self = this, active = this.options.activeTokens, origFrom = from; >+ >+ if (!container.firstChild) >+ return; >+ // lines given as null means 'make sure this BR node has up to date parser information' >+ if (lines == null) { >+ if (!from) return; >+ else from = from.previousSibling; >+ } >+ // Backtrack to the first node before from that has a partial >+ // parse stored. >+ while (from && (!from.parserFromHere || from.dirty)) >+ from = from.previousSibling; >+ // If we are at the end of the document, do nothing. >+ if (from && !from.nextSibling) >+ return; >+ >+ // Check whether a part (<span> node) and the corresponding token >+ // match. >+ function correctPart(token, part){ >+ return !part.reduced && part.currentText == token.value && part.className == token.style; >+ } >+ // Shorten the text associated with a part by chopping off >+ // characters from the front. Note that only the currentText >+ // property gets changed. For efficiency reasons, we leave the >+ // nodeValue alone -- we set the reduced flag to indicate that >+ // this part must be replaced. >+ function shortenPart(part, minus){ >+ part.currentText = part.currentText.substring(minus); >+ part.reduced = true; >+ } >+ // Create a part corresponding to a given token. >+ function tokenPart(token){ >+ var part = makePartSpan(token.value, self.doc); >+ part.className = token.style; >+ return part; >+ } >+ >+ // Get the token stream. If from is null, we start with a new >+ // parser from the start of the frame, otherwise a partial parse >+ // is resumed. >+ var traversal = traverseDOM(from ? from.nextSibling : container.firstChild), >+ stream = stringStream(traversal), >+ parsed = from ? from.parserFromHere(stream) : Editor.Parser.make(stream); >+ >+ // parts is an interface to make it possible to 'delay' fetching >+ // the next DOM node until we are completely done with the one >+ // before it. This is necessary because often the next node is >+ // not yet available when we want to proceed past the current >+ // one. >+ var parts = { >+ current: null, >+ // Fetch current node. >+ get: function(){ >+ if (!this.current) >+ this.current = traversal.nodes.shift(); >+ return this.current; >+ }, >+ // Advance to the next part (do not fetch it yet). >+ next: function(){ >+ this.current = null; >+ }, >+ // Remove the current part from the DOM tree, and move to the >+ // next. >+ remove: function(){ >+ container.removeChild(this.get()); >+ this.current = null; >+ }, >+ // Advance to the next part that is not empty, discarding empty >+ // parts. >+ getNonEmpty: function(){ >+ var part = this.get(); >+ // Allow empty nodes when they are alone on a line, needed >+ // for the FF cursor bug workaround (see select.js, >+ // insertNewlineAtCursor). >+ while (part && part.nodeName == "SPAN" && part.currentText == "") { >+ var old = part; >+ this.remove(); >+ part = this.get(); >+ // Adjust selection information, if any. See select.js for details. >+ select.snapshotMove(old.firstChild, part.firstChild || part, 0); >+ } >+ return part; >+ } >+ }; >+ >+ var lineDirty = false, prevLineDirty = true, lineNodes = 0; >+ >+ // This forEach loops over the tokens from the parsed stream, and >+ // at the same time uses the parts object to proceed through the >+ // corresponding DOM nodes. >+ forEach(parsed, function(token){ >+ var part = parts.getNonEmpty(); >+ >+ if (token.value == "\n"){ >+ // The idea of the two streams actually staying synchronized >+ // is such a long shot that we explicitly check. >+ if (part.nodeName != "BR") >+ throw "Parser out of sync. Expected BR."; >+ >+ if (part.dirty || !part.indentation) lineDirty = true; >+ if (lineDirty) self.history.touch(from); >+ from = part; >+ >+ // Every <br> gets a copy of the parser state and a lexical >+ // context assigned to it. The first is used to be able to >+ // later resume parsing from this point, the second is used >+ // for indentation. >+ part.parserFromHere = parsed.copy(); >+ part.indentation = token.indentation; >+ part.dirty = false; >+ >+ // No line argument passed means 'go at least until this node'. >+ if (lines == null && part == origFrom) throw StopIteration; >+ >+ // A clean line with more than one node means we are done. >+ // Throwing a StopIteration is the way to break out of a >+ // MochiKit forEach loop. >+ if ((lines !== undefined && --lines <= 0) || (!lineDirty && !prevLineDirty && lineNodes > 1 && !cleanLines)) >+ throw StopIteration; >+ prevLineDirty = lineDirty; lineDirty = false; lineNodes = 0; >+ parts.next(); >+ } >+ else { >+ if (part.nodeName != "SPAN") >+ throw "Parser out of sync. Expected SPAN."; >+ if (part.dirty) >+ lineDirty = true; >+ lineNodes++; >+ >+ // If the part matches the token, we can leave it alone. >+ if (correctPart(token, part)){ >+ part.dirty = false; >+ parts.next(); >+ } >+ // Otherwise, we have to fix it. >+ else { >+ lineDirty = true; >+ // Insert the correct part. >+ var newPart = tokenPart(token); >+ container.insertBefore(newPart, part); >+ if (active) active(newPart, token, self); >+ var tokensize = token.value.length; >+ var offset = 0; >+ // Eat up parts until the text for this token has been >+ // removed, adjusting the stored selection info (see >+ // select.js) in the process. >+ while (tokensize > 0) { >+ part = parts.get(); >+ var partsize = part.currentText.length; >+ select.snapshotReplaceNode(part.firstChild, newPart.firstChild, tokensize, offset); >+ if (partsize > tokensize){ >+ shortenPart(part, tokensize); >+ tokensize = 0; >+ } >+ else { >+ tokensize -= partsize; >+ offset += partsize; >+ parts.remove(); >+ } >+ } >+ } >+ } >+ }); >+ if (lineDirty) this.history.touch(from); >+ >+ // The function returns some status information that is used by >+ // hightlightDirty to determine whether and where it has to >+ // continue. >+ return {left: lines, >+ node: parts.get(), >+ dirty: lineDirty}; >+ } >+ }; >+ >+ return Editor; >+})(); >+ >+addEventHandler(window, "load", function() { >+ var CodeMirror = window.frameElement.CodeMirror; >+ CodeMirror.editor = new Editor(CodeMirror.options); >+ if (CodeMirror.options.initCallback) { >+ this.parent.setTimeout(function(){ >+ CodeMirror.options.initCallback(CodeMirror); >+ }, 0); >+ } >+}); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/mirrorframe.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/mirrorframe.js >new file mode 100644 >index 0000000..7f6ad1a >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/mirrorframe.js >@@ -0,0 +1,81 @@ >+/* Demonstration of embedding CodeMirror in a bigger application. The >+ * interface defined here is a mess of prompts and confirms, and >+ * should probably not be used in a real project. >+ */ >+ >+function MirrorFrame(place, options) { >+ this.home = document.createElement("DIV"); >+ if (place.appendChild) >+ place.appendChild(this.home); >+ else >+ place(this.home); >+ >+ var self = this; >+ function makeButton(name, action) { >+ var button = document.createElement("INPUT"); >+ button.type = "button"; >+ button.value = name; >+ self.home.appendChild(button); >+ button.onclick = function(){self[action].call(self);}; >+ } >+ >+ makeButton("Search", "search"); >+ makeButton("Replace", "replace"); >+ makeButton("Current line", "line"); >+ makeButton("Jump to line", "jump"); >+ makeButton("Insert constructor", "macro"); >+ makeButton("Indent all", "reindent"); >+ >+ this.mirror = new CodeMirror(this.home, options); >+} >+ >+MirrorFrame.prototype = { >+ search: function() { >+ var text = prompt("Enter search term:", ""); >+ if (!text) return; >+ >+ var first = true; >+ do { >+ var cursor = this.mirror.getSearchCursor(text, first); >+ first = false; >+ while (cursor.findNext()) { >+ cursor.select(); >+ if (!confirm("Search again?")) >+ return; >+ } >+ } while (confirm("End of document reached. Start over?")); >+ }, >+ >+ replace: function() { >+ // This is a replace-all, but it is possible to implement a >+ // prompting replace. >+ var from = prompt("Enter search string:", ""), to; >+ if (from) to = prompt("What should it be replaced with?", ""); >+ if (to == null) return; >+ >+ var cursor = this.mirror.getSearchCursor(from, false); >+ while (cursor.findNext()) >+ cursor.replace(to); >+ }, >+ >+ jump: function() { >+ var line = prompt("Jump to line:", ""); >+ if (line && !isNaN(Number(line))) >+ this.mirror.jumpToLine(Number(line)); >+ }, >+ >+ line: function() { >+ alert("The cursor is currently at line " + this.mirror.currentLine()); >+ this.mirror.focus(); >+ }, >+ >+ macro: function() { >+ var name = prompt("Name your constructor:", ""); >+ if (name) >+ this.mirror.replaceSelection("function " + name + "() {\n \n}\n\n" + name + ".prototype = {\n \n};\n"); >+ }, >+ >+ reindent: function() { >+ this.mirror.reindent(); >+ } >+}; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsecss.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsecss.js >new file mode 100644 >index 0000000..c22f295 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsecss.js >@@ -0,0 +1,155 @@ >+/* Simple parser for CSS */ >+ >+var CSSParser = Editor.Parser = (function() { >+ var tokenizeCSS = (function() { >+ function normal(source, setState) { >+ var ch = source.next(); >+ if (ch == "@") { >+ source.nextWhile(matcher(/\w/)); >+ return "css-at"; >+ } >+ else if (ch == "/" && source.equals("*")) { >+ setState(inCComment); >+ return null; >+ } >+ else if (ch == "<" && source.equals("!")) { >+ setState(inSGMLComment); >+ return null; >+ } >+ else if (ch == "=") { >+ return "css-compare"; >+ } >+ else if (source.equals("=") && (ch == "~" || ch == "|")) { >+ source.next(); >+ return "css-compare"; >+ } >+ else if (ch == "\"" || ch == "'") { >+ setState(inString(ch)); >+ return null; >+ } >+ else if (ch == "#") { >+ source.nextWhile(matcher(/\w/)); >+ return "css-hash"; >+ } >+ else if (ch == "!") { >+ source.nextWhile(matcher(/[ \t]/)); >+ source.nextWhile(matcher(/\w/)); >+ return "css-important"; >+ } >+ else if (/\d/.test(ch)) { >+ source.nextWhile(matcher(/[\w.%]/)); >+ return "css-unit"; >+ } >+ else if (/[,.+>*\/]/.test(ch)) { >+ return "css-select-op"; >+ } >+ else if (/[;{}:\[\]]/.test(ch)) { >+ return "css-punctuation"; >+ } >+ else { >+ source.nextWhile(matcher(/[\w\\\-_]/)); >+ return "css-identifier"; >+ } >+ } >+ >+ function inCComment(source, setState) { >+ var maybeEnd = false; >+ while (!source.endOfLine()) { >+ var ch = source.next(); >+ if (maybeEnd && ch == "/") { >+ setState(normal); >+ break; >+ } >+ maybeEnd = (ch == "*"); >+ } >+ return "css-comment"; >+ } >+ >+ function inSGMLComment(source, setState) { >+ var dashes = 0; >+ while (!source.endOfLine()) { >+ var ch = source.next(); >+ if (dashes >= 2 && ch == ">") { >+ setState(normal); >+ break; >+ } >+ dashes = (ch == "-") ? dashes + 1 : 0; >+ } >+ return "css-comment"; >+ } >+ >+ function inString(quote) { >+ return function(source, setState) { >+ var escaped = false; >+ while (!source.endOfLine()) { >+ var ch = source.next(); >+ if (ch == quote && !escaped) >+ break; >+ escaped = !escaped && ch == "\\"; >+ } >+ if (!escaped) >+ setState(normal); >+ return "css-string"; >+ }; >+ } >+ >+ return function(source, startState) { >+ return tokenizer(source, startState || normal); >+ }; >+ })(); >+ >+ function indentCSS(inBraces, inRule, base) { >+ return function(nextChars) { >+ if (!inBraces || /^\}/.test(nextChars)) return base; >+ else if (inRule) return base + 4; >+ else return base + 2; >+ }; >+ } >+ >+ // This is a very simplistic parser -- since CSS does not really >+ // nest, it works acceptably well, but some nicer colouroing could >+ // be provided with a more complicated parser. >+ function parseCSS(source, basecolumn) { >+ basecolumn = basecolumn || 0; >+ var tokens = tokenizeCSS(source); >+ var inBraces = false, inRule = false; >+ >+ var iter = { >+ next: function() { >+ var token = tokens.next(), style = token.style, content = token.content; >+ >+ if (style == "css-identifier" && inRule) >+ token.style = "css-value"; >+ if (style == "css-hash") >+ token.style = inRule ? "css-colorcode" : "css-identifier"; >+ >+ if (content == "\n") >+ token.indentation = indentCSS(inBraces, inRule, basecolumn); >+ >+ if (content == "{") >+ inBraces = true; >+ else if (content == "}") >+ inBraces = inRule = false; >+ else if (inBraces && content == ";") >+ inRule = false; >+ else if (inBraces && style != "css-comment" && style != "whitespace") >+ inRule = true; >+ >+ return token; >+ }, >+ >+ copy: function() { >+ var _inBraces = inBraces, _inRule = inRule, _tokenState = tokens.state; >+ return function(source) { >+ tokens = tokenizeCSS(source, _tokenState); >+ inBraces = _inBraces; >+ inRule = _inRule; >+ return iter; >+ }; >+ } >+ }; >+ return iter; >+ } >+ >+ return {make: parseCSS, electricChars: "}"}; >+})(); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsehtmlmixed.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsehtmlmixed.js >new file mode 100644 >index 0000000..166967f >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsehtmlmixed.js >@@ -0,0 +1,73 @@ >+var HTMLMixedParser = Editor.Parser = (function() { >+ if (!(CSSParser && JSParser && XMLParser)) >+ throw new Error("CSS, JS, and XML parsers must be loaded for HTML mixed mode to work."); >+ XMLParser.configure({useHTMLKludges: true}); >+ >+ function parseMixed(stream) { >+ var htmlParser = XMLParser.make(stream), localParser = null, inTag = false; >+ var iter = {next: top, copy: copy}; >+ >+ function top() { >+ var token = htmlParser.next(); >+ if (token.content == "<") >+ inTag = true; >+ else if (token.style == "xml-tagname" && inTag === true) >+ inTag = token.content.toLowerCase(); >+ else if (token.content == ">") { >+ if (inTag == "script") >+ iter.next = local(JSParser, "</script"); >+ else if (inTag == "style") >+ iter.next = local(CSSParser, "</style"); >+ inTag = false; >+ } >+ return token; >+ } >+ function local(parser, tag) { >+ var baseIndent = htmlParser.indentation(); >+ localParser = parser.make(stream, baseIndent + 2); >+ return function() { >+ if (stream.lookAhead(tag, false, false, true)) { >+ localParser = null; >+ iter.next = top; >+ return top(); >+ } >+ >+ var token = localParser.next(); >+ var lt = token.value.lastIndexOf("<"), sz = Math.min(token.value.length - lt, tag.length); >+ if (lt != -1 && token.value.slice(lt, lt + sz).toLowerCase() == tag.slice(0, sz) && >+ stream.lookAhead(tag.slice(sz), false, false, true)) { >+ stream.push(token.value.slice(lt)); >+ token.value = token.value.slice(0, lt); >+ } >+ >+ if (token.indentation) { >+ var oldIndent = token.indentation; >+ token.indentation = function(chars) { >+ if (chars == "</") >+ return baseIndent; >+ else >+ return oldIndent(chars); >+ } >+ } >+ >+ return token; >+ }; >+ } >+ >+ function copy() { >+ var _html = htmlParser.copy(), _local = localParser && localParser.copy(), >+ _next = iter.next, _inTag = inTag; >+ return function(_stream) { >+ stream = _stream; >+ htmlParser = _html(_stream); >+ localParser = _local && _local(_stream); >+ iter.next = _next; >+ inTag = _inTag; >+ return iter; >+ }; >+ } >+ return iter; >+ } >+ >+ return {make: parseMixed, electricChars: "{}/"}; >+})(); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsejavascript.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsejavascript.js >new file mode 100644 >index 0000000..16ddf2b >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsejavascript.js >@@ -0,0 +1,322 @@ >+/* Parse function for JavaScript. Makes use of the tokenizer from >+ * tokenizejavascript.js. Note that your parsers do not have to be >+ * this complicated -- if you don't want to recognize local variables, >+ * in many languages it is enough to just look for braces, semicolons, >+ * parentheses, etc, and know when you are inside a string or comment. >+ * >+ * See manual.html for more info about the parser interface. >+ */ >+ >+var JSParser = Editor.Parser = (function() { >+ // Token types that can be considered to be atoms. >+ var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; >+ // Constructor for the lexical context objects. >+ function JSLexical(indented, column, type, align, prev) { >+ // indentation at start of this line >+ this.indented = indented; >+ // column at which this scope was opened >+ this.column = column; >+ // type of scope ('vardef', 'stat' (statement), 'form' (special form), '[', '{', or '(') >+ this.type = type; >+ // '[', '{', or '(' blocks that have any text after their opening >+ // character are said to be 'aligned' -- any lines below are >+ // indented all the way to the opening character. >+ if (align != null) >+ this.align = align; >+ // Parent scope, if any. >+ this.prev = prev; >+ } >+ // My favourite JavaScript indentation rules. >+ function indentJS(lexical) { >+ return function(firstChars) { >+ var firstChar = firstChars && firstChars.charAt(0); >+ var closing = firstChar == lexical.type; >+ if (lexical.type == "vardef") >+ return lexical.indented + 4; >+ else if (lexical.type == "form" && firstChar == "{") >+ return lexical.indented; >+ else if (lexical.type == "stat" || lexical.type == "form") >+ return lexical.indented + 2; >+ else if (lexical.align) >+ return lexical.column - (closing ? 1 : 0); >+ else >+ return lexical.indented + (closing ? 0 : 2); >+ }; >+ } >+ >+ // The parser-iterator-producing function itself. >+ function parseJS(input, basecolumn) { >+ // Wrap the input in a token stream >+ var tokens = tokenizeJavaScript(input); >+ // The parser state. cc is a stack of actions that have to be >+ // performed to finish the current statement. For example we might >+ // know that we still need to find a closing parenthesis and a >+ // semicolon. Actions at the end of the stack go first. It is >+ // initialized with an infinitely looping action that consumes >+ // whole statements. >+ var cc = [statements]; >+ // Context contains information about the current local scope, the >+ // variables defined in that, and the scopes above it. >+ var context = null; >+ // The lexical scope, used mostly for indentation. >+ var lexical = new JSLexical((basecolumn || 0) - 2, 0, "block", false); >+ // Current column, and the indentation at the start of the current >+ // line. Used to create lexical scope objects. >+ var column = 0; >+ var indented = 0; >+ // Variables which are used by the mark, cont, and pass functions >+ // below to communicate with the driver loop in the 'next' >+ // function. >+ var consume, marked; >+ >+ // The iterator object. >+ var parser = {next: next, copy: copy}; >+ >+ function next(){ >+ // Start by performing any 'lexical' actions (adjusting the >+ // lexical variable), or the operations below will be working >+ // with the wrong lexical state. >+ while(cc[cc.length - 1].lex) >+ cc.pop()(); >+ >+ // Fetch a token. >+ var token = tokens.next(); >+ >+ // Adjust column and indented. >+ if (token.type == "whitespace" && column == 0) >+ indented = token.value.length; >+ column += token.value.length; >+ if (token.content == "\n"){ >+ indented = column = 0; >+ // If the lexical scope's align property is still undefined at >+ // the end of the line, it is an un-aligned scope. >+ if (!("align" in lexical)) >+ lexical.align = false; >+ // Newline tokens get an indentation function associated with >+ // them. >+ token.indentation = indentJS(lexical); >+ } >+ // No more processing for meaningless tokens. >+ if (token.type == "whitespace" || token.type == "comment") >+ return token; >+ // When a meaningful token is found and the lexical scope's >+ // align is undefined, it is an aligned scope. >+ if (!("align" in lexical)) >+ lexical.align = true; >+ >+ // Execute actions until one 'consumes' the token and we can >+ // return it. >+ while(true) { >+ consume = marked = false; >+ // Take and execute the topmost action. >+ cc.pop()(token.type, token.content); >+ if (consume){ >+ // Marked is used to change the style of the current token. >+ if (marked) >+ token.style = marked; >+ // Here we differentiate between local and global variables. >+ else if (token.type == "variable" && inScope(token.content)) >+ token.style = "js-localvariable"; >+ return token; >+ } >+ } >+ } >+ >+ // This makes a copy of the parser state. It stores all the >+ // stateful variables in a closure, and returns a function that >+ // will restore them when called with a new input stream. Note >+ // that the cc array has to be copied, because it is contantly >+ // being modified. Lexical objects are not mutated, and context >+ // objects are not mutated in a harmful way, so they can be shared >+ // between runs of the parser. >+ function copy(){ >+ var _context = context, _lexical = lexical, _cc = cc.concat([]), _tokenState = tokens.state; >+ >+ return function copyParser(input){ >+ context = _context; >+ lexical = _lexical; >+ cc = _cc.concat([]); // copies the array >+ column = indented = 0; >+ tokens = tokenizeJavaScript(input, _tokenState); >+ return parser; >+ }; >+ } >+ >+ // Helper function for pushing a number of actions onto the cc >+ // stack in reverse order. >+ function push(fs){ >+ for (var i = fs.length - 1; i >= 0; i--) >+ cc.push(fs[i]); >+ } >+ // cont and pass are used by the action functions to add other >+ // actions to the stack. cont will cause the current token to be >+ // consumed, pass will leave it for the next action. >+ function cont(){ >+ push(arguments); >+ consume = true; >+ } >+ function pass(){ >+ push(arguments); >+ consume = false; >+ } >+ // Used to change the style of the current token. >+ function mark(style){ >+ marked = style; >+ } >+ >+ // Push a new scope. Will automatically link the current scope. >+ function pushcontext(){ >+ context = {prev: context, vars: {"this": true, "arguments": true}}; >+ } >+ // Pop off the current scope. >+ function popcontext(){ >+ context = context.prev; >+ } >+ // Register a variable in the current scope. >+ function register(varname){ >+ if (context){ >+ mark("js-variabledef"); >+ context.vars[varname] = true; >+ } >+ } >+ // Check whether a variable is defined in the current scope. >+ function inScope(varname){ >+ var cursor = context; >+ while (cursor) { >+ if (cursor.vars[varname]) >+ return true; >+ cursor = cursor.prev; >+ } >+ return false; >+ } >+ >+ // Push a new lexical context of the given type. >+ function pushlex(type){ >+ var result = function(){ >+ lexical = new JSLexical(indented, column, type, null, lexical) >+ }; >+ result.lex = true; >+ return result; >+ } >+ // Pop off the current lexical context. >+ function poplex(){ >+ lexical = lexical.prev; >+ } >+ poplex.lex = true; >+ // The 'lex' flag on these actions is used by the 'next' function >+ // to know they can (and have to) be ran before moving on to the >+ // next token. >+ >+ // Creates an action that discards tokens until it finds one of >+ // the given type. >+ function expect(wanted){ >+ return function expecting(type){ >+ if (type == wanted) cont(); >+ else cont(arguments.callee); >+ }; >+ } >+ >+ // Looks for a statement, and then calls itself. >+ function statements(type){ >+ return pass(statement, statements); >+ } >+ // Dispatches various types of statements based on the type of the >+ // current token. >+ function statement(type){ >+ if (type == "var") cont(pushlex("vardef"), vardef1, expect(";"), poplex); >+ else if (type == "keyword a") cont(pushlex("form"), expression, statement, poplex); >+ else if (type == "keyword b") cont(pushlex("form"), statement, poplex); >+ else if (type == "{") cont(pushlex("}"), block, poplex); >+ else if (type == "function") cont(functiondef); >+ else if (type == "for") cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), poplex, statement, poplex); >+ else if (type == "variable") cont(pushlex("stat"), maybelabel); >+ else if (type == "case") cont(expression, expect(":")); >+ else if (type == "default") cont(expect(":")); >+ else if (type == "catch") cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext); >+ else pass(pushlex("stat"), expression, expect(";"), poplex); >+ } >+ // Dispatch expression types. >+ function expression(type){ >+ if (atomicTypes.hasOwnProperty(type)) cont(maybeoperator); >+ else if (type == "function") cont(functiondef); >+ else if (type == "keyword c") cont(expression); >+ else if (type == "(") cont(pushlex(")"), expression, expect(")"), poplex); >+ else if (type == "operator") cont(expression); >+ else if (type == "[") cont(pushlex("]"), commasep(expression), expect("]"), poplex); >+ else if (type == "{") cont(pushlex("}"), commasep(objprop), expect("}"), poplex); >+ } >+ // Called for places where operators, function calls, or >+ // subscripts are valid. Will skip on to the next action if none >+ // is found. >+ function maybeoperator(type){ >+ if (type == "operator") cont(expression); >+ else if (type == "(") cont(pushlex(")"), expression, commasep(expression), expect(")"), poplex, maybeoperator); >+ else if (type == ".") cont(property, maybeoperator); >+ else if (type == "[") cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); >+ } >+ // When a statement starts with a variable name, it might be a >+ // label. If no colon follows, it's a regular statement. >+ function maybelabel(type){ >+ if (type == ":") cont(poplex, statement); >+ else pass(maybeoperator, expect(";"), poplex); >+ } >+ // Property names need to have their style adjusted -- the >+ // tokenizer thinks they are variables. >+ function property(type){ >+ if (type == "variable") {mark("js-property"); cont();} >+ } >+ // This parses a property and its value in an object literal. >+ function objprop(type){ >+ if (type == "variable") mark("js-property"); >+ if (atomicTypes.hasOwnProperty(type)) cont(expect(":"), expression); >+ } >+ // Parses a comma-separated list of the things that are recognized >+ // by the 'what' argument. >+ function commasep(what){ >+ function proceed(type) { >+ if (type == ",") cont(what, proceed); >+ }; >+ return function commaSeparated() { >+ pass(what, proceed); >+ }; >+ } >+ // Look for statements until a closing brace is found. >+ function block(type){ >+ if (type == "}") cont(); >+ else pass(statement, block); >+ } >+ // Variable definitions are split into two actions -- 1 looks for >+ // a name or the end of the definition, 2 looks for an '=' sign or >+ // a comma. >+ function vardef1(type, value){ >+ if (type == "variable"){register(value); cont(vardef2);} >+ else cont(); >+ } >+ function vardef2(type){ >+ if (type == "operator") cont(expression, vardef2); >+ else if (type == ",") cont(vardef1); >+ } >+ // For loops. >+ function forspec1(type, value){ >+ if (type == "var") cont(vardef1, forspec2); >+ else cont(expression, forspec2); >+ } >+ function forspec2(type){ >+ if (type == ",") cont(forspec1); >+ if (type == ";") cont(expression, expect(";"), expression); >+ } >+ // A function definition creates a new context, and the variables >+ // in its argument list have to be added to this context. >+ function functiondef(type, value){ >+ if (type == "variable"){register(value); cont(functiondef);} >+ else if (type == "(") cont(pushcontext, commasep(funarg), expect(")"), statement, popcontext); >+ } >+ function funarg(type, value){ >+ if (type == "variable"){register(value); cont();} >+ } >+ >+ return parser; >+ } >+ >+ return {make: parseJS, electricChars: "{}"}; >+})(); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsemarc.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsemarc.js >new file mode 100644 >index 0000000..00ee23d >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsemarc.js >@@ -0,0 +1,102 @@ >+Editor.Parser = (function() { >+ function isWhiteSpace(ch) { >+ // The messy regexp is because IE's regexp matcher is of the >+ // opinion that non-breaking spaces are no whitespace. >+ return ch != "\n" && /^[\s\u00a0]*$/.test(ch); >+ } >+ >+ var tokenizeMARC = (function() { >+ function normal(source, setState) { >+ var ch = source.next(); >+ if (ch == '$' || ch == '|') { >+ if (source.applies(matcher(/[a-z0-9]/)) && source.next() && source.applies(isWhiteSpace)) { >+ return 'marc-subfield'; >+ } else { >+ return 'marc-word'; >+ } >+ } else if (ch.match(/[0-9]/)) { >+ // This and the next block are muddled because tags are ^[0-9]{3} and indicators are [0-9_]{2}. >+ var length = 1; >+ while (source.applies(matcher(/[0-9]/))) { >+ source.next(); >+ length++; >+ } >+ >+ if (length == 1 && source.lookAhead('_')) { >+ source.next(); >+ return 'marc-indicator'; >+ } >+ >+ if (source.applies(isWhiteSpace) && length == 2) { >+ return 'marc-indicator'; >+ } else if (source.applies(isWhiteSpace) && length == 3) { >+ return 'marc-tag'; >+ } else { >+ return 'marc-word'; >+ } >+ } else if (ch == '_') { >+ if (source.applies(matcher(/[0-9_]/)) && source.next() && source.applies(isWhiteSpace)) { >+ return 'marc-indicator'; >+ } else { >+ return 'marc-word'; >+ } >+ } else { >+ source.nextWhile(matcher(/[^\$|\n]/)); >+ return 'marc-word'; >+ } >+ } >+ >+ return function(source, startState) { >+ return tokenizer(source, startState || normal); >+ }; >+ })(); >+ >+ function indentMARC(context) { >+ return function(nextChars) { >+ return 0; >+ }; >+ } >+ >+ function parseMARC(source) { >+ var tokens = tokenizeMARC(source); >+ var context = null, indent = 0, col = 0; >+ >+ var iter = { >+ next: function() { >+ var token = tokens.next(), type = token.style, content = token.content, width = token.value.length; >+ >+ if (content == "\n") { >+ token.indentation = indentMARC(context); >+ indent = col = 0; >+ if (context && context.align === null) { context.align = false } >+ } else if (type == "whitespace" && col === 0) { >+ indent = width; >+ } else if (type != "sp-comment" && context && context.align === null) { >+ context.align = true; >+ } >+ >+ if ((type == 'marc-tag' && col != 0) || (type == 'marc-indicator' && col != 4)) { >+ token.style = 'marc-word'; >+ } >+ >+ if (content != "\n") { col += width } >+ >+ return token; >+ }, >+ >+ copy: function() { >+ var _context = context, _indent = indent, _col = col, _tokenState = tokens.state; >+ return function(source) { >+ tokens = tokenizeMARC(source, _tokenState); >+ context = _context; >+ indent = _indent; >+ col = _col; >+ return iter; >+ }; >+ } >+ }; >+ return iter; >+ } >+ >+ return {make: parseMARC, electricChars: "}]"}; >+})(); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsesparql.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsesparql.js >new file mode 100644 >index 0000000..58ced1c >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsesparql.js >@@ -0,0 +1,162 @@ >+Editor.Parser = (function() { >+ function wordRegexp(words) { >+ return new RegExp("^(?:" + words.join("|") + ")$", "i"); >+ } >+ var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", >+ "isblank", "isliteral", "union", "a"]); >+ var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe", >+ "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional", >+ "graph", "by", "asc", "desc", ]); >+ var operatorChars = /[*+\-<>=&|]/; >+ >+ var tokenizeSparql = (function() { >+ function normal(source, setState) { >+ var ch = source.next(); >+ if (ch == "$" || ch == "?") { >+ source.nextWhile(matcher(/[\w\d]/)); >+ return "sp-var"; >+ } >+ else if (ch == "<" && !source.applies(matcher(/[\s\u00a0=]/))) { >+ source.nextWhile(matcher(/[^\s\u00a0>]/)); >+ if (source.equals(">")) source.next(); >+ return "sp-uri"; >+ } >+ else if (ch == "\"" || ch == "'") { >+ setState(inLiteral(ch)); >+ return null; >+ } >+ else if (/[{}\(\),\.;\[\]]/.test(ch)) { >+ return "sp-punc"; >+ } >+ else if (ch == "#") { >+ while (!source.endOfLine()) source.next(); >+ return "sp-comment"; >+ } >+ else if (operatorChars.test(ch)) { >+ source.nextWhile(matcher(operatorChars)); >+ return "sp-operator"; >+ } >+ else if (ch == ":") { >+ source.nextWhile(matcher(/[\w\d\._\-]/)); >+ return "sp-prefixed"; >+ } >+ else { >+ source.nextWhile(matcher(/[_\w\d]/)); >+ if (source.equals(":")) { >+ source.next(); >+ source.nextWhile(matcher(/[\w\d_\-]/)); >+ return "sp-prefixed"; >+ } >+ var word = source.get(), type; >+ if (ops.test(word)) >+ type = "sp-operator"; >+ else if (keywords.test(word)) >+ type = "sp-keyword"; >+ else >+ type = "sp-word"; >+ return {style: type, content: word}; >+ } >+ } >+ >+ function inLiteral(quote) { >+ return function(source, setState) { >+ var escaped = false; >+ while (!source.endOfLine()) { >+ var ch = source.next(); >+ if (ch == quote && !escaped) { >+ setState(normal); >+ break; >+ } >+ escaped = !escaped && ch == "\\"; >+ } >+ return "sp-literal"; >+ }; >+ } >+ >+ return function(source, startState) { >+ return tokenizer(source, startState || normal); >+ }; >+ })(); >+ >+ function indentSparql(context) { >+ return function(nextChars) { >+ var firstChar = nextChars && nextChars.charAt(0); >+ if (/[\]\}]/.test(firstChar)) >+ while (context && context.type == "pattern") context = context.prev; >+ >+ var closing = context && firstChar == matching[context.type]; >+ if (!context) >+ return 0; >+ else if (context.type == "pattern") >+ return context.col; >+ else if (context.align) >+ return context.col - (closing ? context.width : 0); >+ else >+ return context.indent + (closing ? 0 : 2); >+ } >+ } >+ >+ function parseSparql(source) { >+ var tokens = tokenizeSparql(source); >+ var context = null, indent = 0, col = 0; >+ function pushContext(type, width) { >+ context = {prev: context, indent: indent, col: col, type: type, width: width}; >+ } >+ function popContext() { >+ context = context.prev; >+ } >+ >+ var iter = { >+ next: function() { >+ var token = tokens.next(), type = token.style, content = token.content, width = token.value.length; >+ >+ if (content == "\n") { >+ token.indentation = indentSparql(context); >+ indent = col = 0; >+ if (context && context.align == null) context.align = false; >+ } >+ else if (type == "whitespace" && col == 0) { >+ indent = width; >+ } >+ else if (type != "sp-comment" && context && context.align == null) { >+ context.align = true; >+ } >+ >+ if (content != "\n") col += width; >+ >+ if (/[\[\{\(]/.test(content)) { >+ pushContext(content, width); >+ } >+ else if (/[\]\}\)]/.test(content)) { >+ while (context && context.type == "pattern") >+ popContext(); >+ if (context && content == matching[context.type]) >+ popContext(); >+ } >+ else if (content == "." && context && context.type == "pattern") { >+ popContext(); >+ } >+ else if ((type == "sp-word" || type == "sp-prefixed" || type == "sp-uri" || type == "sp-var" || type == "sp-literal") && >+ context && /[\{\[]/.test(context.type)) { >+ pushContext("pattern", width); >+ } >+ >+ return token; >+ }, >+ >+ copy: function() { >+ var _context = context, _indent = indent, _col = col, _tokenState = tokens.state; >+ return function(source) { >+ tokens = tokenizeSparql(source, _tokenState); >+ context = _context; >+ indent = _indent; >+ col = _col; >+ return iter; >+ }; >+ } >+ }; >+ return iter; >+ } >+ >+ return {make: parseSparql, electricChars: "}]"}; >+})(); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsexml.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsexml.js >new file mode 100644 >index 0000000..eee54de >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsexml.js >@@ -0,0 +1,286 @@ >+/* This file defines an XML parser, with a few kludges to make it >+ * useable for HTML. autoSelfClosers defines a set of tag names that >+ * are expected to not have a closing tag, and doNotIndent specifies >+ * the tags inside of which no indentation should happen (see Config >+ * object). These can be disabled by passing the editor an object like >+ * {useHTMLKludges: false} as parserConfig option. >+ */ >+ >+var XMLParser = Editor.Parser = (function() { >+ var Kludges = { >+ autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true, >+ "meta": true, "col": true, "frame": true, "base": true, "area": true}, >+ doNotIndent: {"pre": true} >+ }; >+ var NoKludges = {autoSelfClosers: {}, doNotIndent: {}}; >+ var UseKludges = Kludges; >+ >+ // Simple stateful tokenizer for XML documents. Returns a >+ // MochiKit-style iterator, with a state property that contains a >+ // function encapsulating the current state. See tokenize.js. >+ var tokenizeXML = (function() { >+ function inText(source, setState) { >+ var ch = source.next(); >+ if (ch == "<") { >+ if (source.equals("!")) { >+ source.next(); >+ if (source.equals("[")) { >+ if (source.lookAhead("[CDATA[", true)) { >+ setState(inBlock("xml-cdata", "]]>")); >+ return null; >+ } >+ else { >+ return "xml-text"; >+ } >+ } >+ else if (source.lookAhead("--", true)) { >+ setState(inBlock("xml-comment", "-->")); >+ return null; >+ } >+ else { >+ return "xml-text"; >+ } >+ } >+ else if (source.equals("?")) { >+ source.next(); >+ source.nextWhile(matcher(/[\w\._\-]/)); >+ setState(inBlock("xml-processing", "?>")); >+ return "xml-processing"; >+ } >+ else { >+ if (source.equals("/")) source.next(); >+ setState(inTag); >+ return "xml-punctuation"; >+ } >+ } >+ else if (ch == "&") { >+ while (!source.endOfLine()) { >+ if (source.next() == ";") >+ break; >+ } >+ return "xml-entity"; >+ } >+ else { >+ source.nextWhile(matcher(/[^&<\n]/)); >+ return "xml-text"; >+ } >+ } >+ >+ function inTag(source, setState) { >+ var ch = source.next(); >+ if (ch == ">") { >+ setState(inText); >+ return "xml-punctuation"; >+ } >+ else if (/[?\/]/.test(ch) && source.equals(">")) { >+ source.next(); >+ setState(inText); >+ return "xml-punctuation"; >+ } >+ else if (ch == "=") { >+ return "xml-punctuation"; >+ } >+ else if (/[\'\"]/.test(ch)) { >+ setState(inAttribute(ch)); >+ return null; >+ } >+ else { >+ source.nextWhile(matcher(/[^\s\u00a0=<>\"\'\/?]/)); >+ return "xml-name"; >+ } >+ } >+ >+ function inAttribute(quote) { >+ return function(source, setState) { >+ while (!source.endOfLine()) { >+ if (source.next() == quote) { >+ setState(inTag); >+ break; >+ } >+ } >+ return "xml-attribute"; >+ }; >+ } >+ >+ function inBlock(style, terminator) { >+ return function(source, setState) { >+ while (!source.endOfLine()) { >+ if (source.lookAhead(terminator, true)) { >+ setState(inText); >+ break; >+ } >+ source.next(); >+ } >+ return style; >+ }; >+ } >+ >+ return function(source, startState) { >+ return tokenizer(source, startState || inText); >+ }; >+ })(); >+ >+ // The parser. The structure of this function largely follows that of >+ // parseJavaScript in parsejavascript.js (there is actually a bit more >+ // shared code than I'd like), but it is quite a bit simpler. >+ function parseXML(source) { >+ var tokens = tokenizeXML(source); >+ var cc = [base]; >+ var tokenNr = 0, indented = 0; >+ var currentTag = null, context = null; >+ var consume, marked; >+ >+ function push(fs) { >+ for (var i = fs.length - 1; i >= 0; i--) >+ cc.push(fs[i]); >+ } >+ function cont() { >+ push(arguments); >+ consume = true; >+ } >+ function pass() { >+ push(arguments); >+ consume = false; >+ } >+ >+ function mark(style) { >+ marked = style; >+ } >+ function expect(text) { >+ return function(style, content) { >+ if (content == text) cont(); >+ else mark("xml-error") || cont(arguments.callee); >+ }; >+ } >+ >+ function pushContext(tagname, startOfLine) { >+ var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent); >+ context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent}; >+ } >+ function popContext() { >+ context = context.prev; >+ } >+ function computeIndentation(baseContext) { >+ return function(nextChars) { >+ var context = baseContext; >+ if (context && context.noIndent) >+ return 0; >+ if (context && /^<\//.test(nextChars)) >+ context = context.prev; >+ while (context && !context.startOfLine) >+ context = context.prev; >+ if (context) >+ return context.indent + 2; >+ else >+ return 0; >+ }; >+ } >+ >+ function base() { >+ return pass(element, base); >+ } >+ var harmlessTokens = {"xml-text": true, "xml-entity": true, "xml-comment": true, >+ "xml-cdata": true, "xml-processing": true}; >+ function element(style, content) { >+ if (content == "<") cont(tagname, attributes, endtag(tokenNr == 1)); >+ else if (content == "</") cont(closetagname, expect(">")); >+ else if (content == "<?") cont(tagname, attributes, expect("?>")); >+ else if (harmlessTokens.hasOwnProperty(style)) cont(); >+ else mark("xml-error") || cont(); >+ } >+ function tagname(style, content) { >+ if (style == "xml-name") { >+ currentTag = content.toLowerCase(); >+ mark("xml-tagname"); >+ cont(); >+ } >+ else { >+ currentTag = null; >+ pass(); >+ } >+ } >+ function closetagname(style, content) { >+ if (style == "xml-name" && context && content.toLowerCase() == context.name) { >+ popContext(); >+ mark("xml-tagname"); >+ } >+ else { >+ mark("xml-error"); >+ } >+ cont(); >+ } >+ function endtag(startOfLine) { >+ return function(style, content) { >+ if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont(); >+ else if (content == ">") pushContext(currentTag, startOfLine) || cont(); >+ else mark("xml-error") || cont(arguments.callee); >+ }; >+ } >+ function attributes(style) { >+ if (style == "xml-name") mark("xml-attname") || cont(attribute, attributes); >+ else pass(); >+ } >+ function attribute(style, content) { >+ if (content == "=") cont(value); >+ else if (content == ">" || content == "/>") pass(endtag); >+ else pass(); >+ } >+ function value(style) { >+ if (style == "xml-attribute") cont(value); >+ else pass(); >+ } >+ >+ return { >+ indentation: function() {return indented;}, >+ >+ next: function(){ >+ var token = tokens.next(); >+ if (token.style == "whitespace" && tokenNr == 0) >+ indented = token.value.length; >+ else >+ tokenNr++; >+ if (token.content == "\n") { >+ indented = tokenNr = 0; >+ token.indentation = computeIndentation(context); >+ } >+ >+ if (token.style == "whitespace" || token.type == "xml-comment") >+ return token; >+ >+ while(true){ >+ consume = marked = false; >+ cc.pop()(token.style, token.content); >+ if (consume){ >+ if (marked) >+ token.style = marked; >+ return token; >+ } >+ } >+ }, >+ >+ copy: function(){ >+ var _cc = cc.concat([]), _tokenState = tokens.state, _context = context; >+ var parser = this; >+ >+ return function(input){ >+ cc = _cc.concat([]); >+ tokenNr = indented = 0; >+ context = _context; >+ tokens = tokenizeXML(input, _tokenState); >+ return parser; >+ }; >+ } >+ }; >+ } >+ >+ return { >+ make: parseXML, >+ electricChars: "/", >+ configure: function(config) { >+ if (config.useHTMLKludges) >+ UseKludges = Kludges; >+ else >+ UseKludges = NoKludges; >+ } >+ }; >+})(); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/select.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/select.js >new file mode 100644 >index 0000000..e90c98e >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/select.js >@@ -0,0 +1,584 @@ >+/* Functionality for finding, storing, and restoring selections >+ * >+ * This does not provide a generic API, just the minimal functionality >+ * required by the CodeMirror system. >+ */ >+ >+// Namespace object. >+var select = {}; >+ >+(function() { >+ select.ie_selection = document.selection && document.selection.createRangeCollection; >+ >+ // Find the 'top-level' (defined as 'a direct child of the node >+ // passed as the top argument') node that the given node is >+ // contained in. Return null if the given node is not inside the top >+ // node. >+ function topLevelNodeAt(node, top) { >+ while (node && node.parentNode != top) >+ node = node.parentNode; >+ return node; >+ } >+ >+ // Find the top-level node that contains the node before this one. >+ function topLevelNodeBefore(node, top) { >+ while (!node.previousSibling && node.parentNode != top) >+ node = node.parentNode; >+ return topLevelNodeAt(node.previousSibling, top); >+ } >+ >+ // Used to prevent restoring a selection when we do not need to. >+ var currentSelection = null; >+ >+ var fourSpaces = "\u00a0\u00a0\u00a0\u00a0"; >+ >+ select.snapshotChanged = function() { >+ if (currentSelection) currentSelection.changed = true; >+ }; >+ >+ // This is called by the code in editor.js whenever it is replacing >+ // a text node. The function sees whether the given oldNode is part >+ // of the current selection, and updates this selection if it is. >+ // Because nodes are often only partially replaced, the length of >+ // the part that gets replaced has to be taken into account -- the >+ // selection might stay in the oldNode if the newNode is smaller >+ // than the selection's offset. The offset argument is needed in >+ // case the selection does move to the new object, and the given >+ // length is not the whole length of the new node (part of it might >+ // have been used to replace another node). >+ select.snapshotReplaceNode = function(from, to, length, offset) { >+ if (!currentSelection) return; >+ currentSelection.changed = true; >+ >+ function replace(point) { >+ if (from == point.node) { >+ if (length && point.offset > length) { >+ point.offset -= length; >+ } >+ else { >+ point.node = to; >+ point.offset += (offset || 0); >+ } >+ } >+ } >+ replace(currentSelection.start); >+ replace(currentSelection.end); >+ }; >+ >+ select.snapshotMove = function(from, to, distance, relative, ifAtStart) { >+ if (!currentSelection) return; >+ currentSelection.changed = true; >+ >+ function move(point) { >+ if (from == point.node && (!ifAtStart || point.offset == 0)) { >+ point.node = to; >+ if (relative) point.offset = Math.max(0, point.offset + distance); >+ else point.offset = distance; >+ } >+ } >+ move(currentSelection.start); >+ move(currentSelection.end); >+ }; >+ >+ // Most functions are defined in two ways, one for the IE selection >+ // model, one for the W3C one. >+ if (select.ie_selection) { >+ function selectionNode(win, start) { >+ var range = win.document.selection.createRange(); >+ range.collapse(start); >+ >+ function nodeAfter(node) { >+ var found = null; >+ while (!found && node) { >+ found = node.nextSibling; >+ node = node.parentNode; >+ } >+ return nodeAtStartOf(found); >+ } >+ >+ function nodeAtStartOf(node) { >+ while (node && node.firstChild) node = node.firstChild; >+ return {node: node, offset: 0}; >+ } >+ >+ var containing = range.parentElement(); >+ if (!isAncestor(win.document.body, containing)) return null; >+ if (!containing.firstChild) return nodeAtStartOf(containing); >+ >+ var working = range.duplicate(); >+ working.moveToElementText(containing); >+ working.collapse(true); >+ for (var cur = containing.firstChild; cur; cur = cur.nextSibling) { >+ if (cur.nodeType == 3) { >+ var size = cur.nodeValue.length; >+ working.move("character", size); >+ } >+ else { >+ working.moveToElementText(cur); >+ working.collapse(false); >+ } >+ >+ var dir = range.compareEndPoints("StartToStart", working); >+ if (dir == 0) return nodeAfter(cur); >+ if (dir == 1) continue; >+ if (cur.nodeType != 3) return nodeAtStartOf(cur); >+ >+ working.setEndPoint("StartToEnd", range); >+ return {node: cur, offset: size - working.text.length}; >+ } >+ return nodeAfter(containing); >+ } >+ >+ select.markSelection = function(win) { >+ currentSelection = null; >+ var sel = win.document.selection; >+ if (!sel) return; >+ var start = selectionNode(win, true), >+ end = sel.createRange().text == "" ? start : selectionNode(win, false); >+ if (!start || !end) return; >+ currentSelection = {start: start, end: end, window: win, changed: false}; >+ }; >+ >+ select.selectMarked = function() { >+ if (!currentSelection || !currentSelection.changed) return; >+ >+ function makeRange(point) { >+ var range = currentSelection.window.document.body.createTextRange(); >+ var node = point.node; >+ if (!node) { >+ range.moveToElementText(win.document.body); >+ range.collapse(false); >+ } >+ else if (node.nodeType == 3) { >+ range.moveToElementText(node.parentNode); >+ var offset = point.offset; >+ while (node.previousSibling) { >+ node = node.previousSibling; >+ offset += (node.innerText || "").length; >+ } >+ range.move("character", offset); >+ } >+ else { >+ range.moveToElementText(node); >+ range.collapse(true); >+ } >+ return range; >+ } >+ >+ var start = makeRange(currentSelection.start), end = makeRange(currentSelection.end); >+ start.setEndPoint("StartToEnd", end); >+ start.select(); >+ }; >+ >+ // Get the top-level node that one end of the cursor is inside or >+ // after. Note that this returns false for 'no cursor', and null >+ // for 'start of document'. >+ select.selectionTopNode = function(container, start) { >+ var selection = container.ownerDocument.selection; >+ if (!selection) return false; >+ >+ var range = selection.createRange(); >+ range.collapse(start); >+ var around = range.parentElement(); >+ if (around && isAncestor(container, around)) { >+ // Only use this node if the selection is not at its start. >+ var range2 = range.duplicate(); >+ range2.moveToElementText(around); >+ if (range.compareEndPoints("StartToStart", range2) == -1) >+ return topLevelNodeAt(around, container); >+ } >+ // Fall-back hack >+ try {range.pasteHTML("<span id='xxx-temp-xxx'></span>");} >+ catch (e) {return false;} >+ >+ var temp = container.ownerDocument.getElementById("xxx-temp-xxx"); >+ if (temp) { >+ var result = topLevelNodeBefore(temp, container); >+ removeElement(temp); >+ return result; >+ } >+ return false; >+ }; >+ >+ // Place the cursor after this.start. This is only useful when >+ // manually moving the cursor instead of restoring it to its old >+ // position. >+ select.focusAfterNode = function(node, container) { >+ var range = container.ownerDocument.body.createTextRange(); >+ range.moveToElementText(node || container); >+ range.collapse(!node); >+ range.select(); >+ }; >+ >+ select.somethingSelected = function(win) { >+ var sel = win.document.selection; >+ return sel && (sel.createRange().text != ""); >+ }; >+ >+ function insertAtCursor(window, html) { >+ var selection = window.document.selection; >+ if (selection) { >+ var range = selection.createRange(); >+ range.pasteHTML(html); >+ range.collapse(false); >+ range.select(); >+ } >+ } >+ >+ // Used to normalize the effect of the enter key, since browsers >+ // do widely different things when pressing enter in designMode. >+ select.insertNewlineAtCursor = function(window) { >+ insertAtCursor(window, "<br/>"); >+ }; >+ >+ select.insertTabAtCursor = function(window) { >+ insertAtCursor(window, fourSpaces); >+ }; >+ >+ // Get the BR node at the start of the line on which the cursor >+ // currently is, and the offset into the line. Returns null as >+ // node if cursor is on first line. >+ select.cursorPos = function(container, start) { >+ var selection = container.ownerDocument.selection; >+ if (!selection) return null; >+ >+ var topNode = select.selectionTopNode(container, start); >+ while (topNode && topNode.nodeName != "BR") >+ topNode = topNode.previousSibling; >+ >+ var range = selection.createRange(), range2 = range.duplicate(); >+ range.collapse(start); >+ if (topNode) { >+ range2.moveToElementText(topNode); >+ range2.collapse(false); >+ } >+ else { >+ // When nothing is selected, we can get all kinds of funky errors here. >+ try { range2.moveToElementText(container); } >+ catch (e) { return null; } >+ range2.collapse(true); >+ } >+ range.setEndPoint("StartToStart", range2); >+ >+ return {node: topNode, offset: range.text.length}; >+ }; >+ >+ select.setCursorPos = function(container, from, to) { >+ function rangeAt(pos) { >+ var range = container.ownerDocument.body.createTextRange(); >+ if (!pos.node) { >+ range.moveToElementText(container); >+ range.collapse(true); >+ } >+ else { >+ range.moveToElementText(pos.node); >+ range.collapse(false); >+ } >+ range.move("character", pos.offset); >+ return range; >+ } >+ >+ var range = rangeAt(from); >+ if (to && to != from) >+ range.setEndPoint("EndToEnd", rangeAt(to)); >+ range.select(); >+ } >+ >+ // Make sure the cursor is visible. >+ select.scrollToCursor = function(container) { >+ var selection = container.ownerDocument.selection; >+ if (!selection) return null; >+ selection.createRange().scrollIntoView(); >+ }; >+ >+ // Some hacks for storing and re-storing the selection when the editor loses and regains focus. >+ select.selectionCoords = function (win) { >+ var selection = win.document.selection; >+ if (!selection) return null; >+ var start = selection.createRange(), end = start.duplicate(); >+ start.collapse(true); >+ end.collapse(false); >+ >+ var body = win.document.body; >+ return {start: {x: start.boundingLeft + body.scrollLeft - 1, >+ y: start.boundingTop + body.scrollTop}, >+ end: {x: end.boundingLeft + body.scrollLeft - 1, >+ y: end.boundingTop + body.scrollTop}}; >+ }; >+ >+ // Restore a stored selection. >+ select.selectCoords = function(win, coords) { >+ if (!coords) return; >+ >+ var range1 = win.document.body.createTextRange(), range2 = range1.duplicate(); >+ // This can fail for various hard-to-handle reasons. >+ try { >+ range1.moveToPoint(coords.start.x, coords.start.y); >+ range2.moveToPoint(coords.end.x, coords.end.y); >+ range1.setEndPoint("EndToStart", range2); >+ range1.select(); >+ } catch(e) {alert(e.message);} >+ }; >+ } >+ // W3C model >+ else { >+ // This is used to fix an issue with getting the scroll position >+ // in Opera. >+ var opera_scroll = window.scrollX == null; >+ >+ // Store start and end nodes, and offsets within these, and refer >+ // back to the selection object from those nodes, so that this >+ // object can be updated when the nodes are replaced before the >+ // selection is restored. >+ select.markSelection = function (win) { >+ var selection = win.getSelection(); >+ if (!selection || selection.rangeCount == 0) >+ return (currentSelection = null); >+ var range = selection.getRangeAt(0); >+ >+ currentSelection = { >+ start: {node: range.startContainer, offset: range.startOffset}, >+ end: {node: range.endContainer, offset: range.endOffset}, >+ window: win, >+ scrollX: opera_scroll && win.document.body.scrollLeft, >+ scrollY: opera_scroll && win.document.body.scrollTop, >+ changed: false >+ }; >+ >+ // We want the nodes right at the cursor, not one of their >+ // ancestors with a suitable offset. This goes down the DOM tree >+ // until a 'leaf' is reached (or is it *up* the DOM tree?). >+ function normalize(point){ >+ while (point.node.nodeType != 3 && point.node.nodeName != "BR") { >+ var newNode = point.node.childNodes[point.offset] || point.node.nextSibling; >+ point.offset = 0; >+ while (!newNode && point.node.parentNode) { >+ point.node = point.node.parentNode; >+ newNode = point.node.nextSibling; >+ } >+ point.node = newNode; >+ if (!newNode) >+ break; >+ } >+ } >+ >+ normalize(currentSelection.start); >+ normalize(currentSelection.end); >+ }; >+ >+ select.selectMarked = function () { >+ if (!currentSelection || !currentSelection.changed) return; >+ var win = currentSelection.window, range = win.document.createRange(); >+ >+ function setPoint(point, which) { >+ if (point.node) { >+ // Some magic to generalize the setting of the start and end >+ // of a range. >+ if (point.offset == 0) >+ range["set" + which + "Before"](point.node); >+ else >+ range["set" + which](point.node, point.offset); >+ } >+ else { >+ range.setStartAfter(win.document.body.lastChild || win.document.body); >+ } >+ } >+ >+ // Have to restore the scroll position of the frame in Opera. >+ if (opera_scroll) { >+ win.document.body.scrollLeft = currentSelection.scrollX; >+ win.document.body.scrollTop = currentSelection.scrollY; >+ } >+ setPoint(currentSelection.end, "End"); >+ setPoint(currentSelection.start, "Start"); >+ selectRange(range, win); >+ }; >+ >+ // Helper for selecting a range object. >+ function selectRange(range, window) { >+ var selection = window.getSelection(); >+ selection.removeAllRanges(); >+ selection.addRange(range); >+ }; >+ function selectionRange(window) { >+ var selection = window.getSelection(); >+ if (!selection || selection.rangeCount == 0) >+ return false; >+ else >+ return selection.getRangeAt(0); >+ } >+ >+ // Finding the top-level node at the cursor in the W3C is, as you >+ // can see, quite an involved process. >+ select.selectionTopNode = function(container, start) { >+ var range = selectionRange(container.ownerDocument.defaultView); >+ if (!range) return false; >+ >+ var node = start ? range.startContainer : range.endContainer; >+ var offset = start ? range.startOffset : range.endOffset; >+ // Work around (yet another) bug in Opera's selection model. >+ if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 && >+ container.childNodes[range.startOffset] && container.childNodes[range.startOffset].nodeName == "BR") >+ offset--; >+ >+ // For text nodes, we look at the node itself if the cursor is >+ // inside, or at the node before it if the cursor is at the >+ // start. >+ if (node.nodeType == 3){ >+ if (offset > 0) >+ return topLevelNodeAt(node, container); >+ else >+ return topLevelNodeBefore(node, container); >+ } >+ // Occasionally, browsers will return the HTML node as >+ // selection. If the offset is 0, we take the start of the frame >+ // ('after null'), otherwise, we take the last node. >+ else if (node.nodeName == "HTML") { >+ return (offset == 1 ? null : container.lastChild); >+ } >+ // If the given node is our 'container', we just look up the >+ // correct node by using the offset. >+ else if (node == container) { >+ return (offset == 0) ? null : node.childNodes[offset - 1]; >+ } >+ // In any other case, we have a regular node. If the cursor is >+ // at the end of the node, we use the node itself, if it is at >+ // the start, we use the node before it, and in any other >+ // case, we look up the child before the cursor and use that. >+ else { >+ if (offset == node.childNodes.length) >+ return topLevelNodeAt(node, container); >+ else if (offset == 0) >+ return topLevelNodeBefore(node, container); >+ else >+ return topLevelNodeAt(node.childNodes[offset - 1], container); >+ } >+ }; >+ >+ select.focusAfterNode = function(node, container) { >+ var win = container.ownerDocument.defaultView, >+ range = win.document.createRange(); >+ range.setStartBefore(container.firstChild || container); >+ // In Opera, setting the end of a range at the end of a line >+ // (before a BR) will cause the cursor to appear on the next >+ // line, so we set the end inside of the start node when >+ // possible. >+ if (node && !node.firstChild) >+ range.setEndAfter(node); >+ else if (node) >+ range.setEnd(node, node.childNodes.length); >+ else >+ range.setEndBefore(container.firstChild || container); >+ range.collapse(false); >+ selectRange(range, win); >+ }; >+ >+ select.somethingSelected = function(win) { >+ var range = selectionRange(win); >+ return range && !range.collapsed; >+ }; >+ >+ function insertNodeAtCursor(window, node) { >+ var range = selectionRange(window); >+ if (!range) return; >+ >+ range.deleteContents(); >+ range.insertNode(node); >+ range.setEndAfter(node); >+ range.collapse(false); >+ selectRange(range, window); >+ return node; >+ } >+ >+ select.insertNewlineAtCursor = function(window) { >+ insertNodeAtCursor(window, window.document.createElement("BR")); >+ }; >+ >+ select.insertTabAtCursor = function(window) { >+ insertNodeAtCursor(window, window.document.createTextNode(fourSpaces)); >+ }; >+ >+ select.cursorPos = function(container, start) { >+ var range = selectionRange(window); >+ if (!range) return; >+ >+ var topNode = select.selectionTopNode(container, start); >+ while (topNode && topNode.nodeName != "BR") >+ topNode = topNode.previousSibling; >+ >+ range = range.cloneRange(); >+ range.collapse(start); >+ if (topNode) >+ range.setStartAfter(topNode); >+ else >+ range.setStartBefore(container); >+ return {node: topNode, offset: range.toString().length}; >+ }; >+ >+ select.setCursorPos = function(container, from, to) { >+ var win = container.ownerDocument.defaultView, >+ range = win.document.createRange(); >+ >+ function setPoint(node, offset, side) { >+ if (!node) >+ node = container.firstChild; >+ else >+ node = node.nextSibling; >+ >+ if (!node) >+ return; >+ >+ if (offset == 0) { >+ range["set" + side + "Before"](node); >+ return true; >+ } >+ >+ var backlog = [] >+ function decompose(node) { >+ if (node.nodeType == 3) >+ backlog.push(node); >+ else >+ forEach(node.childNodes, decompose); >+ } >+ while (true) { >+ while (node && !backlog.length) { >+ decompose(node); >+ node = node.nextSibling; >+ } >+ var cur = backlog.shift(); >+ if (!cur) return false; >+ >+ var length = cur.nodeValue.length; >+ if (length >= offset) { >+ range["set" + side](cur, offset); >+ return true; >+ } >+ offset -= length; >+ } >+ } >+ >+ to = to || from; >+ if (setPoint(to.node, to.offset, "End") && setPoint(from.node, from.offset, "Start")) >+ selectRange(range, win); >+ }; >+ >+ select.scrollToCursor = function(container) { >+ var body = container.ownerDocument.body, win = container.ownerDocument.defaultView; >+ var element = select.selectionTopNode(container, true) || container.firstChild; >+ >+ // In Opera, BR elements *always* have a scrollTop property of zero. Go Opera. >+ while (element && !element.offsetTop) >+ element = element.previousSibling; >+ >+ var y = 0, pos = element; >+ while (pos && pos.offsetParent) { >+ y += pos.offsetTop; >+ pos = pos.offsetParent; >+ } >+ >+ var screen_y = y - body.scrollTop; >+ if (screen_y < 0 || screen_y > win.innerHeight - 10) >+ win.scrollTo(0, y); >+ }; >+ } >+})(); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/stringstream.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/stringstream.js >new file mode 100644 >index 0000000..e320f8b >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/stringstream.js >@@ -0,0 +1,131 @@ >+/* String streams are the things fed to parsers (which can feed them >+ * to a tokenizer if they want). They provide peek and next methods >+ * for looking at the current character (next 'consumes' this >+ * character, peek does not), and a get method for retrieving all the >+ * text that was consumed since the last time get was called. >+ * >+ * An easy mistake to make is to let a StopIteration exception finish >+ * the token stream while there are still characters pending in the >+ * string stream (hitting the end of the buffer while parsing a >+ * token). To make it easier to detect such errors, the strings throw >+ * an exception when this happens. >+ */ >+ >+// Make a string stream out of an iterator that returns strings. This >+// is applied to the result of traverseDOM (see codemirror.js), and >+// the resulting stream is fed to the parser. >+window.stringStream = function(source){ >+ source = iter(source); >+ // String that's currently being iterated over. >+ var current = ""; >+ // Position in that string. >+ var pos = 0; >+ // Accumulator for strings that have been iterated over but not >+ // get()-ed yet. >+ var accum = ""; >+ // Make sure there are more characters ready, or throw >+ // StopIteration. >+ function ensureChars() { >+ while (pos == current.length) { >+ accum += current; >+ current = ""; // In case source.next() throws >+ pos = 0; >+ try {current = source.next();} >+ catch (e) { >+ if (e != StopIteration) throw e; >+ else return false; >+ } >+ } >+ return true; >+ } >+ >+ return { >+ // Return the next character in the stream. >+ peek: function() { >+ if (!ensureChars()) return null; >+ return current.charAt(pos); >+ }, >+ // Get the next character, throw StopIteration if at end, check >+ // for unused content. >+ next: function() { >+ if (!ensureChars()) { >+ if (accum.length > 0) >+ throw "End of stringstream reached without emptying buffer ('" + accum + "')."; >+ else >+ throw StopIteration; >+ } >+ return current.charAt(pos++); >+ }, >+ // Return the characters iterated over since the last call to >+ // .get(). >+ get: function() { >+ var temp = accum; >+ accum = ""; >+ if (pos > 0){ >+ temp += current.slice(0, pos); >+ current = current.slice(pos); >+ pos = 0; >+ } >+ return temp; >+ }, >+ // Push a string back into the stream. >+ push: function(str) { >+ current = current.slice(0, pos) + str + current.slice(pos); >+ }, >+ lookAhead: function(str, consume, skipSpaces, caseInsensitive) { >+ function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} >+ str = cased(str); >+ var found = false; >+ >+ var _accum = accum, _pos = pos; >+ if (skipSpaces) this.nextWhile(matcher(/[\s\u00a0]/)); >+ >+ while (true) { >+ var end = pos + str.length, left = current.length - pos; >+ if (end <= current.length) { >+ found = str == cased(current.slice(pos, end)); >+ pos = end; >+ break; >+ } >+ else if (str.slice(0, left) == cased(current.slice(pos))) { >+ accum += current; current = ""; >+ try {current = source.next();} >+ catch (e) {break;} >+ pos = 0; >+ str = str.slice(left); >+ } >+ else { >+ break; >+ } >+ } >+ >+ if (!(found && consume)) { >+ current = accum.slice(_accum.length) + current; >+ pos = _pos; >+ accum = _accum; >+ } >+ >+ return found; >+ }, >+ >+ // Utils built on top of the above >+ more: function() { >+ return this.peek() !== null; >+ }, >+ applies: function(test) { >+ var next = this.peek(); >+ return (next !== null && test(next)); >+ }, >+ nextWhile: function(test) { >+ while (this.applies(test)) >+ this.next(); >+ }, >+ equals: function(ch) { >+ return ch === this.peek(); >+ }, >+ endOfLine: function() { >+ var next = this.peek(); >+ return next == null || next == "\n"; >+ } >+ }; >+}; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/tokenize.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/tokenize.js >new file mode 100644 >index 0000000..b0c9545 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/tokenize.js >@@ -0,0 +1,57 @@ >+// A framework for simple tokenizers. Takes care of newlines and >+// white-space, and of getting the text from the source stream into >+// the token object. A state is a function of two arguments -- a >+// string stream and a setState function. The second can be used to >+// change the tokenizer's state, and can be ignored for stateless >+// tokenizers. This function should advance the stream over a token >+// and return a string or object containing information about the next >+// token, or null to pass and have the (new) state be called to finish >+// the token. When a string is given, it is wrapped in a {style, type} >+// object. In the resulting object, the characters consumed are stored >+// under the content property. Any whitespace following them is also >+// automatically consumed, and added to the value property. (Thus, >+// content is the actual meaningful part of the token, while value >+// contains all the text it spans.) >+ >+function tokenizer(source, state) { >+ // Newlines are always a separate token. >+ function isWhiteSpace(ch) { >+ // The messy regexp is because IE's regexp matcher is of the >+ // opinion that non-breaking spaces are no whitespace. >+ return ch != "\n" && /^[\s\u00a0]*$/.test(ch); >+ } >+ >+ var tokenizer = { >+ state: state, >+ >+ take: function(type) { >+ if (typeof(type) == "string") >+ type = {style: type, type: type}; >+ >+ type.content = (type.content || "") + source.get(); >+ if (!/\n$/.test(type.content)) >+ source.nextWhile(isWhiteSpace); >+ type.value = type.content + source.get(); >+ return type; >+ }, >+ >+ next: function () { >+ if (!source.more()) throw StopIteration; >+ >+ var type; >+ if (source.equals("\n")) { >+ source.next(); >+ return this.take("whitespace"); >+ } >+ >+ if (source.applies(isWhiteSpace)) >+ type = "whitespace"; >+ else >+ while (!type) >+ type = this.state(source, function(s) {tokenizer.state = s;}); >+ >+ return this.take(type); >+ } >+ }; >+ return tokenizer; >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/tokenizejavascript.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/tokenizejavascript.js >new file mode 100644 >index 0000000..c60c6cb >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/tokenizejavascript.js >@@ -0,0 +1,176 @@ >+/* Tokenizer for JavaScript code */ >+ >+var tokenizeJavaScript = (function() { >+ // Advance the stream until the given character (not preceded by a >+ // backslash) is encountered, or the end of the line is reached. >+ function nextUntilUnescaped(source, end) { >+ var escaped = false; >+ var next; >+ while (!source.endOfLine()) { >+ var next = source.next(); >+ if (next == end && !escaped) >+ return false; >+ escaped = !escaped && next == "\\"; >+ } >+ return escaped; >+ } >+ >+ // A map of JavaScript's keywords. The a/b/c keyword distinction is >+ // very rough, but it gives the parser enough information to parse >+ // correct code correctly (we don't care that much how we parse >+ // incorrect code). The style information included in these objects >+ // is used by the highlighter to pick the correct CSS style for a >+ // token. >+ var keywords = function(){ >+ function result(type, style){ >+ return {type: type, style: style}; >+ } >+ // keywords that take a parenthised expression, and then a >+ // statement (if) >+ var keywordA = result("keyword a", "js-keyword"); >+ // keywords that take just a statement (else) >+ var keywordB = result("keyword b", "js-keyword"); >+ // keywords that optionally take an expression, and form a >+ // statement (return) >+ var keywordC = result("keyword c", "js-keyword"); >+ var operator = result("operator", "js-keyword"); >+ var atom = result("atom", "js-atom"); >+ return { >+ "if": keywordA, "switch": keywordA, "while": keywordA, "with": keywordA, >+ "else": keywordB, "do": keywordB, "try": keywordB, "finally": keywordB, >+ "return": keywordC, "break": keywordC, "continue": keywordC, "new": keywordC, "delete": keywordC, "throw": keywordC, >+ "in": operator, "typeof": operator, "instanceof": operator, >+ "var": result("var", "js-keyword"), "function": result("function", "js-keyword"), "catch": result("catch", "js-keyword"), >+ "for": result("for", "js-keyword"), >+ "case": result("case", "js-keyword"), "default": result("default", "js-keyword"), >+ "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom >+ }; >+ }(); >+ >+ // Some helper regexp matchers. >+ var isOperatorChar = matcher(/[+\-*&%\/=<>!?|]/); >+ var isDigit = matcher(/[0-9]/); >+ var isHexDigit = matcher(/[0-9A-Fa-f]/); >+ var isWordChar = matcher(/[\w\$_]/); >+ >+ // Wrapper around jsToken that helps maintain parser state (whether >+ // we are inside of a multi-line comment and whether the next token >+ // could be a regular expression). >+ function jsTokenState(inside, regexp) { >+ return function(source, setState) { >+ var newInside = inside; >+ var type = jsToken(inside, regexp, source, function(c) {newInside = c;}); >+ var newRegexp = type.type == "operator" || type.type == "keyword c" || type.type.match(/^[\[{}\(,;:]$/); >+ if (newRegexp != regexp || newInside != inside) >+ setState(jsTokenState(newInside, newRegexp)); >+ return type; >+ }; >+ } >+ >+ // The token reader, inteded to be used by the tokenizer from >+ // tokenize.js (through jsTokenState). Advances the source stream >+ // over a token, and returns an object containing the type and style >+ // of that token. >+ function jsToken(inside, regexp, source, setInside) { >+ function readHexNumber(){ >+ source.next(); // skip the 'x' >+ source.nextWhile(isHexDigit); >+ return {type: "number", style: "js-atom"}; >+ } >+ >+ function readNumber() { >+ source.nextWhile(isDigit); >+ if (source.equals(".")){ >+ source.next(); >+ source.nextWhile(isDigit); >+ } >+ if (source.equals("e") || source.equals("E")){ >+ source.next(); >+ if (source.equals("-")) >+ source.next(); >+ source.nextWhile(isDigit); >+ } >+ return {type: "number", style: "js-atom"}; >+ } >+ // Read a word, look it up in keywords. If not found, it is a >+ // variable, otherwise it is a keyword of the type found. >+ function readWord() { >+ source.nextWhile(isWordChar); >+ var word = source.get(); >+ var known = keywords.hasOwnProperty(word) && keywords.propertyIsEnumerable(word) && keywords[word]; >+ return known ? {type: known.type, style: known.style, content: word} : >+ {type: "variable", style: "js-variable", content: word}; >+ } >+ function readRegexp() { >+ nextUntilUnescaped(source, "/"); >+ source.nextWhile(matcher(/[gi]/)); >+ return {type: "regexp", style: "js-string"}; >+ } >+ // Mutli-line comments are tricky. We want to return the newlines >+ // embedded in them as regular newline tokens, and then continue >+ // returning a comment token for every line of the comment. So >+ // some state has to be saved (inside) to indicate whether we are >+ // inside a /* */ sequence. >+ function readMultilineComment(start){ >+ var newInside = "/*"; >+ var maybeEnd = (start == "*"); >+ while (true) { >+ if (source.endOfLine()) >+ break; >+ var next = source.next(); >+ if (next == "/" && maybeEnd){ >+ newInside = null; >+ break; >+ } >+ maybeEnd = (next == "*"); >+ } >+ setInside(newInside); >+ return {type: "comment", style: "js-comment"}; >+ } >+ function readOperator() { >+ source.nextWhile(isOperatorChar); >+ return {type: "operator", style: "js-operator"}; >+ } >+ function readString(quote) { >+ var endBackSlash = nextUntilUnescaped(source, quote); >+ setInside(endBackSlash ? quote : null); >+ return {type: "string", style: "js-string"}; >+ } >+ >+ // Fetch the next token. Dispatches on first character in the >+ // stream, or first two characters when the first is a slash. >+ if (inside == "\"" || inside == "'") >+ return readString(inside); >+ var ch = source.next(); >+ if (inside == "/*") >+ return readMultilineComment(ch); >+ else if (ch == "\"" || ch == "'") >+ return readString(ch); >+ // with punctuation, the type of the token is the symbol itself >+ else if (/[\[\]{}\(\),;\:\.]/.test(ch)) >+ return {type: ch, style: "js-punctuation"}; >+ else if (ch == "0" && (source.equals("x") || source.equals("X"))) >+ return readHexNumber(); >+ else if (isDigit(ch)) >+ return readNumber(); >+ else if (ch == "/"){ >+ if (source.equals("*")) >+ { source.next(); return readMultilineComment(ch); } >+ else if (source.equals("/")) >+ { nextUntilUnescaped(source, null); return {type: "comment", style: "js-comment"};} >+ else if (regexp) >+ return readRegexp(); >+ else >+ return readOperator(); >+ } >+ else if (isOperatorChar(ch)) >+ return readOperator(); >+ else >+ return readWord(); >+ } >+ >+ // The external interface to the tokenizer. >+ return function(source, startState) { >+ return tokenizer(source, startState || jsTokenState(false, true)); >+ }; >+})(); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/undo.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/undo.js >new file mode 100644 >index 0000000..f7990ee >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/undo.js >@@ -0,0 +1,388 @@ >+/** >+ * Storage and control for undo information within a CodeMirror >+ * editor. 'Why on earth is such a complicated mess required for >+ * that?', I hear you ask. The goal, in implementing this, was to make >+ * the complexity of storing and reverting undo information depend >+ * only on the size of the edited or restored content, not on the size >+ * of the whole document. This makes it necessary to use a kind of >+ * 'diff' system, which, when applied to a DOM tree, causes some >+ * complexity and hackery. >+ * >+ * In short, the editor 'touches' BR elements as it parses them, and >+ * the History stores these. When nothing is touched in commitDelay >+ * milliseconds, the changes are committed: It goes over all touched >+ * nodes, throws out the ones that did not change since last commit or >+ * are no longer in the document, and assembles the rest into zero or >+ * more 'chains' -- arrays of adjacent lines. Links back to these >+ * chains are added to the BR nodes, while the chain that previously >+ * spanned these nodes is added to the undo history. Undoing a change >+ * means taking such a chain off the undo history, restoring its >+ * content (text is saved per line) and linking it back into the >+ * document. >+ */ >+ >+// A history object needs to know about the DOM container holding the >+// document, the maximum amount of undo levels it should store, the >+// delay (of no input) after which it commits a set of changes, and, >+// unfortunately, the 'parent' window -- a window that is not in >+// designMode, and on which setTimeout works in every browser. >+function History(container, maxDepth, commitDelay, editor, onChange) { >+ this.container = container; >+ this.maxDepth = maxDepth; this.commitDelay = commitDelay; >+ this.editor = editor; this.parent = editor.parent; >+ this.onChange = onChange; >+ // This line object represents the initial, empty editor. >+ var initial = {text: "", from: null, to: null}; >+ // As the borders between lines are represented by BR elements, the >+ // start of the first line and the end of the last one are >+ // represented by null. Since you can not store any properties >+ // (links to line objects) in null, these properties are used in >+ // those cases. >+ this.first = initial; this.last = initial; >+ // Similarly, a 'historyTouched' property is added to the BR in >+ // front of lines that have already been touched, and 'firstTouched' >+ // is used for the first line. >+ this.firstTouched = false; >+ // History is the set of committed changes, touched is the set of >+ // nodes touched since the last commit. >+ this.history = []; this.redoHistory = []; this.touched = []; >+} >+ >+History.prototype = { >+ // Schedule a commit (if no other touches come in for commitDelay >+ // milliseconds). >+ scheduleCommit: function() { >+ this.parent.clearTimeout(this.commitTimeout); >+ this.commitTimeout = this.parent.setTimeout(method(this, "tryCommit"), this.commitDelay); >+ }, >+ >+ // Mark a node as touched. Null is a valid argument. >+ touch: function(node) { >+ this.setTouched(node); >+ this.scheduleCommit(); >+ }, >+ >+ // Undo the last change. >+ undo: function() { >+ // Make sure pending changes have been committed. >+ this.commit(); >+ >+ if (this.history.length) { >+ // Take the top diff from the history, apply it, and store its >+ // shadow in the redo history. >+ this.redoHistory.push(this.updateTo(this.history.pop(), "applyChain")); >+ if (this.onChange) this.onChange(); >+ } >+ }, >+ >+ // Redo the last undone change. >+ redo: function() { >+ this.commit(); >+ if (this.redoHistory.length) { >+ // The inverse of undo, basically. >+ this.addUndoLevel(this.updateTo(this.redoHistory.pop(), "applyChain")); >+ if (this.onChange) this.onChange(); >+ } >+ }, >+ >+ // Push a changeset into the document. >+ push: function(from, to, lines) { >+ var chain = []; >+ for (var i = 0; i < lines.length; i++) { >+ var end = (i == lines.length - 1) ? to : this.container.ownerDocument.createElement("BR"); >+ chain.push({from: from, to: end, text: lines[i]}); >+ from = end; >+ } >+ this.pushChains([chain], from == null && to == null); >+ }, >+ >+ pushChains: function(chains, doNotHighlight) { >+ this.commit(doNotHighlight); >+ this.addUndoLevel(this.updateTo(chains, "applyChain")); >+ this.redoHistory = []; >+ }, >+ >+ // Clear the undo history, make the current document the start >+ // position. >+ reset: function() { >+ this.history = []; this.redoHistory = []; >+ }, >+ >+ textAfter: function(br) { >+ return this.after(br).text; >+ }, >+ >+ nodeAfter: function(br) { >+ return this.after(br).to; >+ }, >+ >+ nodeBefore: function(br) { >+ return this.before(br).from; >+ }, >+ >+ // Commit unless there are pending dirty nodes. >+ tryCommit: function() { >+ if (this.editor.highlightDirty()) this.commit(); >+ else this.scheduleCommit(); >+ }, >+ >+ // Check whether the touched nodes hold any changes, if so, commit >+ // them. >+ commit: function(doNotHighlight) { >+ this.parent.clearTimeout(this.commitTimeout); >+ // Make sure there are no pending dirty nodes. >+ if (!doNotHighlight) this.editor.highlightDirty(true); >+ // Build set of chains. >+ var chains = this.touchedChains(), self = this; >+ >+ if (chains.length) { >+ this.addUndoLevel(this.updateTo(chains, "linkChain")); >+ this.redoHistory = []; >+ if (this.onChange) this.onChange(); >+ } >+ }, >+ >+ // [ end of public interface ] >+ >+ // Update the document with a given set of chains, return its >+ // shadow. updateFunc should be "applyChain" or "linkChain". In the >+ // second case, the chains are taken to correspond the the current >+ // document, and only the state of the line data is updated. In the >+ // first case, the content of the chains is also pushed iinto the >+ // document. >+ updateTo: function(chains, updateFunc) { >+ var shadows = [], dirty = []; >+ for (var i = 0; i < chains.length; i++) { >+ shadows.push(this.shadowChain(chains[i])); >+ dirty.push(this[updateFunc](chains[i])); >+ } >+ if (updateFunc == "applyChain") >+ this.notifyDirty(dirty); >+ return shadows; >+ }, >+ >+ // Notify the editor that some nodes have changed. >+ notifyDirty: function(nodes) { >+ forEach(nodes, method(this.editor, "addDirtyNode")) >+ this.editor.scheduleHighlight(); >+ }, >+ >+ // Link a chain into the DOM nodes (or the first/last links for null >+ // nodes). >+ linkChain: function(chain) { >+ for (var i = 0; i < chain.length; i++) { >+ var line = chain[i]; >+ if (line.from) line.from.historyAfter = line; >+ else this.first = line; >+ if (line.to) line.to.historyBefore = line; >+ else this.last = line; >+ } >+ }, >+ >+ // Get the line object after/before a given node. >+ after: function(node) { >+ return node ? node.historyAfter : this.first; >+ }, >+ before: function(node) { >+ return node ? node.historyBefore : this.last; >+ }, >+ >+ // Mark a node as touched if it has not already been marked. >+ setTouched: function(node) { >+ if (node) { >+ if (!node.historyTouched) { >+ this.touched.push(node); >+ node.historyTouched = true; >+ } >+ } >+ else { >+ this.firstTouched = true; >+ } >+ }, >+ >+ // Store a new set of undo info, throw away info if there is more of >+ // it than allowed. >+ addUndoLevel: function(diffs) { >+ this.history.push(diffs); >+ if (this.history.length > this.maxDepth) >+ this.history.shift(); >+ }, >+ >+ // Build chains from a set of touched nodes. >+ touchedChains: function() { >+ var self = this; >+ // Compare two strings, treating nbsps as spaces. >+ function compareText(a, b) { >+ return a.replace(/\u00a0/g, " ") == b.replace(/\u00a0/g, " "); >+ } >+ >+ // The temp system is a crummy hack to speed up determining >+ // whether a (currently touched) node has a line object associated >+ // with it. nullTemp is used to store the object for the first >+ // line, other nodes get it stored in their historyTemp property. >+ var nullTemp = null; >+ function temp(node) {return node ? node.historyTemp : nullTemp;} >+ function setTemp(node, line) { >+ if (node) node.historyTemp = line; >+ else nullTemp = line; >+ } >+ >+ function buildLine(node) { >+ var text = []; >+ for (var cur = node ? node.nextSibling : self.container.firstChild; >+ cur && cur.nodeName != "BR"; cur = cur.nextSibling) >+ if (cur.currentText) text.push(cur.currentText); >+ return {from: node, to: cur, text: text.join("")}; >+ } >+ >+ // Filter out unchanged lines and nodes that are no longer in the >+ // document. Build up line objects for remaining nodes. >+ var lines = []; >+ if (self.firstTouched) self.touched.push(null); >+ forEach(self.touched, function(node) { >+ if (node && node.parentNode != self.container) return; >+ >+ if (node) node.historyTouched = false; >+ else self.firstTouched = false; >+ >+ var line = buildLine(node), shadow = self.after(node); >+ if (!shadow || !compareText(shadow.text, line.text) || shadow.to != line.to) { >+ lines.push(line); >+ setTemp(node, line); >+ } >+ }); >+ >+ // Get the BR element after/before the given node. >+ function nextBR(node, dir) { >+ var link = dir + "Sibling", search = node[link]; >+ while (search && search.nodeName != "BR") >+ search = search[link]; >+ return search; >+ } >+ >+ // Assemble line objects into chains by scanning the DOM tree >+ // around them. >+ var chains = []; self.touched = []; >+ forEach(lines, function(line) { >+ // Note that this makes the loop skip line objects that have >+ // been pulled into chains by lines before them. >+ if (!temp(line.from)) return; >+ >+ var chain = [], curNode = line.from, safe = true; >+ // Put any line objects (referred to by temp info) before this >+ // one on the front of the array. >+ while (true) { >+ var curLine = temp(curNode); >+ if (!curLine) { >+ if (safe) break; >+ else curLine = buildLine(curNode); >+ } >+ chain.unshift(curLine); >+ setTemp(curNode, null); >+ if (!curNode) break; >+ safe = self.after(curNode); >+ curNode = nextBR(curNode, "previous"); >+ } >+ curNode = line.to; safe = self.before(line.from); >+ // Add lines after this one at end of array. >+ while (true) { >+ if (!curNode) break; >+ var curLine = temp(curNode); >+ if (!curLine) { >+ if (safe) break; >+ else curLine = buildLine(curNode); >+ } >+ chain.push(curLine); >+ setTemp(curNode, null); >+ safe = self.before(curNode); >+ curNode = nextBR(curNode, "next"); >+ } >+ chains.push(chain); >+ }); >+ >+ return chains; >+ }, >+ >+ // Find the 'shadow' of a given chain by following the links in the >+ // DOM nodes at its start and end. >+ shadowChain: function(chain) { >+ var shadows = [], next = this.after(chain[0].from), end = chain[chain.length - 1].to; >+ while (true) { >+ shadows.push(next); >+ var nextNode = next.to; >+ if (!nextNode || nextNode == end) >+ break; >+ else >+ next = nextNode.historyAfter || this.before(end); >+ // (The this.before(end) is a hack -- FF sometimes removes >+ // properties from BR nodes, in which case the best we can hope >+ // for is to not break.) >+ } >+ return shadows; >+ }, >+ >+ // Update the DOM tree to contain the lines specified in a given >+ // chain, link this chain into the DOM nodes. >+ applyChain: function(chain) { >+ // Some attempt is made to prevent the cursor from jumping >+ // randomly when an undo or redo happens. It still behaves a bit >+ // strange sometimes. >+ var cursor = select.cursorPos(this.container, false), self = this; >+ >+ // Remove all nodes in the DOM tree between from and to (null for >+ // start/end of container). >+ function removeRange(from, to) { >+ var pos = from ? from.nextSibling : self.container.firstChild; >+ while (pos != to) { >+ var temp = pos.nextSibling; >+ removeElement(pos); >+ pos = temp; >+ } >+ } >+ >+ var start = chain[0].from, end = chain[chain.length - 1].to; >+ // Clear the space where this change has to be made. >+ removeRange(start, end); >+ >+ // Build a function that will insert nodes before the end node of >+ // this chain. >+ var insert = end ? >+ function(node) {self.container.insertBefore(node, end);} >+ : function(node) {self.container.appendChild(node);}; >+ >+ // Insert the content specified by the chain into the DOM tree. >+ for (var i = 0; i < chain.length; i++) { >+ var line = chain[i]; >+ // The start and end of the space are already correct, but BR >+ // tags inside it have to be put back. >+ if (i > 0) >+ insert(line.from); >+ // Add the text. >+ var node = makePartSpan(splitSpaces(line.text), this.container.ownerDocument); >+ insert(node); >+ // See if the cursor was on this line. Put it back, adjusting >+ // for changed line length, if it was. >+ if (cursor && cursor.node == line.from) { >+ var cursordiff = 0; >+ var prev = this.after(line.from); >+ if (prev && i == chain.length - 1) { >+ // Only adjust if the cursor is after the unchanged part of >+ // the line. >+ for (var match = 0; match < cursor.offset && >+ line.text.charAt(match) == prev.text.charAt(match); match++); >+ if (cursor.offset > match) >+ cursordiff = line.text.length - prev.text.length; >+ } >+ select.setCursorPos(this.container, {node: line.from, offset: Math.max(0, cursor.offset + cursordiff)}); >+ } >+ // Cursor was in removed line, this is last new line. >+ else if (cursor && (i == chain.length - 1) && cursor.node && cursor.node.parentNode != this.container) { >+ select.setCursorPos(this.container, {node: line.from, offset: line.text.length}); >+ } >+ } >+ >+ // Anchor the chain in the DOM tree. >+ this.linkChain(chain); >+ return start; >+ } >+}; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/util.js b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/util.js >new file mode 100644 >index 0000000..ba2e3d4 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/util.js >@@ -0,0 +1,123 @@ >+/* A few useful utility functions. */ >+ >+// Capture a method on an object. >+function method(obj, name) { >+ return function() {obj[name].apply(obj, arguments);}; >+} >+ >+// The value used to signal the end of a sequence in iterators. >+var StopIteration = {toString: function() {return "StopIteration"}}; >+ >+// Checks whether the argument is an iterator or a regular sequence, >+// turns it into an iterator. >+function iter(seq) { >+ var i = 0; >+ if (seq.next) return seq; >+ else return { >+ next: function() { >+ if (i >= seq.length) throw StopIteration; >+ else return seq[i++]; >+ } >+ }; >+} >+ >+// Apply a function to each element in a sequence. >+function forEach(iter, f) { >+ if (iter.next) { >+ try {while (true) f(iter.next());} >+ catch (e) {if (e != StopIteration) throw e;} >+ } >+ else { >+ for (var i = 0; i < iter.length; i++) >+ f(iter[i]); >+ } >+} >+ >+// Map a function over a sequence, producing an array of results. >+function map(iter, f) { >+ var accum = []; >+ forEach(iter, function(val) {accum.push(f(val));}); >+ return accum; >+} >+ >+// Create a predicate function that tests a string againsts a given >+// regular expression. >+function matcher(regexp){ >+ return function(value){return regexp.test(value);}; >+} >+ >+// Test whether a DOM node has a certain CSS class. Much faster than >+// the MochiKit equivalent, for some reason. >+function hasClass(element, className){ >+ var classes = element.className; >+ return classes && new RegExp("(^| )" + className + "($| )").test(classes); >+} >+ >+// Insert a DOM node after another node. >+function insertAfter(newNode, oldNode) { >+ var parent = oldNode.parentNode; >+ parent.insertBefore(newNode, oldNode.nextSibling); >+ return newNode; >+} >+ >+function removeElement(node) { >+ if (node.parentNode) >+ node.parentNode.removeChild(node); >+} >+ >+function clearElement(node) { >+ while (node.firstChild) >+ node.removeChild(node.firstChild); >+} >+ >+// Check whether a node is contained in another one. >+function isAncestor(node, child) { >+ while (child = child.parentNode) { >+ if (node == child) >+ return true; >+ } >+ return false; >+} >+ >+// The non-breaking space character. >+var nbsp = "\u00a0"; >+var matching = {"{": "}", "[": "]", "(": ")", >+ "}": "{", "]": "[", ")": "("}; >+ >+// Standardize a few unportable event properties. >+function normalizeEvent(event) { >+ if (!event.stopPropagation) { >+ event.stopPropagation = function() {this.cancelBubble = true;}; >+ event.preventDefault = function() {this.returnValue = false;}; >+ } >+ if (!event.stop) { >+ event.stop = function() { >+ this.stopPropagation(); >+ this.preventDefault(); >+ }; >+ } >+ >+ if (event.type == "keypress") { >+ if (event.charCode === 0 || event.charCode == undefined) >+ event.code = event.keyCode; >+ else >+ event.code = event.charCode; >+ event.character = String.fromCharCode(event.code); >+ } >+ return event; >+} >+ >+// Portably register event handlers. >+function addEventHandler(node, type, handler, removeFunc) { >+ function wrapHandler(event) { >+ handler(normalizeEvent(event || window.event)); >+ } >+ if (typeof node.addEventListener == "function") { >+ node.addEventListener(type, wrapHandler, false); >+ if (removeFunc) return function() {node.removeEventListener(type, wrapHandler, false);}; >+ } >+ else { >+ node.attachEvent("on" + type, wrapHandler); >+ if (removeFunc) return function() {node.detachEvent("on" + type, wrapHandler);}; >+ } >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref >index 4fc1305..470a364 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref >@@ -12,6 +12,13 @@ Cataloging: > yes: "Don't display" > no: Display > - descriptions of fields and subfields in the MARC editor. >+ - >+ - Use a >+ - pref: MARCEditor >+ choices: >+ normal: "guided" >+ text: "textual" >+ - editor for MARC records. > Spine Labels: > - > - When using the quick spine label printer, >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio-text.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio-text.tt >new file mode 100644 >index 0000000..6acb6ba >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio-text.tt >@@ -0,0 +1,155 @@ >+[% INCLUDE 'doc-head-open.inc' %] >+<title>Koha › Cataloging › [% IF ( biblionumber ) %]Editing [% title |html %] (Record Number [% biblionumber %])[% ELSE %]Add MARC Record[% END %]</title> >+[% INCLUDE 'doc-head-close.inc' %] >+<script type="text/javascript" src="[% themelang %]/lib/yui/plugins/bubbling-min.js"></script> >+<link rel="stylesheet" type="text/css" href="[% themelang %]/css/humanmsg.css" /> >+<script src="[% themelang %]/lib/jquery/plugins/humanmsg.js" type="text/javascript"></script> >+<style type="text/css"> >+ .controls {clear: both} >+ #scratchpad {float: right; color: #888; display: none; width: 47%} >+ #record {float: left; width: 47%} >+ #close-scratchpad {display: none} >+</style> >+</head> >+<body> >+<div id="yui-cms-loading"> >+ <div id="yui-cms-float"> >+ Loading, please wait... >+ </div> >+ </div> >+<script type="text/javascript" src="[% themelang %]/lib/yui/plugins/loading-min.js"></script> >+<script type="text/javascript"> >+//<![CDATA[ >+(function() { >+ // configuring the loading mask >+ YAHOO.widget.Loading.config({ >+ opacity: 0.8 >+ }); >+})(); >+//]]> >+</script> >+[% INCLUDE 'header.inc' %] >+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> › <a href="/cgi-bin/koha/cataloguing/addbooks.pl">Cataloging</a> › [% IF ( biblionumber ) %]Editing <em>[% title |html %]</em> (Record Number [% biblionumber %])[% ELSE %]Add MARC Record[% END %]</div> >+ >+<div id="doc" class="yui-t7"> >+ >+<div id="bd"> >+ <div id="yui-main"> >+ <div class="yui-g"> >+ >+ >+ >+<h1>[% IF ( biblionumber ) %]Editing <em>[% title |html %]</em> (Record Number [% biblionumber %])</h1>[% ELSE %]Add MARC Record</h1>[% END %] >+ >+[% UNLESS ( number ) %] >+ <!-- show duplicate warning on tab 0 only --> >+ [% IF ( duplicatebiblionumber ) %] >+ <div class="dialog alert"> >+ <h4>Duplicate Record suspected</h4> >+ <p>Is this a duplicate of <a href="/cgi-bin/koha/catalogue/MARCdetail.pl?biblionumber=[% duplicatebiblionumber %]" onclick="openWindow('../MARCdetail.pl?biblionumber=[% duplicatebiblionumber %]&popup=1', 'Duplicate biblio'; return false;)">[% duplicatetitle %]</a>?</p> >+ <form action="/cgi-bin/koha/cataloguing/additem.pl" method="get"> >+ <input type="hidden" name="biblionumber" value="[% duplicatebiblionumber %]" /> >+ <input type="submit" class="edit" value="Yes: Edit existing items" /> >+ </form> >+ <form action="/cgi-bin/koha/cataloguing/addbiblio-text.pl" method="get"> >+ <input type="submit" class="save" onclick="addbiblio.not_duplicate(); return false;" value="No: Save as New Record" /> >+ </form> >+ </div> >+ [% END %] >+ [% END %] >+ >+[% IF ( done ) %] >+ <script type="text/javascript"> >+ opener.document.forms['f'].biblionumber.value=[% biblionumber %]; >+ opener.document.forms['f'].title.value='[% title |html %]'; >+ window.close(); >+ </script> >+[% ELSE %] >+ <form method="post" name="f" id="f" action="/cgi-bin/koha/cataloguing/addbiblio-text.pl" onsubmit="addbiblio.submit(); return false"> >+ <input type="hidden" value="0" id="confirm_not_duplicate" name="confirm_not_duplicate" /> >+[% END %] >+ >+<div id="toolbar"> >+ >+<script type="text/javascript"> >+ //<![CDATA[ >+ >+ // prepare DOM for YUI Toolbar >+ >+ $(document).ready(function() { >+ $("#z3950searchc").empty(); >+ yuiToolbar(); >+ }); >+ >+ // YUI Toolbar Functions >+ >+ function yuiToolbar() { >+ new YAHOO.widget.Button("addbiblio"); >+ new YAHOO.widget.Button({ >+ id: "z3950search", >+ type: "button", >+ label: _("z39.50 Search"), >+ container: "z3950searchc", >+ onclick: {fn:function(){addbiblio.z3950_search()}} >+ }); >+ } >+ >+ //]]> >+ </script> >+ >+ <ul class="toolbar"> >+ <li><input id="addbiblio" type="submit" value="Save" /></li> >+ <li id="z3950searchc"><input type="button" id="z3950search" value="z39.50 Search" onclick="PopupZ3950(); return false;" /></li> >+ <li id="changeframework"><label for="Frameworks">Change framework: </label> >+ <select name="Frameworks" id="Frameworks" onchange="Changefwk(this);"> >+ <option value="">Default</option> >+ [% FOREACH frameworkcodeloo IN frameworkcodeloop %] >+ <option value="[% frameworkcodeloo.value %]" [% frameworkcodeloo.selected %]> >+ [% frameworkcodeloo.frameworktext %] >+ </option> >+ [% END %] >+ </select> >+<input type="hidden" name="op" value="addbiblio" /></li> >+ </ul> >+</div> >+ >+[% IF ( popup ) %] >+ <input type="hidden" name="mode" value="popup" /> >+[% END %] >+ <input type="hidden" name="frameworkcode" value="[% frameworkcode %]" /> >+ <input type="hidden" name="biblionumber" value="[% biblionumber %]" /> >+ <input type="hidden" name="breedingid" value="[% breedingid %]" /> >+ <p> >+ <label for="itemtypes">Insert Item Type Code for: </label> >+ <select id="itemtypes"> >+ [% FOREACH itemtype IN itemtypes %] >+ <option value="[% itemtype.value %]">[% itemtype.description %]</option> >+ [% END %] >+ </select> >+ <button id="insert-itemtype">Insert</button> >+ </p> >+ >+ <textarea name="record" id="record" rows="20">[% BIG_LOOP %]</textarea> >+ [% FOREACH HIDDEN_LOO IN HIDDEN_LOOP %] >+ <input type="hidden" name="tag_[% HIDDEN_LOO.tag %]_indicator1_[% HIDDEN_LOO.index %][% HIDDEN_LOO.random %]" value="" /> >+ <input type="hidden" name="tag_[% HIDDEN_LOO.tag %]_indicator2_[% HIDDEN_LOO.index %][% HIDDEN_LOO.random %]" value="" /> >+ <input type="hidden" name="tag_[% HIDDEN_LOO.tag %]_code_[% HIDDEN_LOO.subfield %]_[% HIDDEN_LOO.index %]_[% HIDDEN_LOO.index_subfield %]" value="[% HIDDEN_LOO.subfield %]" /> >+ <input type="hidden" name="tag_[% HIDDEN_LOO.tag %]_subfield_[% HIDDEN_LOO.subfield %]_[% HIDDEN_LOO.index %]_[% HIDDEN_LOO.index_subfield %]" value="[% HIDDEN_LOO.subfield_value %]" /> >+ [% END %] >+ >+</form> >+ >+</div> >+</div> >+ >+<script type="text/javascript" src="[% themelang %]/lib/codemirror/js/codemirror.js"></script> >+<script type="text/javascript" src="[% themelang %]/js/marc.js"></script> >+<script type="text/javascript" src="[% themelang %]/js/pages/addbiblio-text.js"></script> >+<script type="text/javascript"> >+ <!-- >+ addbiblio.biblionumber = [% biblionumber or 0 %]; >+ // --> >+</script> >+<script type="text/javascript" src="/cgi-bin/koha/cataloguing/framework-jsonp.pl?prepend=addbiblio.mandatory%3D&info=mandatory"></script> >+ >+[% INCLUDE 'intranet-bottom.inc' %] >-- >1.7.6
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 6707
:
4956
|
5061
|
5101
|
10014