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) { filename: function(post) {
var file, fileInfo; var file, fileInfo;
fileInfo = post.fileInfo; fileInfo = post.fileInfo;
if (fileInfo && (file = $('.fileText > span', fileInfo))) { if (fileInfo) {
return file.title; if (file = $('.fileText > span', fileInfo)) {
return file.title;
} else {
return fileInfo.firstElementChild.dataset.filename;
}
} }
return false; return false;
}, },
@ -745,6 +749,73 @@
return img.dataset.md5; return img.dataset.md5;
} }
return false; 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)); return Menu.open(this, Main.preParse(post));
}, },
open: function(button, 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 = Menu.el;
el.setAttribute('data-id', post.ID); el.setAttribute('data-id', post.ID);
el.setAttribute('data-rootid', post.root.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; _ref = Menu.entries;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
entry = _ref[_i]; entry = _ref[_i];
if (entry.open(post)) { funk(entry, el);
$.add(el, entry.el);
}
} }
$.addClass($('.entry', Menu.el), 'focused'); Menu.focus($('.entry', Menu.el));
$.on(d, 'click', Menu.close); $.on(d, 'click', Menu.close);
$.add(d.body, el); $.add(d.body, el);
mRect = el.getBoundingClientRect(); mRect = el.getBoundingClientRect();
@ -1162,20 +1250,23 @@
return el.focus(); return el.focus();
}, },
close: function() { close: function() {
var el, focused; var el, focused, _i, _len, _ref;
el = Menu.el; el = Menu.el;
$.rm(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'); $.rmClass(focused, 'focused');
} }
el.innerHTML = null; el.innerHTML = null;
el.removeAttribute('style'); el.removeAttribute('style');
delete Menu.lastOpener; delete Menu.lastOpener;
delete Menu.focusedEntry;
return $.off(d, 'click', Menu.close); return $.off(d, 'click', Menu.close);
}, },
keybinds: function(e) { keybinds: function(e) {
var el, next; var el, next, subMenu;
el = $('.focused.entry', Menu.el); el = Menu.focusedEntry;
switch (Keybinds.keyCode(e) || e.keyCode) { switch (Keybinds.keyCode(e) || e.keyCode) {
case 'Esc': case 'Esc':
Menu.lastOpener.focus(); Menu.lastOpener.focus();
@ -1190,18 +1281,18 @@
Menu.focus(next); Menu.focus(next);
} }
break; break;
case 'Right':
if (next = el.firstElementChild) {
Menu.focus(next);
}
break;
case 'Down': case 'Down':
if (next = el.nextElementSibling) { if (next = el.nextElementSibling) {
Menu.focus(next); Menu.focus(next);
} }
break; break;
case 'Right':
if ((subMenu = $('.subMenu', el)) && (next = subMenu.firstElementChild)) {
Menu.focus(next);
}
break;
case 'Left': case 'Left':
if ((next = el.parentNode) && next.id !== 'menu') { if (next = $.x('parent::*[contains(@class,"subMenu")]/parent::*', el)) {
Menu.focus(next); Menu.focus(next);
} }
break; break;
@ -1212,23 +1303,35 @@
return e.stopPropagation(); return e.stopPropagation();
}, },
focus: function(el) { focus: function(el) {
var focused; var focused, _i, _len, _ref;
if (focused = $('.focused.entry', Menu.el)) { if (focused = $.x('parent::*/child::*[contains(@class,"focused")]', el)) {
$.rmClass(focused, 'focused'); $.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'); return $.addClass(el, 'focused');
}, },
addEntry: function(entry) { addEntry: function(entry) {
var el, els, _i, _len; var funk;
els = $$('*', entry.el); funk = function(entry) {
els.push(entry.el); var child, children, el, _i, _len, _ref;
for (_i = 0, _len = els.length; _i < _len; _i++) { el = entry.el, children = entry.children;
el = els[_i];
$.addClass(el, 'entry'); $.addClass(el, 'entry');
$.on(el, 'focus mouseover', function() { $.on(el, 'focus mouseover', function(e) {
e.stopPropagation();
return Menu.focus(this); 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); return Menu.entries.push(entry);
} }
}; };
@ -4730,6 +4833,9 @@
if (Conf['Delete Link']) { if (Conf['Delete Link']) {
DeleteLink.init(); DeleteLink.init();
} }
if (Conf['Filter']) {
Filter.menuInit();
}
if (Conf['Download Link']) { if (Conf['Download Link']) {
DownloadLink.init(); DownloadLink.init();
} }
@ -5019,7 +5125,9 @@ a[href="javascript:;"] {\
display: block;\ display: block;\
outline: none;\ outline: none;\
padding: 3px 7px;\ padding: 3px 7px;\
position: relative;\
text-decoration: none;\ text-decoration: none;\
white-space: nowrap;\
}\ }\
.entry:last-child {\ .entry:last-child {\
border: none;\ border: none;\
@ -5027,6 +5135,15 @@ a[href="javascript:;"] {\
.focused.entry {\ .focused.entry {\
background: rgba(255, 255, 255, .33);\ background: rgba(255, 255, 255, .33);\
}\ }\
.entry:not(.focused) > .subMenu {\
display: none;\
}\
.subMenu {\
position: absolute;\
left: 100%;\
top: 0;\
margin-top: -1px;\
}\
\ \
h1 {\ h1 {\
text-align: center;\ text-align: center;\

View File

@ -565,8 +565,11 @@ Filter =
false false
filename: (post) -> filename: (post) ->
{fileInfo} = post {fileInfo} = post
if fileInfo and file = $ '.fileText > span', fileInfo if fileInfo
return file.title if file = $ '.fileText > span', fileInfo
return file.title
else
return fileInfo.firstElementChild.dataset.filename
false false
dimensions: (post) -> dimensions: (post) ->
{fileInfo} = post {fileInfo} = post
@ -584,6 +587,98 @@ Filter =
return img.dataset.md5 return img.dataset.md5
false 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 = StrikethroughQuotes =
init: -> init: ->
Main.callbacks.push @node Main.callbacks.push @node
@ -877,15 +972,22 @@ Menu =
# XXX GM/Scriptish require setAttribute # XXX GM/Scriptish require setAttribute
el.setAttribute 'data-id', post.ID el.setAttribute 'data-id', post.ID
el.setAttribute 'data-rootid', post.root.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 $.on d, 'click', Menu.close
$.add d.body, el $.add d.body, el
@ -909,15 +1011,16 @@ Menu =
close: -> close: ->
{el} = Menu {el} = Menu
$.rm el $.rm el
if focused = $ '.focused.entry', el for focused in $$ '.focused.entry', el
$.rmClass focused, 'focused' $.rmClass focused, 'focused'
el.innerHTML = null el.innerHTML = null
el.removeAttribute 'style' el.removeAttribute 'style'
delete Menu.lastOpener delete Menu.lastOpener
delete Menu.focusedEntry
$.off d, 'click', Menu.close $.off d, 'click', Menu.close
keybinds: (e) -> keybinds: (e) ->
el = $ '.focused.entry', Menu.el el = Menu.focusedEntry
switch Keybinds.keyCode(e) or e.keyCode switch Keybinds.keyCode(e) or e.keyCode
when 'Esc' when 'Esc'
@ -928,14 +1031,14 @@ Menu =
when 'Up' when 'Up'
if next = el.previousElementSibling if next = el.previousElementSibling
Menu.focus next Menu.focus next
when 'Right'
if next = el.firstElementChild
Menu.focus next
when 'Down' when 'Down'
if next = el.nextElementSibling if next = el.nextElementSibling
Menu.focus next Menu.focus next
when 'Right'
if (subMenu = $ '.subMenu', el) and next = subMenu.firstElementChild
Menu.focus next
when 'Left' when 'Left'
if (next = el.parentNode) and next.id isnt 'menu' if next = $.x 'parent::*[contains(@class,"subMenu")]/parent::*', el
Menu.focus next Menu.focus next
else else
return return
@ -943,16 +1046,24 @@ Menu =
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
focus: (el) -> focus: (el) ->
if focused = $ '.focused.entry', Menu.el if focused = $.x 'parent::*/child::*[contains(@class,"focused")]', el
$.rmClass focused, 'focused' $.rmClass focused, 'focused'
for focused in $$ '.focused', el
$.rmClass focused, 'focused'
Menu.focusedEntry = el
$.addClass el, 'focused' $.addClass el, 'focused'
addEntry: (entry) -> addEntry: (entry) ->
els = $$ '*', entry.el funk = (entry) ->
els.push entry.el {el, children} = entry
for el in els
$.addClass el, '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 Menu.entries.push entry
Keybinds = Keybinds =
@ -1493,9 +1604,9 @@ QR =
QR.characterCount.call $ 'textarea', QR.el QR.characterCount.call $ 'textarea', QR.el
$('#spoiler', QR.el).checked = @spoiler $('#spoiler', QR.el).checked = @spoiler
dragStart: -> dragStart: ->
$.addClass @, 'drag' $.addClass @, 'drag'
dragEnter: -> dragEnter: ->
$.addClass @, 'over' $.addClass @, 'over'
dragLeave: -> dragLeave: ->
$.rmClass @, 'over' $.rmClass @, 'over'
dragOver: (e) -> dragOver: (e) ->
@ -3151,10 +3262,10 @@ ReportLink =
open: (post) -> open: (post) ->
post.isArchived is false post.isArchived is false
report: -> report: ->
a = $ '.postNum > a[title="Highlight this post"]', $.id @parentNode.dataset.rootid 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}" url = "//sys.4chan.org/#{a.pathname.split('/')[1]}/imgboard.php?mode=report&no=#{@parentNode.dataset.id}"
id = Date.now() id = Date.now()
set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200" set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200"
window.open url, id, set window.open url, id, set
DownloadLink = DownloadLink =
@ -3682,6 +3793,9 @@ Main =
if Conf['Delete Link'] if Conf['Delete Link']
DeleteLink.init() DeleteLink.init()
if Conf['Filter']
Filter.menuInit()
if Conf['Download Link'] if Conf['Download Link']
DownloadLink.init() DownloadLink.init()
@ -3912,7 +4026,9 @@ a[href="javascript:;"] {
display: block; display: block;
outline: none; outline: none;
padding: 3px 7px; padding: 3px 7px;
position: relative;
text-decoration: none; text-decoration: none;
white-space: nowrap;
} }
.entry:last-child { .entry:last-child {
border: none; border: none;
@ -3920,6 +4036,15 @@ a[href="javascript:;"] {
.focused.entry { .focused.entry {
background: rgba(255, 255, 255, .33); background: rgba(255, 255, 255, .33);
} }
.entry:not(.focused) > .subMenu {
display: none;
}
.subMenu {
position: absolute;
left: 100%;
top: 0;
margin-top: -1px;
}
h1 { h1 {
text-align: center; text-align: center;