Add unlimited sub menu support to the Menu.

Add Filter entries to the Menu.
Close #511.

You should be able to solve this.
This commit is contained in:
Nicolas Stepien 2012-07-03 17:49:34 +02:00
parent 356bad36ef
commit 76d56e3f70
2 changed files with 295 additions and 53 deletions

View File

@ -717,8 +717,12 @@
filename: function(post) {
var file, fileInfo;
fileInfo = post.fileInfo;
if (fileInfo && (file = $('.fileText > span', fileInfo))) {
return file.title;
if (fileInfo) {
if (file = $('.fileText > span', fileInfo)) {
return file.title;
} else {
return fileInfo.firstElementChild.dataset.filename;
}
}
return false;
},
@ -745,6 +749,73 @@
return img.dataset.md5;
}
return false;
},
menuInit: function() {
var div, entry, type, _i, _len, _ref;
div = $.el('div');
entry = {
el: div,
open: function() {
div.textContent = 'Filter';
return true;
},
children: []
};
_ref = [['Name', 'name'], ['Unique ID', 'uniqueid'], ['Tripcode', 'tripcode'], ['Admin/Mod', 'mod'], ['E-mail', 'email'], ['Subject', 'subject'], ['Comment', 'comment'], ['Country', 'country'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'md5']];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
type = _ref[_i];
entry.children.push(Filter.createSubEntry(type[0], type[1]));
}
return Menu.addEntry(entry);
},
createSubEntry: function(text, type) {
var el, onclick, open;
el = $.el('a', {
href: 'javascript:;',
textContent: text
});
onclick = null;
open = function(post) {
var value;
value = Filter[type](post);
if (value === false) {
return false;
}
$.off(el, 'click', onclick);
onclick = function() {
var re, save, select, ta, tl;
re = type === 'md5' ? value : value.replace(/\/|\\|\^|\$|\n|\.|\(|\)|\{|\}|\[|\]|\?|\*|\+|\|/g, function(c) {
if (c === '\n') {
return '\\n';
} else if (c === '\\') {
return '\\\\';
} else {
return "\\" + c;
}
});
re = "/^" + re + "$/";
if (/\bop\b/.test(post["class"])) {
re += ';op:yes';
}
save = (save = $.get(type, '')) ? "" + save + "\n" + re : re;
$.set(type, save);
Options.dialog();
select = $('select[name=filter]', $.id('options'));
select.value = type;
$.event(select, new Event('change'));
$.id('filter_tab').checked = true;
ta = select.nextElementSibling;
tl = ta.textLength;
ta.setSelectionRange(tl, tl);
return ta.focus();
};
$.on(el, 'click', onclick);
return true;
};
return {
el: el,
open: open
};
}
};
@ -1139,18 +1210,35 @@
return Menu.open(this, Main.preParse(post));
},
open: function(button, post) {
var bLeft, bRect, bTop, el, entry, mRect, _i, _len, _ref;
var bLeft, bRect, bTop, el, entry, funk, mRect, _i, _len, _ref;
el = Menu.el;
el.setAttribute('data-id', post.ID);
el.setAttribute('data-rootid', post.root.id);
funk = function(entry, parent) {
var child, children, open, subMenu, _i, _len;
open = entry.open, children = entry.children;
if (!open(post)) {
return;
}
$.add(parent, entry.el);
if (!children) {
return;
}
subMenu = $.el('div', {
className: 'reply dialog subMenu'
});
$.add(entry.el, subMenu);
for (_i = 0, _len = children.length; _i < _len; _i++) {
child = children[_i];
funk(child, subMenu);
}
};
_ref = Menu.entries;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
entry = _ref[_i];
if (entry.open(post)) {
$.add(el, entry.el);
}
funk(entry, el);
}
$.addClass($('.entry', Menu.el), 'focused');
Menu.focus($('.entry', Menu.el));
$.on(d, 'click', Menu.close);
$.add(d.body, el);
mRect = el.getBoundingClientRect();
@ -1162,20 +1250,23 @@
return el.focus();
},
close: function() {
var el, focused;
var el, focused, _i, _len, _ref;
el = Menu.el;
$.rm(el);
if (focused = $('.focused.entry', el)) {
_ref = $$('.focused.entry', el);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
focused = _ref[_i];
$.rmClass(focused, 'focused');
}
el.innerHTML = null;
el.removeAttribute('style');
delete Menu.lastOpener;
delete Menu.focusedEntry;
return $.off(d, 'click', Menu.close);
},
keybinds: function(e) {
var el, next;
el = $('.focused.entry', Menu.el);
var el, next, subMenu;
el = Menu.focusedEntry;
switch (Keybinds.keyCode(e) || e.keyCode) {
case 'Esc':
Menu.lastOpener.focus();
@ -1190,18 +1281,18 @@
Menu.focus(next);
}
break;
case 'Right':
if (next = el.firstElementChild) {
Menu.focus(next);
}
break;
case 'Down':
if (next = el.nextElementSibling) {
Menu.focus(next);
}
break;
case 'Right':
if ((subMenu = $('.subMenu', el)) && (next = subMenu.firstElementChild)) {
Menu.focus(next);
}
break;
case 'Left':
if ((next = el.parentNode) && next.id !== 'menu') {
if (next = $.x('parent::*[contains(@class,"subMenu")]/parent::*', el)) {
Menu.focus(next);
}
break;
@ -1212,23 +1303,35 @@
return e.stopPropagation();
},
focus: function(el) {
var focused;
if (focused = $('.focused.entry', Menu.el)) {
var focused, _i, _len, _ref;
if (focused = $.x('parent::*/child::*[contains(@class,"focused")]', el)) {
$.rmClass(focused, 'focused');
}
_ref = $$('.focused', el);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
focused = _ref[_i];
$.rmClass(focused, 'focused');
}
Menu.focusedEntry = el;
return $.addClass(el, 'focused');
},
addEntry: function(entry) {
var el, els, _i, _len;
els = $$('*', entry.el);
els.push(entry.el);
for (_i = 0, _len = els.length; _i < _len; _i++) {
el = els[_i];
var funk;
funk = function(entry) {
var child, children, el, _i, _len, _ref;
el = entry.el, children = entry.children;
$.addClass(el, 'entry');
$.on(el, 'focus mouseover', function() {
$.on(el, 'focus mouseover', function(e) {
e.stopPropagation();
return Menu.focus(this);
});
}
_ref = children || [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child = _ref[_i];
funk(child);
}
};
funk(entry);
return Menu.entries.push(entry);
}
};
@ -4730,6 +4833,9 @@
if (Conf['Delete Link']) {
DeleteLink.init();
}
if (Conf['Filter']) {
Filter.menuInit();
}
if (Conf['Download Link']) {
DownloadLink.init();
}
@ -5019,7 +5125,9 @@ a[href="javascript:;"] {\
display: block;\
outline: none;\
padding: 3px 7px;\
position: relative;\
text-decoration: none;\
white-space: nowrap;\
}\
.entry:last-child {\
border: none;\
@ -5027,6 +5135,15 @@ a[href="javascript:;"] {\
.focused.entry {\
background: rgba(255, 255, 255, .33);\
}\
.entry:not(.focused) > .subMenu {\
display: none;\
}\
.subMenu {\
position: absolute;\
left: 100%;\
top: 0;\
margin-top: -1px;\
}\
\
h1 {\
text-align: center;\

View File

@ -565,8 +565,11 @@ Filter =
false
filename: (post) ->
{fileInfo} = post
if fileInfo and file = $ '.fileText > span', fileInfo
return file.title
if fileInfo
if file = $ '.fileText > span', fileInfo
return file.title
else
return fileInfo.firstElementChild.dataset.filename
false
dimensions: (post) ->
{fileInfo} = post
@ -584,6 +587,98 @@ Filter =
return img.dataset.md5
false
menuInit: ->
div = $.el 'div'
entry =
el: div
open: ->
# Reset the container's content,
# don't keep irrelevant entries.
div.textContent = 'Filter'
true
children: []
for type in [
['Name', 'name']
['Unique ID', 'uniqueid']
['Tripcode', 'tripcode']
['Admin/Mod', 'mod']
['E-mail', 'email']
['Subject', 'subject']
['Comment', 'comment']
['Country', 'country']
['Filename', 'filename']
['Image dimensions', 'dimensions']
['Filesize', 'filesize']
['Image MD5', 'md5']
]
# Add a sub entry for each filter type.
entry.children.push Filter.createSubEntry type[0], type[1]
Menu.addEntry entry
createSubEntry: (text, type) ->
el = $.el 'a',
href: 'javascript:;'
textContent: text
# Define the onclick var outside of open's scope to $.off it properly.
onclick = null
open = (post) ->
value = Filter[type] post
return false if value is false
$.off el, 'click', onclick
onclick = ->
# Convert value -> regexp, unless type is md5
re = if type is 'md5' then value else value.replace ///
/
| \\
| \^
| \$
| \n
| \.
| \(
| \)
| \{
| \}
| \[
| \]
| \?
| \*
| \+
| \|
///g, (c) ->
if c is '\n'
'\\n'
else if c is '\\'
'\\\\'
else
"\\#{c}"
re = "/^#{re}$/"
if /\bop\b/.test post.class
re += ';op:yes'
# Add a new line before the regexp unless the text is empty.
save = if save = $.get type, '' then "#{save}\n#{re}" else re
$.set type, save
# Open the options and display & focus the relevant filter textarea.
Options.dialog()
select = $ 'select[name=filter]', $.id 'options'
select.value = type
$.event select, new Event 'change'
$.id('filter_tab').checked = true
ta = select.nextElementSibling
tl = ta.textLength
ta.setSelectionRange tl, tl
ta.focus()
$.on el, 'click', onclick
true
return el: el, open: open
StrikethroughQuotes =
init: ->
Main.callbacks.push @node
@ -877,15 +972,22 @@ Menu =
# XXX GM/Scriptish require setAttribute
el.setAttribute 'data-id', post.ID
el.setAttribute 'data-rootid', post.root.id
# for i of post
# $.add Menu.el, $.el 'code',
# className: 'entry'
# textContent: "#{i}: #{post[i]}"
for entry in Menu.entries
if entry.open post
$.add el, entry.el
$.addClass $('.entry', Menu.el), 'focused'
funk = (entry, parent) ->
{open, children} = entry
return unless open post
$.add parent, entry.el
return unless children
subMenu = $.el 'div',
className: 'reply dialog subMenu'
$.add entry.el, subMenu
for child in children
funk child, subMenu
return
for entry in Menu.entries
funk entry, el
Menu.focus $ '.entry', Menu.el
$.on d, 'click', Menu.close
$.add d.body, el
@ -909,15 +1011,16 @@ Menu =
close: ->
{el} = Menu
$.rm el
if focused = $ '.focused.entry', el
for focused in $$ '.focused.entry', el
$.rmClass focused, 'focused'
el.innerHTML = null
el.removeAttribute 'style'
delete Menu.lastOpener
delete Menu.focusedEntry
$.off d, 'click', Menu.close
keybinds: (e) ->
el = $ '.focused.entry', Menu.el
el = Menu.focusedEntry
switch Keybinds.keyCode(e) or e.keyCode
when 'Esc'
@ -928,14 +1031,14 @@ Menu =
when 'Up'
if next = el.previousElementSibling
Menu.focus next
when 'Right'
if next = el.firstElementChild
Menu.focus next
when 'Down'
if next = el.nextElementSibling
Menu.focus next
when 'Right'
if (subMenu = $ '.subMenu', el) and next = subMenu.firstElementChild
Menu.focus next
when 'Left'
if (next = el.parentNode) and next.id isnt 'menu'
if next = $.x 'parent::*[contains(@class,"subMenu")]/parent::*', el
Menu.focus next
else
return
@ -943,16 +1046,24 @@ Menu =
e.preventDefault()
e.stopPropagation()
focus: (el) ->
if focused = $ '.focused.entry', Menu.el
if focused = $.x 'parent::*/child::*[contains(@class,"focused")]', el
$.rmClass focused, 'focused'
for focused in $$ '.focused', el
$.rmClass focused, 'focused'
Menu.focusedEntry = el
$.addClass el, 'focused'
addEntry: (entry) ->
els = $$ '*', entry.el
els.push entry.el
for el in els
funk = (entry) ->
{el, children} = entry
$.addClass el, 'entry'
$.on el, 'focus mouseover', -> Menu.focus @
$.on el, 'focus mouseover', (e) ->
e.stopPropagation()
Menu.focus @
for child in children or []
funk child
return
funk entry
Menu.entries.push entry
Keybinds =
@ -1493,9 +1604,9 @@ QR =
QR.characterCount.call $ 'textarea', QR.el
$('#spoiler', QR.el).checked = @spoiler
dragStart: ->
$.addClass @, 'drag'
$.addClass @, 'drag'
dragEnter: ->
$.addClass @, 'over'
$.addClass @, 'over'
dragLeave: ->
$.rmClass @, 'over'
dragOver: (e) ->
@ -3151,10 +3262,10 @@ ReportLink =
open: (post) ->
post.isArchived is false
report: ->
a = $ '.postNum > a[title="Highlight this post"]', $.id @parentNode.dataset.rootid
url = "//sys.4chan.org/#{a.pathname.split('/')[1]}/imgboard.php?mode=report&no=#{@parentNode.dataset.id}"
id = Date.now()
set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200"
a = $ '.postNum > a[title="Highlight this post"]', $.id @parentNode.dataset.rootid
url = "//sys.4chan.org/#{a.pathname.split('/')[1]}/imgboard.php?mode=report&no=#{@parentNode.dataset.id}"
id = Date.now()
set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200"
window.open url, id, set
DownloadLink =
@ -3682,6 +3793,9 @@ Main =
if Conf['Delete Link']
DeleteLink.init()
if Conf['Filter']
Filter.menuInit()
if Conf['Download Link']
DownloadLink.init()
@ -3912,7 +4026,9 @@ a[href="javascript:;"] {
display: block;
outline: none;
padding: 3px 7px;
position: relative;
text-decoration: none;
white-space: nowrap;
}
.entry:last-child {
border: none;
@ -3920,6 +4036,15 @@ a[href="javascript:;"] {
.focused.entry {
background: rgba(255, 255, 255, .33);
}
.entry:not(.focused) > .subMenu {
display: none;
}
.subMenu {
position: absolute;
left: 100%;
top: 0;
margin-top: -1px;
}
h1 {
text-align: center;