Revision 480

Date:
2015/03/03 12:14:40
Author:
ahitrov
Revision Log:
Tags plugin: initial import

Files:

Legend:

 
Added
 
Removed
 
Modified
  • utf8/plugins/tag/comps/contenido/components/inputs/tagset.msn

     
    1 % if ( $object->id ) {
    2 <script type="text/javascript">
    3 <!--
    4 function <% $name %>_onAddTag(tag) {
    5 $.ajax({
    6 'url' : '/contenido/tag/ajax/manage.html',
    7 'type' : 'GET',
    8 'data' : { 'action' : 'add', 'id' : <% $object->id %>, 'class' : '<% $object->class %>', 'tag' : tag },
    9 'dataType' : 'json',
    10 'success' : function( data ) {
    11 if ( data.fallback ) {
    12 alert('Не могу добавить тег ' + tag + '. Откат');
    13 $('#<% $name%>_text').removeTag(tag);
    14 }
    15 }
    16 });
    17 }
    18
    19 function <% $name %>_onRemoveTag(tag) {
    20 $.ajax({
    21 'url' : '/contenido/tag/ajax/manage.html',
    22 'type' : 'GET',
    23 'data' : { 'action' : 'remove', 'id' : <% $object->id %>, 'class' : '<% $object->class %>', 'tag' : tag },
    24 'dataType' : 'json',
    25 'success' : function( data ) {
    26 if ( data.fallback ) {
    27 alert('Не могу удалить тег ' + tag + '. Откат');
    28 $('#<% $name%>_text').addTag(tag);
    29 }
    30 }
    31 });
    32 }
    33
    34 $(document).ready(function() {
    35
    36 $('#<% $name %>_text').tagsInput({
    37 'width' : 'auto',
    38 'height' : '60px',
    39 'minChars' : 3,
    40 'autocomplete_url' : '/contenido/tag/ajax/search.html?id=<% $object->id %>&class=<% $object->class %>',
    41 'onAddTag' : <% $name %>_onAddTag,
    42 'onRemoveTag' : <% $name %>_onRemoveTag,
    43 'defaultText' : 'Добавьте тег'
    44 });
    45
    46 % if ( @tags ) {
    47 $('#<% $name %>_text').importTags('<% join(',', map { $_->name } @tags) %>');
    48 % }
    49
    50 });
    51 //-->
    52 </script>
    53 <div style="width:95%;">
    54 <input type="text" name="<% $name %>" id="<% $name %>_text" placeholder="Tags" class="tm-input"/>
    55 </div>
    56 % } else {
    57 <div style="width:95%; padding:10px; border:1px solid green; background:#f0fff0">
    58 <div style="color:green">Ввод тегов возможен только после сохранения документа</div>
    59 </div>
    60 % }
    61 <%once>
    62
    63 use Data::Recursive::Encode;
    64 use JSON::XS;
    65 my $json = JSON::XS->new->utf8;
    66
    67 </%once>
    68 <%args>
    69
    70 $object
    71 $name => undef
    72 $check => undef
    73 $prop => {}
    74
    75 </%args>
    76 <%init>
    77
    78 my @tags;
    79 if ( $object->id ) {
    80 @tags = $keeper->get_documents(
    81 class => 'tag::Tag',
    82 lclass => 'tag::Cloud',
    83 ldest => $object->id,
    84 ldestclass => $object->class,
    85 );
    86 }
    87 my $value;
    88 unless ( $prop->{virtual} ) {
    89 if ( ref $object->$name ) {
    90 $value = $object->$name;
    91 } elsif ( $object->$name ) {
    92 $value = Data::Recursive::Encode->encode_utf8( $json->decode( $object->$name ) );
    93 }
    94 }
    95
    96 </%init>
  • utf8/plugins/tag/comps/contenido/components/outputs/tagset.msn

     
    1 <%once>
    2
    3 use JSON::XS;
    4 my $json = JSON::XS->new;
    5
    6 </%once>
    7 <%args>
    8
    9 $object => undef
    10 $name => undef
    11 $SETS => undef
    12
    13 </%args>
    14 <%init>
    15
    16 return undef unless ref $SETS;
    17 return undef unless $name;
    18
    19 return undef if $SETS->{$name} eq '';
    20
    21 warn "\nTAG Store:\n" if $DEBUG;
    22 warn Dumper $SETS->{$name} if $DEBUG;
    23
    24 my ($prop) = grep { $_->{attr} eq $name } $object->structure;
    25 return undef if exists $prop->{virtual} && $prop->{virtual};
    26 my $class = $object->class;
    27 my $is_extra = grep { ref $_ && $_->{attr} eq $name } $class->extra_properties ? 1 : 0;
    28
    29 my $result;
    30 my @tags;
    31 if ( $object->id ) {
    32 @tags = $keeper->get_documents(
    33 class => 'tag::Tag',
    34 lclass => 'tag::Cloud',
    35 ldest => $object->id,
    36 ldestclass => $object->class,
    37 );
    38 if ( @tags ) {
    39 $result = [];
    40 foreach my $tag ( @tags ) {
    41 push @$result, { id => $tag->id, name => Encode::decode('utf-8', $tag->name) };
    42 }
    43 }
    44 unless ( $is_extra ) {
    45 $result = Encode::encode('utf-8', $json->encode( $result ));
    46 }
    47 warn Dumper $result if $DEBUG;
    48 return $result;
    49 } else {
    50 return undef;
    51 }
    52
    53
    54 </%init>
  • utf8/plugins/tag/comps/contenido/tag/ajax/manage.html

     
    1 <% $json %>
    2 <%once>
    3
    4 use JSON::XS;
    5
    6 </%once>
    7 <%args>
    8
    9 $action => undef
    10 $id => undef
    11 $class => undef
    12 $tag => undef
    13
    14 </%args>
    15 <%init>
    16
    17 my %result;
    18 warn Dumper \%ARGS;
    19 my $doc = $keeper->get_document_by_id( $id, class => $class );
    20
    21 if ( ref $doc && $ARGS{tag} ) {
    22 my @links = $keeper->get_links(
    23 class => 'tag::Cloud',
    24 dest_id => $id,
    25 dest_class => $class,
    26 );
    27 my @tags;
    28 if ( @links ) {
    29 my %ids = map { $_->source_id => 1 } @links;
    30 my @ids = keys %ids;
    31 @tags = $keeper->get_documents(
    32 id => \@ids,
    33 class => 'tag::Tag',
    34 ) if @ids;
    35 }
    36 my ($tobj) = $keeper->get_documents(
    37 class => 'tag::Tag',
    38 name => $ARGS{tag},
    39 ilike => 1,
    40 limit => 1,
    41 );
    42 if ( $action eq 'add' ) {
    43 unless ( ref $tobj ) {
    44 $tobj = tag::Tag->new( $keeper );
    45 $tobj->name( $ARGS{tag} );
    46 $tobj->status( 1 );
    47 $tobj->store;
    48 }
    49 my $exists = (grep { $_->id == $tobj->id } @tags) ? 1 : 0;
    50 if ( ref $tobj && !$exists ) {
    51 my $link = tag::Cloud->new( $keeper );
    52 $link->source_id( $tobj->id );
    53 $link->source_class( $tobj->class );
    54 $link->dest_id( $id );
    55 $link->dest_class( $class );
    56 $link->status( 1 );
    57 $link->store;
    58 } else {
    59 $result{fallback} = 1;
    60 }
    61 } elsif ( $action eq 'remove' ) {
    62 if ( ref $tobj ) {
    63 my @delete = grep { $_->source_id == $tobj->id } @links;
    64 foreach my $obj ( @delete ) {
    65 $obj->delete;
    66 }
    67 my $count = $keeper->get_links(
    68 class => 'tag::Cloud',
    69 count => 1,
    70 source_id => $tobj->id,
    71 );
    72 $tobj->delete unless $count;
    73 } else {
    74 $result{fallback} = 1;
    75 }
    76 }
    77 }
    78 $result{ok} = 1 unless exists $result{fallback};
    79
    80 my $json = encode_json \%result;
    81
    82 </%init>
  • utf8/plugins/tag/comps/contenido/tag/ajax/search.html

     
    1 <% $json %>
    2 <%once>
    3
    4 use JSON::XS;
    5
    6 </%once>
    7 <%args>
    8
    9 $id => undef
    10 $class => undef
    11 $term => undef
    12
    13 </%args>
    14 <%init>
    15
    16 warn Dumper \%ARGS if $DEBUG;
    17 return if length( $ARGS{term} ) < 3;
    18
    19 my @links = $keeper->get_links(
    20 class => 'tag::Cloud',
    21 dest_class => $class,
    22 dest_id => $id,
    23 );
    24 my %ex = map { $_->source_id => 1 } @links;
    25 my @ex = keys %ex;
    26
    27 my @tags = $keeper->get_documents(
    28 @ex ? ( excludes => \@ex ) : (),
    29 class => 'tag::Tag',
    30 status => 1,
    31 name => '%'.$ARGS{term}.'%',
    32 ilike => 1,
    33 order_by => 'name',
    34 );
    35 return unless @tags;
    36
    37 my @result = map { {id => $_->id, label => Encode::decode('utf-8', $_->name), value => Encode::decode('utf-8', $_->name)} } @tags;
    38
    39 my $json = encode_json \@result;
    40
    41 </%init>
  • utf8/plugins/tag/comps/contenido/tag/autohandler

     
    1 <%init>
    2
    3 $r->content_type('text/html');
    4 $m->call_next();
    5
    6 </%init>
  • utf8/plugins/tag/comps/contenido/tag/components/title_inc.msn

     
    1 <script language="javascript" type="text/javascript" src="/contenido/tag/i/js/jquery.tagsinput.js"></script>
    2 <link href="/contenido/tag/i/js/jquery.tagsinput.css" rel="stylesheet" type="text/css">
  • utf8/plugins/tag/comps/contenido/tag/dhandler

     
    1 <& $call, %ARGS &>
    2 <%init>
    3
    4 my $call;
    5 if ( $r->uri eq '/contenido/tag/' ) {
    6 $call = 'index.html';
    7 } else {
    8 &abort404;
    9 }
    10
    11 </%init>
  • utf8/plugins/tag/comps/contenido/tag/i/js/jquery.tagsinput.css

     
    1 div.tagsinput { border:1px solid #CCC; background: #FFF; padding:5px; width:300px; height:100px; overflow-y: auto;}
    2 div.tagsinput span.tag { border: 1px solid #a5d24a; -moz-border-radius:2px; -webkit-border-radius:2px; display: block; float: left; padding: 5px; text-decoration:none; background: #cde69c; color: #638421; margin-right: 5px; margin-bottom:5px;font-family: helvetica; font-size:13px;}
    3 div.tagsinput span.tag a { font-weight: bold; color: #82ad2b; text-decoration:none; font-size: 11px; }
    4 div.tagsinput input { width:100px; margin:0px; font-family: helvetica; font-size: 13px; border:1px solid transparent; padding:5px; background: transparent; color: #000; outline:0px; margin-right:5px; margin-bottom:5px; }
    5 div.tagsinput div { display:block; float: left; }
    6 .tags_clear { clear: both; width: 100%; height: 0px; }
    7 .not_valid {background: #FBD8DB !important; color: #90111A !important;}
  • utf8/plugins/tag/comps/contenido/tag/i/js/jquery.tagsinput.js

     
    1 /*
    2
    3 jQuery Tags Input Plugin 1.3.3
    4
    5 Copyright (c) 2011 XOXCO, Inc
    6
    7 Documentation for this plugin lives here:
    8 http://xoxco.com/clickable/jquery-tags-input
    9
    10 Licensed under the MIT license:
    11 http://www.opensource.org/licenses/mit-license.php
    12
    13 ben@xoxco.com
    14
    15 */
    16
    17 (function($) {
    18
    19 var delimiter = new Array();
    20 var tags_callbacks = new Array();
    21 $.fn.doAutosize = function(o){
    22 var minWidth = $(this).data('minwidth'),
    23 maxWidth = $(this).data('maxwidth'),
    24 val = '',
    25 input = $(this),
    26 testSubject = $('#'+$(this).data('tester_id'));
    27
    28 if (val === (val = input.val())) {return;}
    29
    30 // Enter new content into testSubject
    31 var escaped = val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    32 testSubject.html(escaped);
    33 // Calculate new width + whether to change
    34 var testerWidth = testSubject.width(),
    35 newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
    36 currentWidth = input.width(),
    37 isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
    38 || (newWidth > minWidth && newWidth < maxWidth);
    39
    40 // Animate width
    41 if (isValidWidthChange) {
    42 input.width(newWidth);
    43 }
    44
    45
    46 };
    47 $.fn.resetAutosize = function(options){
    48 // alert(JSON.stringify(options));
    49 var minWidth = $(this).data('minwidth') || options.minInputWidth || $(this).width(),
    50 maxWidth = $(this).data('maxwidth') || options.maxInputWidth || ($(this).closest('.tagsinput').width() - options.inputPadding),
    51 val = '',
    52 input = $(this),
    53 testSubject = $('<tester/>').css({
    54 position: 'absolute',
    55 top: -9999,
    56 left: -9999,
    57 width: 'auto',
    58 fontSize: input.css('fontSize'),
    59 fontFamily: input.css('fontFamily'),
    60 fontWeight: input.css('fontWeight'),
    61 letterSpacing: input.css('letterSpacing'),
    62 whiteSpace: 'nowrap'
    63 }),
    64 testerId = $(this).attr('id')+'_autosize_tester';
    65 if(! $('#'+testerId).length > 0){
    66 testSubject.attr('id', testerId);
    67 testSubject.appendTo('body');
    68 }
    69
    70 input.data('minwidth', minWidth);
    71 input.data('maxwidth', maxWidth);
    72 input.data('tester_id', testerId);
    73 input.css('width', minWidth);
    74 };
    75
    76 $.fn.addTag = function(value,options) {
    77 options = jQuery.extend({focus:false,callback:true},options);
    78 this.each(function() {
    79 var id = $(this).attr('id');
    80
    81 var tagslist = $(this).val().split(delimiter[id]);
    82 if (tagslist[0] == '') {
    83 tagslist = new Array();
    84 }
    85
    86 value = jQuery.trim(value);
    87
    88 if (options.unique) {
    89 var skipTag = $(this).tagExist(value);
    90 if(skipTag == true) {
    91 //Marks fake input as not_valid to let styling it
    92 $('#'+id+'_tag').addClass('not_valid');
    93 }
    94 } else {
    95 var skipTag = false;
    96 }
    97
    98 if (value !='' && skipTag != true) {
    99 $('<span>').addClass('tag').append(
    100 $('<span>').text(value).append('&nbsp;&nbsp;'),
    101 $('<a>', {
    102 href : '#',
    103 title : 'Removing tag',
    104 text : 'x'
    105 }).click(function () {
    106 return $('#' + id).removeTag(escape(value));
    107 })
    108 ).insertBefore('#' + id + '_addTag');
    109
    110 tagslist.push(value);
    111
    112 $('#'+id+'_tag').val('');
    113 if (options.focus) {
    114 $('#'+id+'_tag').focus();
    115 } else {
    116 $('#'+id+'_tag').blur();
    117 }
    118
    119 $.fn.tagsInput.updateTagsField(this,tagslist);
    120
    121 if (options.callback && tags_callbacks[id] && tags_callbacks[id]['onAddTag']) {
    122 var f = tags_callbacks[id]['onAddTag'];
    123 f.call(this, value);
    124 }
    125 if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
    126 {
    127 var i = tagslist.length;
    128 var f = tags_callbacks[id]['onChange'];
    129 f.call(this, $(this), tagslist[i-1]);
    130 }
    131 }
    132
    133 });
    134
    135 return false;
    136 };
    137
    138 $.fn.removeTag = function(value) {
    139 value = unescape(value);
    140 this.each(function() {
    141 var id = $(this).attr('id');
    142
    143 var old = $(this).val().split(delimiter[id]);
    144
    145 $('#'+id+'_tagsinput .tag').remove();
    146 str = '';
    147 for (i=0; i< old.length; i++) {
    148 if (old[i]!=value) {
    149 str = str + delimiter[id] +old[i];
    150 }
    151 }
    152
    153 $.fn.tagsInput.importTags(this,str);
    154
    155 if (tags_callbacks[id] && tags_callbacks[id]['onRemoveTag']) {
    156 var f = tags_callbacks[id]['onRemoveTag'];
    157 f.call(this, value);
    158 }
    159 });
    160
    161 return false;
    162 };
    163
    164 $.fn.tagExist = function(val) {
    165 var id = $(this).attr('id');
    166 var tagslist = $(this).val().split(delimiter[id]);
    167 return (jQuery.inArray(val, tagslist) >= 0); //true when tag exists, false when not
    168 };
    169
    170 // clear all existing tags and import new ones from a string
    171 $.fn.importTags = function(str) {
    172 id = $(this).attr('id');
    173 $('#'+id+'_tagsinput .tag').remove();
    174 $.fn.tagsInput.importTags(this,str);
    175 }
    176
    177 $.fn.tagsInput = function(options) {
    178 var settings = jQuery.extend({
    179 interactive:true,
    180 defaultText:'add a tag',
    181 minChars:0,
    182 width:'300px',
    183 height:'100px',
    184 autocomplete: {selectFirst: false },
    185 'hide':true,
    186 'delimiter':',',
    187 'unique':true,
    188 removeWithBackspace:true,
    189 placeholderColor:'#666666',
    190 autosize: true,
    191 comfortZone: 20,
    192 inputPadding: 6*2
    193 },options);
    194
    195 this.each(function() {
    196 if (settings.hide) {
    197 $(this).hide();
    198 }
    199 var id = $(this).attr('id');
    200 if (!id || delimiter[$(this).attr('id')]) {
    201 id = $(this).attr('id', 'tags' + new Date().getTime()).attr('id');
    202 }
    203
    204 var data = jQuery.extend({
    205 pid:id,
    206 real_input: '#'+id,
    207 holder: '#'+id+'_tagsinput',
    208 input_wrapper: '#'+id+'_addTag',
    209 fake_input: '#'+id+'_tag'
    210 },settings);
    211
    212 delimiter[id] = data.delimiter;
    213
    214 if (settings.onAddTag || settings.onRemoveTag || settings.onChange) {
    215 tags_callbacks[id] = new Array();
    216 tags_callbacks[id]['onAddTag'] = settings.onAddTag;
    217 tags_callbacks[id]['onRemoveTag'] = settings.onRemoveTag;
    218 tags_callbacks[id]['onChange'] = settings.onChange;
    219 }
    220
    221 var markup = '<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag">';
    222
    223 if (settings.interactive) {
    224 markup = markup + '<input id="'+id+'_tag" value="" data-default="'+settings.defaultText+'" />';
    225 }
    226
    227 markup = markup + '</div><div class="tags_clear"></div></div>';
    228
    229 $(markup).insertAfter(this);
    230
    231 $(data.holder).css('width',settings.width);
    232 $(data.holder).css('min-height',settings.height);
    233 $(data.holder).css('height','100%');
    234
    235 if ($(data.real_input).val()!='') {
    236 $.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
    237 }
    238 if (settings.interactive) {
    239 $(data.fake_input).val($(data.fake_input).attr('data-default'));
    240 $(data.fake_input).css('color',settings.placeholderColor);
    241 $(data.fake_input).resetAutosize(settings);
    242
    243 $(data.holder).bind('click',data,function(event) {
    244 $(event.data.fake_input).focus();
    245 });
    246
    247 $(data.fake_input).bind('focus',data,function(event) {
    248 if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('data-default')) {
    249 $(event.data.fake_input).val('');
    250 }
    251 $(event.data.fake_input).css('color','#000000');
    252 });
    253
    254 if (settings.autocomplete_url != undefined) {
    255 autocomplete_options = {source: settings.autocomplete_url};
    256 for (attrname in settings.autocomplete) {
    257 autocomplete_options[attrname] = settings.autocomplete[attrname];
    258 }
    259
    260 if (jQuery.Autocompleter !== undefined) {
    261 $(data.fake_input).autocomplete(settings.autocomplete_url, settings.autocomplete);
    262 $(data.fake_input).bind('result',data,function(event,data,formatted) {
    263 if (data) {
    264 $('#'+id).addTag(data[0] + "",{focus:true,unique:(settings.unique)});
    265 }
    266 });
    267 } else if (jQuery.ui.autocomplete !== undefined) {
    268 $(data.fake_input).autocomplete(autocomplete_options);
    269 $(data.fake_input).bind('autocompleteselect',data,function(event,ui) {
    270 $(event.data.real_input).addTag(ui.item.value,{focus:true,unique:(settings.unique)});
    271 return false;
    272 });
    273 }
    274
    275
    276 } else {
    277 // if a user tabs out of the field, create a new tag
    278 // this is only available if autocomplete is not used.
    279 $(data.fake_input).bind('blur',data,function(event) {
    280 var d = $(this).attr('data-default');
    281 if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
    282 if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
    283 $(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
    284 } else {
    285 $(event.data.fake_input).val($(event.data.fake_input).attr('data-default'));
    286 $(event.data.fake_input).css('color',settings.placeholderColor);
    287 }
    288 return false;
    289 });
    290
    291 }
    292 // if user types a comma, create a new tag
    293 $(data.fake_input).bind('keypress',data,function(event) {
    294 if (event.which==event.data.delimiter.charCodeAt(0) || event.which==13 ) {
    295 event.preventDefault();
    296 if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
    297 $(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
    298 $(event.data.fake_input).resetAutosize(settings);
    299 return false;
    300 } else if (event.data.autosize) {
    301 $(event.data.fake_input).doAutosize(settings);
    302
    303 }
    304 });
    305 //Delete last tag on backspace
    306 data.removeWithBackspace && $(data.fake_input).bind('keydown', function(event)
    307 {
    308 if(event.keyCode == 8 && $(this).val() == '')
    309 {
    310 event.preventDefault();
    311 var last_tag = $(this).closest('.tagsinput').find('.tag:last').text();
    312 var id = $(this).attr('id').replace(/_tag$/, '');
    313 last_tag = last_tag.replace(/[\s]+x$/, '');
    314 $('#' + id).removeTag(escape(last_tag));
    315 $(this).trigger('focus');
    316 }
    317 });
    318 $(data.fake_input).blur();
    319
    320 //Removes the not_valid class when user changes the value of the fake input
    321 if(data.unique) {
    322 $(data.fake_input).keydown(function(event){
    323 if(event.keyCode == 8 || String.fromCharCode(event.which).match(/\w+|[áéíóúÁÉÍÓÚñÑ,/]+/)) {
    324 $(this).removeClass('not_valid');
    325 }
    326 });
    327 }
    328 } // if settings.interactive
    329 });
    330
    331 return this;
    332
    333 };
    334
    335 $.fn.tagsInput.updateTagsField = function(obj,tagslist) {
    336 var id = $(obj).attr('id');
    337 $(obj).val(tagslist.join(delimiter[id]));
    338 };
    339
    340 $.fn.tagsInput.importTags = function(obj,val) {
    341 $(obj).val('');
    342 var id = $(obj).attr('id');
    343 var tags = val.split(delimiter[id]);
    344 for (i=0; i<tags.length; i++) {
    345 $(obj).addTag(tags[i],{focus:false,callback:false});
    346 }
    347 if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
    348 {
    349 var f = tags_callbacks[id]['onChange'];
    350 f.call(obj, obj, tags[i]);
    351 }
    352 };
    353
    354 })(jQuery);
  • utf8/plugins/tag/comps/contenido/tag/i/js/jquery.tagsinput.min.js

     
    1 (function(a){var b=new Array;var c=new Array;a.fn.doAutosize=function(b){var c=a(this).data("minwidth"),d=a(this).data("maxwidth"),e="",f=a(this),g=a("#"+a(this).data("tester_id"));if(e===(e=f.val())){return}var h=e.replace(/&/g,"&").replace(/\s/g," ").replace(/</g,"<").replace(/>/g,">");g.html(h);var i=g.width(),j=i+b.comfortZone>=c?i+b.comfortZone:c,k=f.width(),l=j<k&&j>=c||j>c&&j<d;if(l){f.width(j)}};a.fn.resetAutosize=function(b){var c=a(this).data("minwidth")||b.minInputWidth||a(this).width(),d=a(this).data("maxwidth")||b.maxInputWidth||a(this).closest(".tagsinput").width()-b.inputPadding,e="",f=a(this),g=a("<tester/>").css({position:"absolute",top:-9999,left:-9999,width:"auto",fontSize:f.css("fontSize"),fontFamily:f.css("fontFamily"),fontWeight:f.css("fontWeight"),letterSpacing:f.css("letterSpacing"),whiteSpace:"nowrap"}),h=a(this).attr("id")+"_autosize_tester";if(!a("#"+h).length>0){g.attr("id",h);g.appendTo("body")}f.data("minwidth",c);f.data("maxwidth",d);f.data("tester_id",h);f.css("width",c)};a.fn.addTag=function(d,e){e=jQuery.extend({focus:false,callback:true},e);this.each(function(){var f=a(this).attr("id");var g=a(this).val().split(b[f]);if(g[0]==""){g=new Array}d=jQuery.trim(d);if(e.unique){var h=a(g).tagExist(d);if(h==true){a("#"+f+"_tag").addClass("not_valid")}}else{var h=false}if(d!=""&&h!=true){a("<span>").addClass("tag").append(a("<span>").text(d).append("  "),a("<a>",{href:"#",title:"Removing tag",text:"x"}).click(function(){return a("#"+f).removeTag(escape(d))})).insertBefore("#"+f+"_addTag");g.push(d);a("#"+f+"_tag").val("");if(e.focus){a("#"+f+"_tag").focus()}else{a("#"+f+"_tag").blur()}a.fn.tagsInput.updateTagsField(this,g);if(e.callback&&c[f]&&c[f]["onAddTag"]){var i=c[f]["onAddTag"];i.call(this,d)}if(c[f]&&c[f]["onChange"]){var j=g.length;var i=c[f]["onChange"];i.call(this,a(this),g[j-1])}}});return false};a.fn.removeTag=function(d){d=unescape(d);this.each(function(){var e=a(this).attr("id");var f=a(this).val().split(b[e]);a("#"+e+"_tagsinput .tag").remove();str="";for(i=0;i<f.length;i++){if(f[i]!=d){str=str+b[e]+f[i]}}a.fn.tagsInput.importTags(this,str);if(c[e]&&c[e]["onRemoveTag"]){var g=c[e]["onRemoveTag"];g.call(this,d)}});return false};a.fn.tagExist=function(b){return jQuery.inArray(b,a(this))>=0};a.fn.importTags=function(b){id=a(this).attr("id");a("#"+id+"_tagsinput .tag").remove();a.fn.tagsInput.importTags(this,b)};a.fn.tagsInput=function(d){var e=jQuery.extend({interactive:true,defaultText:"add a tag",minChars:0,width:"300px",height:"100px",autocomplete:{selectFirst:false},hide:true,delimiter:",",unique:true,removeWithBackspace:true,placeholderColor:"#666666",autosize:true,comfortZone:20,inputPadding:6*2},d);this.each(function(){if(e.hide){a(this).hide()}var d=a(this).attr("id");if(!d||b[a(this).attr("id")]){d=a(this).attr("id","tags"+(new Date).getTime()).attr("id")}var f=jQuery.extend({pid:d,real_input:"#"+d,holder:"#"+d+"_tagsinput",input_wrapper:"#"+d+"_addTag",fake_input:"#"+d+"_tag"},e);b[d]=f.delimiter;if(e.onAddTag||e.onRemoveTag||e.onChange){c[d]=new Array;c[d]["onAddTag"]=e.onAddTag;c[d]["onRemoveTag"]=e.onRemoveTag;c[d]["onChange"]=e.onChange}var g='<div id="'+d+'_tagsinput" class="tagsinput"><div id="'+d+'_addTag">';if(e.interactive){g=g+'<input id="'+d+'_tag" value="" data-default="'+e.defaultText+'" />'}g=g+'</div><div class="tags_clear"></div></div>';a(g).insertAfter(this);a(f.holder).css("width",e.width);a(f.holder).css("height",e.height);if(a(f.real_input).val()!=""){a.fn.tagsInput.importTags(a(f.real_input),a(f.real_input).val())}if(e.interactive){a(f.fake_input).val(a(f.fake_input).attr("data-default"));a(f.fake_input).css("color",e.placeholderColor);a(f.fake_input).resetAutosize(e);a(f.holder).bind("click",f,function(b){a(b.data.fake_input).focus()});a(f.fake_input).bind("focus",f,function(b){if(a(b.data.fake_input).val()==a(b.data.fake_input).attr("data-default")){a(b.data.fake_input).val("")}a(b.data.fake_input).css("color","#000000")});if(e.autocomplete_url!=undefined){autocomplete_options={source:e.autocomplete_url};for(attrname in e.autocomplete){autocomplete_options[attrname]=e.autocomplete[attrname]}if(jQuery.Autocompleter!==undefined){a(f.fake_input).autocomplete(e.autocomplete_url,e.autocomplete);a(f.fake_input).bind("result",f,function(b,c,f){if(c){a("#"+d).addTag(c[0]+"",{focus:true,unique:e.unique})}})}else if(jQuery.ui.autocomplete!==undefined){a(f.fake_input).autocomplete(autocomplete_options);a(f.fake_input).bind("autocompleteselect",f,function(b,c){a(b.data.real_input).addTag(c.item.value,{focus:true,unique:e.unique});return false})}}else{a(f.fake_input).bind("blur",f,function(b){var c=a(this).attr("data-default");if(a(b.data.fake_input).val()!=""&&a(b.data.fake_input).val()!=c){if(b.data.minChars<=a(b.data.fake_input).val().length&&(!b.data.maxChars||b.data.maxChars>=a(b.data.fake_input).val().length))a(b.data.real_input).addTag(a(b.data.fake_input).val(),{focus:true,unique:e.unique})}else{a(b.data.fake_input).val(a(b.data.fake_input).attr("data-default"));a(b.data.fake_input).css("color",e.placeholderColor)}return false})}a(f.fake_input).bind("keypress",f,function(b){if(b.which==b.data.delimiter.charCodeAt(0)||b.which==13){b.preventDefault();if(b.data.minChars<=a(b.data.fake_input).val().length&&(!b.data.maxChars||b.data.maxChars>=a(b.data.fake_input).val().length))a(b.data.real_input).addTag(a(b.data.fake_input).val(),{focus:true,unique:e.unique});a(b.data.fake_input).resetAutosize(e);return false}else if(b.data.autosize){a(b.data.fake_input).doAutosize(e)}});f.removeWithBackspace&&a(f.fake_input).bind("keydown",function(b){if(b.keyCode==8&&a(this).val()==""){b.preventDefault();var c=a(this).closest(".tagsinput").find(".tag:last").text();var d=a(this).attr("id").replace(/_tag$/,"");c=c.replace(/[\s]+x$/,"");a("#"+d).removeTag(escape(c));a(this).trigger("focus")}});a(f.fake_input).blur();if(f.unique){a(f.fake_input).keydown(function(b){if(b.keyCode==8||String.fromCharCode(b.which).match(/\w+|[áéíóúÁÉÍÓÚñÑ,/]+/)){a(this).removeClass("not_valid")}})}}});return this};a.fn.tagsInput.updateTagsField=function(c,d){var e=a(c).attr("id");a(c).val(d.join(b[e]))};a.fn.tagsInput.importTags=function(d,e){a(d).val("");var f=a(d).attr("id");var g=e.split(b[f]);for(i=0;i<g.length;i++){a(d).addTag(g[i],{focus:false,callback:false})}if(c[f]&&c[f]["onChange"]){var h=c[f]["onChange"];h.call(d,d,g[i])}}})(jQuery);
  • utf8/plugins/tag/comps/contenido/tag/index.html

     
    1 <& "/contenido/components/header.msn" &>
    2 <& "/contenido/components/naviline.msn" &>
    3
    4 <p>PLugin [tag]</p>
    5
    6 </body>
    7 </html>
  • utf8/plugins/tag/config.proto

     
    1 #############################################################################
    2 #
    3 # Параметры данного шаблона необходимо ВРУЧНУЮ добавить в config.mk проекта
    4 # и привести в соответствие с требованиями проекта
    5 #
    6 #############################################################################
    7
    8 PLUGINS += tag
    9
    10 TAG_DEST = <список через пробел классов тегируемых документов>
    11 REWRITE += TAG_DEST
  • utf8/plugins/tag/lib/tag/Apache.pm

     
    1 package tag::Apache;
    2
    3 use strict;
    4 use warnings 'all';
    5
    6 use tag::State;
    7 use Contenido::Globals;
    8
    9
    10 sub child_init {
    11 # встраиваем keeper плагина в keeper проекта
    12 $keeper->{tag} = tag::Keeper->new($state->tag);
    13 }
    14
    15 sub request_init {
    16 }
    17
    18 sub child_exit {
    19 }
    20
    21 1;
  • utf8/plugins/tag/lib/tag/Cloud.pm

     
    1 package tag::Cloud;
    2
    3 use base 'Contenido::Link';
    4 use Contenido::Globals;
    5 use Data::Recursive::Encode;
    6 use Data::Dumper;
    7 use JSON::XS;
    8
    9 sub class_name
    10 {
    11 return 'Тег к объекту';
    12 }
    13
    14 sub class_description
    15 {
    16 return 'Организация облака тегов';
    17 }
    18
    19 sub extra_properties
    20 {
    21 return (
    22 );
    23 }
    24
    25 sub available_sources
    26 {
    27 return [ qw(tag::Tag) ];
    28 }
    29
    30 sub available_destinations
    31 {
    32 return $state->{tag}->tag_destinations;
    33 }
    34
    35 sub class_table
    36 {
    37 return 'tag::SQL::TagsCloudTable';
    38 }
    39
    40 sub pre_store
    41 {
    42 my $self = shift;
    43
    44 return 1;
    45 }
    46
    47 sub post_store
    48 {
    49 my $self = shift;
    50 warn "Store tag cloud element\n" if $DEBUG;
    51 my $object = $keeper->get_document_by_id($self->dest_id, class => $self->dest_class ) if $self->dest_id && $self->dest_class;
    52 my $tag = $self->keeper->get_document_by_id($self->source_id, class => $self->source_class ) if $self->source_id && $self->source_class;
    53 if ( ref $object && ref $tag ) {
    54 my ($prop) = grep { $_->{type} eq 'tagset' } $object->structure;
    55 my $class = $object->class;
    56 my $is_extra = grep { ref $_ && $_->{attr} eq $name } $class->extra_properties ? 1 : 0;
    57 if ( ref $prop && !(exists $prop->{virtual} && $prop->{virtual}) ) {
    58 my $name = $prop->{attr};
    59 my $struct;
    60 if ( ref $object->$name ) {
    61 $struct = $object->$name;
    62 } elsif ( $object->$name ) {
    63 $struct = JSON::XS->new->utf8->decode( $object->$name );
    64 }
    65 if ( ref $struct eq 'ARRAY' && @$struct && !(grep { $_->{id} == $tag->id } @$struct) ) {
    66 push @$struct, { id => $tag->id, name => Encode::decode('utf-8', $tag->name) };
    67 unless ( $is_extra ) {
    68 $struct = Encode::encode('utf-8', JSON::XS->new->encode( $struct ));
    69 }
    70 $object->$name( $struct );
    71 $object->store;
    72 }
    73 }
    74 } else {
    75 warn "Tag Cloud update error: cloud_element_id=".$self->id.", no source or destination available\n";
    76 }
    77 }
    78
    79 sub post_delete
    80 {
    81 my $self = shift;
    82 my $object = $keeper->get_document_by_id($self->dest_id, class => $self->dest_class ) if $self->dest_id && $self->dest_class;
    83 my $tag = $self->keeper->get_document_by_id($self->source_id, class => $self->source_class ) if $self->source_id && $self->source_class;
    84 if ( ref $object && ref $tag ) {
    85 my ($prop) = grep { $_->{type} eq 'tagset' } $object->structure;
    86 my $class = $object->class;
    87 my $is_extra = grep { ref $_ && $_->{attr} eq $name } $class->extra_properties ? 1 : 0;
    88 if ( ref $prop && !(exists $prop->{virtual} && $prop->{virtual}) ) {
    89 my $name = $prop->{attr};
    90 my $struct;
    91 if ( ref $object->$name ) {
    92 $struct = $object->$name;
    93 } elsif ( $object->$name ) {
    94 $struct = JSON::XS->new->utf8->decode( $object->$name );
    95 }
    96 if ( ref $struct eq 'ARRAY' && @$struct && (grep { $_->{id} == $tag->id } @$struct) ) {
    97 @$struct = grep { $_->{id} != $tag->id } @$struct;
    98 unless ( $is_extra ) {
    99 $struct = Encode::encode('utf-8', JSON::XS->new->encode( $struct ));
    100 }
    101 $object->$name( $struct );
    102 $object->store;
    103 }
    104 }
    105 } else {
    106 warn "Tag Cloud delete error: cloud_element_id=".$self->id.", no source or destination available\n";
    107 }
    108 }
    109
    110 1;
  • utf8/plugins/tag/lib/tag/Init.pm

     
    1 package tag::Init;
    2
    3 use strict;
    4 use warnings 'all';
    5
    6 use Contenido::Globals;
    7 use tag::Apache;
    8 use tag::Keeper;
    9 use tag::Section;
    10 use tag::Tag;
    11 use tag::Cloud;
    12 use tag::SQL::TagsCloudTable;
    13 use tag::SQL::TagsTable;
    14
    15 # загрузка всех необходимых плагину классов
    16 # tag::SQL::SomeTable
    17 # tag::SomeClass
    18 Contenido::Init::load_classes(qw(
    19 tag::Section
    20 tag::Tag
    21 tag::Cloud
    22 tag::SQL::TagsCloudTable
    23 tag::SQL::TagsTable
    24 ));
    25
    26 sub init {
    27 push @{ $state->{'available_documents'} },
    28 qw (
    29 tag::Tag
    30 );
    31 push @{ $state->{'available_sections'} },
    32 qw (
    33 tag::Section
    34 );
    35 push @{ $state->{'available_links'} },
    36 qw (
    37 tag::Cloud
    38 );
    39 0;
    40 }
    41
    42 1;
  • utf8/plugins/tag/lib/tag/Keeper.pm

     
    1 package tag::Keeper;
    2
    3 use strict;
    4 use warnings 'all';
    5 use base qw(Contenido::Keeper);
    6
    7
    8 use Contenido::Globals;
    9
    10
    11 1;
  • utf8/plugins/tag/lib/tag/Section.pm

     
    1 package tag::Section;
    2
    3 use base 'Contenido::Section';
    4
    5 sub extra_properties
    6 {
    7 return (
    8 { 'attr' => 'default_document_class', 'default' => 'tag::Tag' },
    9 )
    10 }
    11
    12 sub class_name
    13 {
    14 return 'Секция тегов';
    15 }
    16
    17 sub class_description
    18 {
    19 return 'Секция тегов';
    20 }
    21
    22 1;
  • utf8/plugins/tag/lib/tag/SQL/TagsCloudTable.pm

     
    1 package tag::SQL::TagsCloudTable;
    2
    3 use strict;
    4 use base 'SQL::LinkTable';
    5
    6 sub db_table
    7 {
    8 return 'tags_cloud';
    9 }
    10
    11 # ----------------------------------------------------------------------------
    12 # Свойства храним в массивах, потому что порядок важен!
    13 # Это общие свойства - одинаковые для всех документов.
    14 #
    15 # attr - обязательный параметр, название атрибута;
    16 # type - тип аттрибута, требуется для отображдения;
    17 # rusname - русское название, опять же требуется для отображения;
    18 # hidden - равен 1, когда
    19 # readonly - инициализации при записи только без изменения в дальнейшем
    20 # db_field - поле в таблице
    21 # default - значение по умолчанию (поле всегда имеет это значение)
    22 # ----------------------------------------------------------------------------
    23 sub required_properties
    24 {
    25 my $self = shift;
    26
    27 my @parent_properties = $self->SUPER::required_properties;
    28 return (
    29 @parent_properties,
    30 );
    31 }
    32
    33 ########### FILTERS DESCRIPTION ####################################################################################
    34
    35 1;
    36
  • utf8/plugins/tag/lib/tag/SQL/TagsTable.pm

     
    1 package tag::SQL::TagsTable;
    2
    3 use strict;
    4 use base 'SQL::DocumentTable';
    5
    6 sub db_table
    7 {
    8 return 'tags';
    9 }
    10
    11 sub available_filters {
    12 my @available_filters = qw(
    13 _class_filter
    14 _status_filter
    15 _in_id_filter
    16 _id_filter
    17 _name_filter
    18 _class_excludes_filter
    19 _prev_to_filter
    20 _next_to_filter
    21 _s_filter
    22
    23 _excludes_filter
    24 _link_filter
    25 _alias_filter
    26 );
    27 return \@available_filters;
    28 }
    29
    30 # ----------------------------------------------------------------------------
    31 # Свойства храним в массивах, потому что порядок важен!
    32 # Это общие свойства - одинаковые для всех документов.
    33 #
    34 # attr - обязательный параметр, название атрибута;
    35 # type - тип аттрибута, требуется для отображдения;
    36 # rusname - русское название, опять же требуется для отображения;
    37 # hidden - равен 1, когда
    38 # readonly - инициализации при записи только без изменения в дальнейшем
    39 # db_field - поле в таблице
    40 # default - значение по умолчанию (поле всегда имеет это значение)
    41 # ----------------------------------------------------------------------------
    42 sub required_properties
    43 {
    44 my $self = shift;
    45
    46 my @parent_properties = grep { $_->{attr} ne 'dtime' && $_->{attr} ne 'sections' } $self->SUPER::required_properties;
    47 return (
    48 @parent_properties,
    49 {
    50 'attr' => 'alias',
    51 'type' => 'string',
    52 'rusname' => 'Алиас',
    53 'column' => 3,
    54 'db_field' => 'alias',
    55 'db_type' => 'text',
    56 },
    57 { # Подменяем секцию
    58 'attr' => 'sections',
    59 'type' => 'parent',
    60 'rusname' => 'Родительская секция',
    61 'db_field' => 'sections',
    62 'db_type' => 'integer',
    63 },
    64 );
    65 }
    66
    67 ########### FILTERS DESCRIPTION ####################################################################################
    68 sub _s_filter {
    69 my ($self,%opts)=@_;
    70 return undef unless ( exists $opts{s} );
    71 return &SQL::Common::_generic_int_filter('d.sections', $opts{s});
    72 }
    73
    74 sub _alias_filter {
    75 my ($self,%opts)=@_;
    76 return undef unless ( exists $opts{alias} );
    77 return &SQL::Common::_generic_text_filter('d.alias', $opts{alias});
    78 }
    79
    80 sub _link_filter {
    81 my ($self,%opts)=@_;
    82
    83 my @wheres=();
    84 my @binds=();
    85
    86 # Связь определенного класса
    87 if (exists($opts{lclass})) {
    88 my ($where, $values) = SQL::Common::_generic_text_filter('l.class', $opts{lclass});
    89 push (@wheres, $where);
    90 push (@binds, ref($values) ? @$values:$values) if (defined $values);
    91 }
    92
    93 my $lclass = $opts{lclass} || 'Contenido::Link';
    94 my $link_table = $lclass->_get_table->db_table();
    95
    96 # Ограничение по статусу связи
    97 if ( exists $opts{lstatus} ) {
    98 my ($where, $values) = SQL::Common::_generic_int_filter('l.status', $opts{lstatus});
    99 push (@wheres, $where);
    100 push (@binds, ref($values) ? @$values:$values) if (defined $values);
    101 }
    102
    103 # Связь с определенным документ(ом/тами) по цели линка
    104 if ( exists $opts{ldest} ) {
    105 my ($where, $values) = SQL::Common::_generic_int_filter('l.dest_id', $opts{ldest});
    106 push (@wheres, $where);
    107 push (@binds, ref($values) ? @$values:$values) if (defined $values);
    108 if ($self->_single_class) {
    109 return (\@wheres, \@binds, " join $link_table as l on l.source_id=d.id");
    110 } elsif ( exists $opts{ldestclass} ) {
    111 my ($where, $values) = SQL::Common::_generic_text_filter('l.dest_class', $opts{ldestclass});
    112 push (@wheres, $where);
    113 push (@binds, ref($values) ? @$values:$values) if (defined $values);
    114
    115 return (\@wheres, \@binds, " join $link_table as l on l.source_id=d.id and l.source_class=d.class");
    116 } else {
    117 return (\@wheres, \@binds, " join $link_table as l on l.source_id=d.id and l.source_class=d.class");
    118 }
    119 }
    120
    121 # Связь с определенным документ(ом/тами) по источнику линка
    122 if ( exists $opts{lsource} ) {
    123 my ($where, $values) = SQL::Common::_generic_int_filter('l.source_id', $opts{lsource});
    124 push (@wheres, $where);
    125 push (@binds, ref($values) ? @$values:$values) if (defined $values);
    126 if ($self->_single_class) {
    127 return (\@wheres, \@binds, " join $link_table as l on l.dest_id=d.id");
    128 } elsif ( exists $opts{lsourceclass} ) {
    129 my ($where, $values) = SQL::Common::_generic_text_filter('l.source_class', $opts{lsourceclass});
    130 push (@wheres, $where);
    131 push (@binds, ref($values) ? @$values:$values) if (defined $values);
    132
    133 return (\@wheres, \@binds, " join $link_table as l on l.dest_id=d.id and l.dest_class=d.class");
    134 } else {
    135 return (\@wheres, \@binds, " join $link_table as l on l.dest_id=d.id and l.dest_class=d.class");
    136 }
    137 }
    138
    139 return (undef);
    140 }
    141
    142
    143 1;
  • utf8/plugins/tag/lib/tag/State.pm.proto

     
    1 package tag::State;
    2
    3 use strict;
    4 use warnings 'all';
    5 use vars qw($AUTOLOAD);
    6
    7
    8 sub new {
    9 my ($proto) = @_;
    10 my $class = ref($proto) || $proto;
    11 my $self = {};
    12 bless $self, $class;
    13
    14 # configured
    15 $self->{project} = '@PROJECT@';
    16 $self->{debug} = (lc('@DEBUG@') eq 'yes');
    17 $self->{contenido_notab} = 0;
    18 $self->{tab_name} = 'Теги';
    19 $self->{project_name} = '@PROJECT_NAME@';
    20 $self->{default_expire} = '@DEFAULT_EXPIRE@' || 300;
    21 $self->{default_object_expire} = '@DEFAULT_OBJECT_EXPIRE@' || 600;
    22
    23 # зашитая конфигурация плагина
    24 $self->{db_type} = 'none'; ### For REAL database use 'remote'
    25 $self->{db_keepalive} = 0;
    26 $self->{db_host} = '';
    27 $self->{db_name} = '';
    28 $self->{db_user} = '';
    29 $self->{db_password} = '';
    30 $self->{db_port} = '';
    31 $self->{store_method} = 'toast';
    32 $self->{cascade} = 1;
    33 $self->{db_prepare} = 0;
    34
    35 $self->{memcached_enable} = lc( '@MEMCACHED_ENABLE@' ) eq 'yes' ? 1 : 0;
    36 $self->{memcached_backend} = '@MEMCACHED_BACKEND@';
    37 $self->{memcached_select_timeout} = '@MEMCACHED_SELECT_TIMEOUT@' || 0.2;
    38 $self->{memcached_servers} = [qw(@MEMCACHED_SERVERS@)];
    39 $self->{memcached_enable_compress} = lc( '@MEMCACHED_ENABLE_COMPRESS@' ) eq 'yes' ? 1 : 0;
    40 $self->{memcached_delayed} = lc('@MEMCACHED_DELAYED@') eq 'yes' ? 1 : 0;
    41 $self->{memcached_set_mode} = lc('@MEMCACHED_SET_MODE@') eq 'add' ? 'add' : 'set';
    42 $self->{memcached_busy_lock} = 60;
    43 $self->{memcached_namespace} = lc( $self->{'project'} ).'|plugin_tag|';
    44
    45 $self->{serialize_with} = 'json'; ### or 'dumper'
    46
    47 # not implemented really (core compatibility)
    48 $self->{data_directory} = '';
    49 $self->{images_directory} = '';
    50 $self->{binary_directory} = '';
    51 $self->{preview} = '';
    52
    53 $self->{tag_destinations} = [qw(@TAG_DEST@)];
    54
    55 $self->_init_();
    56 $self;
    57 }
    58
    59 sub info {
    60 my $self = shift;
    61 return unless ref $self;
    62
    63 for (sort keys %{$self->{attributes}}) {
    64 my $la = length $_;
    65 warn "\t$_".("\t" x (2-int($la/8))).": $self->{$_}\n";
    66 }
    67 }
    68
    69 sub _init_ {
    70 my $self = shift;
    71
    72 # зашитая конфигурация плагина
    73 $self->{attributes}->{$_} = 'SCALAR' for qw(
    74 debug
    75 project
    76 tab_name
    77
    78 db_type
    79 db_keepalive
    80 db_host
    81 db_port
    82 db_name
    83 db_user
    84 db_password
    85 store_method
    86 cascade
    87 db_prepare
    88 db_client_encoding
    89
    90 memcached_enable
    91 memcached_servers
    92 memcached_select_timeout
    93 memcached_backend
    94 memcached_enable_compress
    95 memcached_set_mode
    96 memcached_object_expire
    97 memcached_busy_lock
    98 memcached_delayed
    99 memcached_namespace
    100
    101 binary_directory
    102 data_directory
    103 images_directory
    104 preview
    105
    106 tag_destinations
    107 );
    108 }
    109
    110 sub AUTOLOAD {
    111 my $self = shift;
    112 my $attribute = $AUTOLOAD;
    113
    114 $attribute =~ s/.*:://;
    115 return unless $attribute =~ /[^A-Z]/; # Отключаем методы типа DESTROY
    116
    117 if (!exists $self->{attributes}->{$attribute}) {
    118 warn "Contenido Error (tag::State): Вызов метода, для которого не существует обрабатываемого свойства: ->$attribute()\n";
    119 return;
    120 }
    121
    122 $self->{$attribute} = shift @_ if $#_>=0;
    123 $self->{$attribute};
    124 }
    125
    126 1;
  • utf8/plugins/tag/lib/tag/Tag.pm

     
    1 package tag::Tag;
    2
    3 use base 'Contenido::Document';
    4 use Contenido::Globals;
    5
    6 sub extra_properties
    7 {
    8 return (
    9 { 'attr' => 'name', 'rusname' => 'Название тега', shortname => 'Тег' },
    10 )
    11 }
    12
    13
    14 sub class_name
    15 {
    16 return 'Тег';
    17 }
    18
    19 sub class_description
    20 {
    21 return 'Тег';
    22 }
    23
    24 sub class_table
    25 {
    26 return 'tag::SQL::TagsTable';
    27 }
    28
    29 sub search_fields {
    30 return ('name');
    31 }
    32
    33 sub pre_store
    34 {
    35 my $self = shift;
    36
    37 my $default_section = $project->s_alias->{tags} if ref $project->s_alias eq 'HASH' && exists $project->s_alias->{tags};
    38 $self->sections( $default_section ) if $default_section;
    39
    40 return 1;
    41 }
    42
    43 sub post_delete
    44 {
    45 my $self = shift;
    46
    47 my $links = $self->keeper->get_links(
    48 class => 'tag::Cloud',
    49 source_id => $self->id,
    50 return_mode => 'array_ref',
    51 );
    52 if ( ref $links eq 'ARRAY' && @$links ) {
    53 foreach my $link ( @$links ) {
    54 $link->delete;
    55 }
    56 }
    57 }
    58
    59 1;
  • utf8/plugins/tag/sql/TOAST/tags.sql

     
    1 create table tags
    2 (
    3 id integer not null primary key default nextval('public.documents_id_seq'::text),
    4 ctime timestamp not null default now(),
    5 mtime timestamp not null default now(),
    6 class text not null,
    7 status smallint not null default 0,
    8 sections integer,
    9 name text,
    10 alias text,
    11 data text
    12 );
    13 create index tags_name on tags (name);
    14 create index tags_alias on tags (alias) WHERE alias IS NOT NULL AND alias != '';
  • utf8/plugins/tag/sql/TOAST/tags_cloud.sql

     
    1 create table tags_cloud
    2 (
    3 id integer not null primary key default nextval('public.documents_id_seq'::text),
    4 class text not null,
    5 ctime timestamp not null default now(),
    6 mtime timestamp not null default now(),
    7 status smallint not null default 1,
    8 source_id integer not null,
    9 source_class text not null default 'tags::Tag',
    10 dest_id integer not null,
    11 dest_class text not null,
    12 data text
    13 );
    14 create index tags_cloud_source on tags_cloud (source_id);
    15 create index tags_cloud_dest on tags_cloud (dest_id);

Небольшая справка по веткам

cnddist – контейнер, в котором хранятся все дистрибутивы всех библиотек и программных пакетов, которые использовались при построении различных версий Contenido. Если какой-то библиотеки в данном хранилище нет, инсталлятор сделает попытку "подтянуть" ее с веба (например, с CPAN). Если библиотека слишком старая, есть очень большая вероятность, что ее там уже нет. Поэтому мы храним весь хлам от всех сборок. Если какой-то дистрибутив вдруг отсутствует в cnddist - напишите нам, мы положим его туда.

koi8 – отмирающая ветка, чей код, выдача и все внутренние библиотеки заточены на кодировку KOI8-R. Вносятся только те дополнения, которые касаются внешнего вида и функционала админки, баги ядра, обязательные обновления портов и мелочи, которые легко скопипастить. В дальнейшем планируется полная остановка поддержки по данной ветке.

utf8 – актуальная ветка, заточенная под UTF-8.

Внутри каждой ветки: core – исходники ядра; install – скрипт установки инсталляции; plugins – плагины; samples – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.