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, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>');
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(' '),
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 – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.