diff --git a/contrib/tagger/main.php b/contrib/tagger/main.php index f5502486..70e07736 100644 --- a/contrib/tagger/main.php +++ b/contrib/tagger/main.php @@ -1,93 +1,155 @@ ) -// Do not remove this notice. +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Tagger - Advanced Tagging v2 * + * Author: Artanis (Erik Youngren ) * + * Do not remove this notice. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -class tagger extends Extension { +class Tagger extends Extension { var $theme; public function receive_event ($event) { if(is_null($this->theme)) - $this->theme = get_theme_object("tagger", "taggerTheme"); + $this->theme = get_theme_object("tagger", "taggerTheme"); - if(is_a($event,"InitExtEvent")) { - global $config; - if ($config->get_int("ext-tagger_tags-min") == -1) - $config->set_int("ext-tagger_tags-min",2); - - if ($config->get_string("ext-tagger_clear-tagme") == -1) - $config->set_bool("ext-tagger_clear-tagme",false); - - if ($config->get_string("ext-tagger_show-hidden") == -1) - $config->set_bool("ext-tagger_show-hidden",false); - } - - if(is_a($event,"DisplayingImageEvent")) { - //show tagger box - global $database; - global $page; - global $config; + if(is_a($event,'DisplayingImageEvent')) { + global $page, $config, $user; - $base_href = $config->get_string('base_href'); - $tags_min = (isset($_GET['tagger_min']) && $_GET['tagger_min']>0)?$_GET['tagger_min']:$config->get_int('ext-tagger_tags-min',2); - $hidden = $config->get_string( - 'ext-tagger_show-hidden','N')=='N' ? - " AND substring(tag,1,1) != '.' " : null; - - $tags = $database->Execute(" - SELECT tag - FROM `tags` - WHERE count>=?{$hidden} - ORDER BY tag",array($tags_min)); - - $this->theme->build($page, $tags); - global $user; - if($tags->_numOfRows > 1000 && $user->is_admin()) { - $page->add_block( new Block ( - "Warning - ext/tagger", - "

It is likely that Tagger will not function

- Currently the javascript code chokes on large numbers of - tags. The tag list currently numbers - {$tags->_numOfRows}.
- You can increase the minimum use requirement for the tag - list in the Board Config - to reduce the size of this list.
- This is a limitation of the method in which Tagger operates. - I am working on a solution, I do not know when such a - solution will be ready.", - "main",0)); - } + if($config->get_bool("tag_edit_anon") || ($user->id != $config->get_int("anon_id"))) + $this->theme->build_tagger($page,$event); } - - if(is_a($event,"PageRequestEvent")) { - if($event->page_name == "about" && $event->get_arg(0) == "tagger") { - global $page; - $this->theme->show_about($page); - } - if($event->page_name == "tagger") { - global $page; -// $this->theme->configTagger($page); - } - } - - if(is_a($event, 'UserBlockBuildingEvent')) { - if($event->user->is_admin()) { -// $event->add_link("Tagger Config", make_link("tagger")); - } - } - - if(is_a($event, 'SetupBuildingEvent')) { - $sb = new SetupBlock("Tagger - Power Tagging"); - $sb->add_bool_option("ext-tagger_show-hidden", "Show Hidden Tags"); - $sb->add_bool_option( - "ext-tagger_clear-tagme", - "
Remove 'tagme' on use"); - $sb->add_int_option( - "ext-tagger_tags-min", - "
Ignore tags used fewer than "); $sb->add_label("times."); - $event->panel->add_block($sb); - } } -} -add_event_listener( new tagger()); +} if(isset($_GET['debug'])) add_event_listener( new tagger()); + +// Tagger AJAX back-end +class TaggerXML extends Extension { + public function receive_event($event) { + if(is_a($event,'PageRequestEvent') + && $event->page_name == "tagger" + && $event->get_arg(0) == "tags") + { + global $page; + + //$match_tags = null; + //$image_tags = null; + $tags=null; + if (isset($_GET['s'])) { // tagger/tags[/...]?s=$string + // return matching tags in XML form + $tags = $this->match_tag_list($_GET['s']); + } else if($event->get_arg(1)) { // tagger/tags/$int + // return arg[1] AS image_id's tag list in XML form + $tags = $this->image_tag_list($event->get_arg(1)); + } + + $xml = "\n". + "". + $tags. + ""; + + $page->set_mode("data"); + $page->set_type("text/xml"); + $page->set_data($xml); + } + } + + private function match_tag_list ($s) { + global $database, $config, $event; + + $values = array(); + + // Match + $p = strlen($s) == 1? " ":"\_"; + $sq = "%".$p.$s."%"; + $match = "concat(?,tag) LIKE ?"; + array_push($values,$p,$sq); + // Exclude +// $exclude = $event->get_arg(1)? "AND NOT IN ".$this->image_tags($event->get_arg(1)) : null; + + // Hidden Tags + $hidden = $config->get_string('ext-tagger_show-hidden','N')=='N' ? + "AND substring(tag,1,1) != '.'" : null; + + $q_where = "WHERE {$match} {$hidden} AND count > 0"; + + // FROM based on return count + $q_from = null; + $count = $this->count($q_where,$values); + if ($count > 60) { + $q_from = "FROM (SELECT * FROM `tags` {$q_where} ". + "ORDER BY count DESC LIMIT 0,30) AS `c_tags`"; + $q_where = null; + $count = array("max"=>$count); + } else { + $q_from = "FROM `tags`"; + $count = null; + } + + $tags = $database->Execute(" + SELECT * + {$q_from} + {$q_where} + ORDER BY tag", + $values); + + return $this->list_to_xml($tags,"search",$s,$count); + } + + private function image_tag_list ($image_id) { + global $database; + $tags = $database->Execute(" + SELECT tags.* + FROM image_tags JOIN tags ON image_tags.tag_id = tags.id + WHERE image_id=? ORDER BY tag", array($image_id)); + return $this->list_to_xml($tags,"image",$image_id); + } + + private function list_to_xml ($tags,$type,$query,$misc=null) { + $r = $tags->_numOfRows; + + $s_misc = ""; + if(!is_null($misc)) + foreach($misc as $attr => $val) $s_misc .= " ".$attr."=\"".$val."\""; + + $result = ""; + foreach($tags as $tag) { + $result .= $this->tag_to_xml($tag); + } + return $result.""; + } + + private function tag_to_xml ($tag) { + return + "". + html_escape($tag['tag']). + ""; + } + + private function count($query,$values) { + global $database; + return $database->Execute( + "SELECT COUNT(*) FROM `tags` $query",$values)->fields['COUNT(*)']; + } + + private function image_tags ($image_id) { + global $database; + $list = "("; + $i_tags = $database->Execute( + "SELECT tag_id FROM `image_tags` WHERE image_id=?", + array($image_id)); + + $b = false; + foreach($i_tags as $tag) { + if($b) + $list .= ","; + $b = true; + $list .= $tag['tag_id']; + + } + $list .= ")"; + + return $list; + } +} add_event_listener( new taggerXML(),10); ?> diff --git a/contrib/tagger/script.js b/contrib/tagger/script.js index 6468b22e..430875b4 100644 --- a/contrib/tagger/script.js +++ b/contrib/tagger/script.js @@ -1,210 +1,205 @@ -// Tagger - Advanced Tagging -// Author: Artanis (Erik Youngren ) -// Do not remove this notice. -// All other code copyright by their authors, see comments for details. - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ -* Tagger Management * -\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -// Global settings and oft-used objects -var remove_tagme = null; -var tags_field = false; -var set_button = false; - -// Put everything in a class? better? - -function taggerInit() { - // get imgdata hooks - tags_field = getTagsField(); - set_button = getSetButton(); - - // Set up Tagger - // Get last position - c = getCookie('shimmie-tagger-position'); - c = c ? c.replace(/px/g,"").split(" ") : new Array(null,null); - taggerResetPos(c[0],c[1]); - - tagger_tagIndicators(); - DragHandler.attach(byId("tagger_titlebar")); - remove_tagme = byId('tagme'); - - // save position cookie on unload. - window.onunload = function(e) { - taggerSavePosition(); - }; -} - -function taggerResetPos(x,y) { - tagger = byId("tagger_window"); - - var pos = new Array(); - if(!x || !y) { - tagger.style.top=""; - tagger.style.left=""; - tagger.style.right="25px"; - tagger.style.bottom="25px"; - // get location in (left,top) terms - pos = findPos(tagger); - } else { - pos[0] = x; - pos[1] = y; - } - - tagger.style.top = pos[1]+"px"; - tagger.style.left = pos[0]+"px"; - tagger.style.right=""; - tagger.style.bottom=""; -} - -function taggerSavePosition() { - tw = byId('tagger_window'); - if (tw) { - xy = tw.style.left +" "+ tw.style.top - setCookie('shimmie-tagger-position',xy); - return true; - } - return false; -} - -function tagger_tagIndicators() { - arObjTags = getElementsByTagNames('a',byId('tagger_body')); - - for (i in arObjTags) { - objTag = arObjTags[i]; - if(tagExists(objTag)) { - objTag.style.fontWeight="bold"; - } - } -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ -* Tagging * +* Tagger - Advanced Tagging v2 * +* Author: Artanis (Erik Youngren ) * +* Do not remove this notice. * \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -function toggleTag(objTag) { - if(!tagExists(objTag)) { - addTag(objTag); - if (remove_tagme && objTag.getAttribute('tag') != 'tagme') { - remTag(remove_tagme); - } - } else { - remTag(objTag); - } - t = byId("tagger_new-tag"); - if(t.value) { t.select(); } -} - -function addTag (objTag) { - delim = tags_field.value==" "?"":" "; - - tags_field.value += delim + objTag.getAttribute('tag'); +/* Tagger Window Object + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +function Tagger() { +// components + this.t_parent = null; + this.t_title = null; + this.t_toolbar = null; + this.t_menu = null; + this.t_body = null; + this.t_tags = null; + this.t_form = null; + this.t_status = null; +// data + this.searchTags = null; + this.appliedTags = null; +// methods + this.initialize = initialize; + this.submit = submit; + this.tagSearch = tagSearch; + this.searchRequest = searchRequest; + this.searchReceive = searchReceive; + this.tagListReceive = tagListReceive; + this.tagPublish = tagPublish; + this.prepTags = prepTags; + this.createTag = createTag; + this.buildPages = buildPages; + this.tagsToString = tagsToString; + this.toggleTag = toggleTag; + this.setAlert = setAlert; - if(objTag.value != 'Add') { - objTag.style.fontWeight = "bold"; +// definitions + function initialize () { + // components + this.t_parent = document.getElementById("tagger_parent"); + this.t_title = document.getElementById("tagger_titlebar"); + this.t_toolbar = document.getElementById("tagger_toolbar"); + this.t_menu = document.getElementById("tagger_p-menu"); + this.t_body = document.getElementById("tagger_body"); + this.t_tags = document.getElementById("tagger_tags"); + this.t_form = this.t_tags.parentNode; + this.t_status = document.getElementById("tagger_statusbar"); + //pages + //this.buildPages(); + // initial data + ajaxXML(query+"/"+image_id,tagListReceive); + // reveal + this.t_parent.style.display = ""; } -} + function submit() { + this.t_tags.value = Tagger.tagsToString(Tagger.appliedTags); + } + function tagSearch(s,ms) { + clearTimeout(tagger_filter_timer); + tagger_filter_timer = setTimeout("Tagger.searchRequest('"+s+"')",ms); + } + function searchRequest(s) { + var s_query = !s? query+"?s" : query+"?s="+sqlescape(s); -function remTag (objTag) { - aTags = tags_field.value.split(" "); - - tags_field.value=""; - for(i in aTags) { - aTag = aTags[i]; - if(aTag != objTag.getAttribute('tag')) { - if(tags_field.value=="") { - tags_field.value += aTag; - } else { - tags_field.value += " "+aTag; + if(!this.searchTags) { + ajaxXML(s_query,searchReceive); + return true; + } else { + var prv_s = this.searchTags.getAttribute('query'); + + if(s==prv_s) { + return false; + }else if(!s || s.length <= 2 || s.length 2 && s.match(reescape(prv_s))) { + var len = this.searchTags.childNodes.length; + + for (var i=len-1; i>=0; i--) { + var tag = this.searchTags.childNodes[i]; + var t_name = tag.firstChild.data; + + if(!t_name.match(reescape(s))) { + this.searchTags.removeChild(tag); + // TODO: Fix so back searches are not needlessly re-queried. + //tag.setAttribute("style","display:none;"); + } else { + //tag.setAttribute("style",""); + } + } + + if (len != this.searchTags.childNodes.length) { + this.searchTags.setAttribute('query',s); + } } } + return false; } - if(objTag.value != 'Add') { - objTag.style.fontWeight = ""; - } -} - -function tagExists(objTag) { - return tags_field.value.match(reescape(objTag.getAttribute('tag'))); -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ -* Filtering * -\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -function tagger_filter() { - var filter = byId('tagger_new-tag'); - var arObjTags = getElementsByTagNames('a',byId('tagger_body')); - var prepend = filter.value.length<2? " ":"_"; - var search = prepend + reescape(filter.value); - - for(i in arObjTags) { - objTag = arObjTags[i]; - tag = prepend + objTag.getAttribute('tag'); - - if(tag.match(search) && taggerFilterMode(objTag)) { - objTag.style.display=''; + function searchReceive(xml) { + Tagger.searchTags = document.importNode(xml.getElementsByTagName("list")[0],true); + tagPublish(Tagger.searchTags,document.getElementById("tagger_p-search")); + + if(Tagger.searchTags.getAttribute("max")) { + Tagger.setAlert("maxout","Limited to "+Tagger.searchTags.getAttribute("rows")+" of "+Tagger.searchTags.getAttribute("max")+" tags"); } else { - objTag.style.display='none'; + Tagger.setAlert("maxout",false); } } -} -function taggerToggleMode() { - var obj = byId('tagger_mode'); - if(obj.getAttribute('mode')=='all') { - obj.setAttribute('mode', 'applied'); - obj.innerHTML = 'View All Tags'; - } else { - obj.setAttribute('mode','all'); - obj.innerHTML = 'View Applied Tags'; + function tagListReceive(xml) { + Tagger.appliedTags = document.importNode(xml.getElementsByTagName("list")[0],true); + tagPublish(Tagger.appliedTags,document.getElementById("tagger_p-applied")); } - tagger_filter(true); -} -function taggerFilterMode(objTag) { - var obj = byId('tagger_mode'); - if(obj.getAttribute('mode') == 'all') { - return true; - } else { - return objTag.style.fontWeight=='bold'; + function tagPublish(tag_list,page) { + page.innerHTML = ""; + Tagger.prepTags(tag_list); + page.appendChild(tag_list); } -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ -* Misc * -\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -function getTagsField() { - var nodes = getElementsByTagNames('input,textarea',byId('imgdata')); - for (i in nodes) { - node = nodes[i]; - if (node.getAttribute('name') == 'tags') - return node; - } - return false; -} - -function pushSet(form_id) { - if(set_button) { - set_button.click(); - } -} - -function getSetButton() { - var form_nodes = getElementsByTagNames('input',byId('imgdata')); - for (i in form_nodes) { - node = form_nodes[i]; - if (node.getAttribute('value')=="Set" && node.getAttribute('type')=="submit") { - return node; + function prepTags(tag_list) { + var len = tag_list.childNodes.length; + + for(var i=0; i0) { + var tag = document.createElement("tag"); + tag.setAttribute("count","0"); + tag.setAttribute("id","newTag_"+tag_name); + tag.onclick = function() { toggleTag(this); }; + tag.appendChild(document.createTextNode(tag_name)); + Tagger.appliedTags.appendChild(tag); + } + } + function buildPages () { + var pages = getElementsByTagNames("div",document.getElementById("tagger_body")); + var len = pages.length; + for(var i=0; i"+ + pages[i].getAttribute('name')+""; + } + } + function tagsToString(tags) { + var len = tags.childNodes.length; + var str = ""; + for (var i=0; i) * * Do not remove this notice. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ @@ -8,65 +8,83 @@ text-align:left; } -#tagger_window { +#tagger_parent { position:fixed; - text-align:left; - min-width:250px; + top:25px; + right:25px; + max-width:300px; + } - +#tagger_parent * { + background-color:#EEE; +} + #tagger_titlebar { - background-color:#DDDDDD; - background-image:none; + background-color:#ddd; border:2px solid; cursor:move; font-weight:bold; - /*margin-bottom:.5em;*/ - -moz-border-radius:2em 2em 0 0; + -moz-border-radius:5px 5px 0 0; padding:.25em; - position:relative; text-align:center; - top:-0em; } +#tagger_toolbar, #tagger_body { + padding:.25em; + border-style:solid; + border-width: 0px 2px 0px 2px; +} #tagger_body { - background-color:#EEEEEE; - border:2px solid; - border-top:none; - overflow:scroll; - padding:1em; - max-height:200px; + max-height:250px; + overflow:auto; + overflow-x:hidden; + overflow-y:auto; } -#tagger_filter { - background-color:#EEEEEE; + +#tagger_statusbar { + background-color:#ddd; border:2px solid; - border-bottom:none; - border-top:none; - margin-bottom:-1px; - /*-moz-border-radius:1em 1em 0 0;*/ - padding:1em; - padding-bottom:1px; - text-align:center; + font-weight: bold; + -moz-border-radius:0 0 5px 5px; + padding:.25em; +} #tagger_statusbar * { background-color:#ddd; } + +#tagger_body div { + padding-bottom:.125em; + margin-bottom:.125em; + border-top:1px solid; } -#tagger_filter input { + +/* Tagger Styling + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#Tagger form { + display:inline; +} +#Tagger input { width:auto; } +#Tagger input[type=text] { + background-color:white; +} -#tagger_body a { +/* Custom Element Base Styles + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +list { + display:inherit; +} +tag { font-size:1.25em; display:block; +} +tag:hover { cursor:pointer; + font-weight: bold; + background-color:#ddd; } +list[id=image] tag:hover { -#Use #Tagger { - -moz-column-gap:20px; - -moz-column-count:3; } +list[id=search] tag:hover { -#Use #Tagger li { - margin-bottom:1em; - max-width:30em; -} - -#Tagger .tagger_js { - cursor:pointer; } diff --git a/contrib/tagger/theme.php b/contrib/tagger/theme.php index b515abf9..0b0bbab0 100644 --- a/contrib/tagger/theme.php +++ b/contrib/tagger/theme.php @@ -1,198 +1,64 @@ ) -// Do not remove this notice. +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Tagger - Advanced Tagging v2 * + * Author: Artanis (Erik Youngren ) * + * Do not remove this notice. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ class taggerTheme extends Themelet { - - public function build($event,$tags) { - // When overriding this function, take care that all tag attributes are - // maintained UNCHANGED. There are no attributes in the HTML code below - // that go unused by the javascript. If you fail to do this, - // the extension will BREAK! - // - // Now that that's sunk in, chaniging title attributes is fine. - global $config; - global $page; - - // set up data - $base_href = $config->get_string('base_href'); - $tagme = $config->get_string( - 'ext-tagger_clear-tagme','N')=="Y"? - "": - null; - $url_about = make_link("about/tagger"); - - // build floater tags - $h_tags = ""; - foreach($tags as $tag) { - $h_tags .= $this->tag_to_html($tag); - } - - $html = " -
- Collapse this block to hide Tagger -
- About Tagger -
- ". - // Tagger Floater - "
-
Tagger
-
- - - - - - $tagme -
- View Applied Tags | - Refresh Filter -
-
$h_tags
- -
"; + public function build_tagger ($page, $event) { + // Initialization code + // TODO: AJAX test and fallback. + $page->add_block(new Block(null, + "","main",1000)); + // Tagger block $page->add_block( new Block( "Tagger", - $html, - "left")); - $page->add_header( - ""); + $this->html($event->get_image()), + "main")); } + private function html($image) { + $i_image_id = int_escape($image->id); + $h_source = html_escape($image->source); + if(isset($_GET['search'])) {$h_query = "search=".url_escape($_GET['search']);} + else {$h_query = "";} + + $url_form = make_link("tag_edit/set"); + + $html = <<< EOD + +EOD; return $html; } - - // Important for script.js, no override. - final function tag_id ($tag) { - $tag_id = ""; - $m=null; - for($i=0; $i < strlen($tag); $i++) { - $l = substr($tag,$i,1); - $m=null; - preg_match("[\pP]",$l,$m); - $tag_id .= !isset($m[0]) ? $l:"_"; - } - - return trim(str_replace("__","_",$tag_id)," _"); - } - - function trimTag($s,$len=80,$br=" ") { - if(strlen($s) > $len) { - $s = substr($s, 0,$len-1); - $s = substr($s,0, strrpos($s,$br))."..."; - } - return $s; - } } - ?>