[mod_dirlisting] js column sort for dirlist table (fixes #613, fixes #2315)

copied javascript from mod_status and from lighttpd2 mod_dirlist

modified and specialized for stable dirlist sorting by name

Partial implementation of Apache autoindex request query arguments
  (https://httpd.apache.org/docs/current/en/mod/mod_autoindex.html)
If query string is supplied, allow specifying initial column to sort
  ?C=N name (default)
  ?C=M last-modified, then by name
  ?C=S size, then by name
  ?C=T type, then by name
  ?C=D type, then by name
and O=[AD] can be added for descending or ascending order, e.g.
  ?C=N&O=D descending (default)
  ?C=N&O=A ascending

(While functional, no effort was made on js performance.
 Patches welcome)

New directive dir-listing.external-js for user to replace sorting js

Note: dir-listing.external-js or default js sorting is enabled only
if dir-listing.auto-layout = "enable" (which is the default)

x-ref:
  "client-selectable directory list sorting"
  https://redmine.lighttpd.net/issues/613
  "dir-listing.external-js"
  https://redmine.lighttpd.net/issues/2315
This commit is contained in:
Glenn Strauss 2016-08-11 00:08:43 -04:00
parent 09a663b95b
commit cfa3d27fc3
1 changed files with 202 additions and 1 deletions

View File

@ -63,6 +63,7 @@ typedef struct {
excludes_buffer *excludes;
buffer *external_css;
buffer *external_js;
buffer *encoding;
buffer *set_footer;
} plugin_config;
@ -178,6 +179,7 @@ FREE_FUNC(mod_dirlisting_free) {
excludes_buffer_free(s->excludes);
buffer_free(s->external_css);
buffer_free(s->external_js);
buffer_free(s->encoding);
buffer_free(s->set_footer);
@ -200,6 +202,7 @@ FREE_FUNC(mod_dirlisting_free) {
#define CONFIG_ACTIVATE "dir-listing.activate"
#define CONFIG_HIDE_DOTFILES "dir-listing.hide-dotfiles"
#define CONFIG_EXTERNAL_CSS "dir-listing.external-css"
#define CONFIG_EXTERNAL_JS "dir-listing.external-js"
#define CONFIG_ENCODING "dir-listing.encoding"
#define CONFIG_SHOW_README "dir-listing.show-readme"
#define CONFIG_HIDE_README_FILE "dir-listing.hide-readme-file"
@ -231,6 +234,7 @@ SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) {
{ CONFIG_ENCODE_README, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 11 */
{ CONFIG_ENCODE_HEADER, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
{ CONFIG_AUTO_LAYOUT, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 13 */
{ CONFIG_EXTERNAL_JS, NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 14 */
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};
@ -248,6 +252,7 @@ SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) {
s->excludes = excludes_buffer_init();
s->dir_listing = 0;
s->external_css = buffer_init();
s->external_js = buffer_init();
s->hide_dot_files = 1;
s->show_readme = 0;
s->hide_readme_file = 0;
@ -274,6 +279,7 @@ SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) {
cv[11].destination = &(s->encode_readme);
cv[12].destination = &(s->encode_header);
cv[13].destination = &(s->auto_layout);
cv[14].destination = s->external_js;
p->config_storage[i] = s;
@ -331,6 +337,7 @@ static int mod_dirlisting_patch_connection(server *srv, connection *con, plugin_
PATCH(dir_listing);
PATCH(external_css);
PATCH(external_js);
PATCH(hide_dot_files);
PATCH(encoding);
PATCH(show_readme);
@ -362,6 +369,8 @@ static int mod_dirlisting_patch_connection(server *srv, connection *con, plugin_
PATCH(hide_dot_files);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXTERNAL_CSS))) {
PATCH(external_css);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXTERNAL_JS))) {
PATCH(external_js);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_ENCODING))) {
PATCH(encoding);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_SHOW_README))) {
@ -509,6 +518,189 @@ static void http_list_directory_include_file(buffer *out, buffer *path, const ch
}
}
/* portions copied from mod_status
* modified and specialized for stable dirlist sorting by name */
static const char js_simple_table_resort[] = \
"var click_column;\n" \
"var name_column = 0;\n" \
"var date_column = 1;\n" \
"var size_column = 2;\n" \
"var type_column = 3;\n" \
"var prev_span = null;\n" \
"\n" \
"if (typeof(String.prototype.localeCompare) === 'undefined') {\n" \
" String.prototype.localeCompare = function(str, locale, options) {\n" \
" return ((this == str) ? 0 : ((this > str) ? 1 : -1));\n" \
" };\n" \
"}\n" \
"\n" \
"if (typeof(String.prototype.toLocaleUpperCase) === 'undefined') {\n" \
" String.prototype.toLocaleUpperCase = function() {\n" \
" return this.toUpperCase();\n" \
" };\n" \
"}\n" \
"\n" \
"function get_inner_text(el) {\n" \
" if((typeof el == 'string')||(typeof el == 'undefined'))\n" \
" return el;\n" \
" if(el.innerText)\n" \
" return el.innerText;\n" \
" else {\n" \
" var str = \"\";\n" \
" var cs = el.childNodes;\n" \
" var l = cs.length;\n" \
" for (i=0;i<l;i++) {\n" \
" if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n" \
" else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n" \
" }\n" \
" }\n" \
" return str;\n" \
"}\n" \
"\n" \
"function isdigit(c) {\n" \
" return (c >= '0' && c <= '9');\n" \
"}\n" \
"\n" \
"function unit_multiplier(unit) {\n" \
" return (unit=='K') ? 1000\n" \
" : (unit=='M') ? 1000000\n" \
" : (unit=='G') ? 1000000000\n" \
" : (unit=='T') ? 1000000000000\n" \
" : (unit=='P') ? 1000000000000000\n" \
" : (unit=='E') ? 1000000000000000000 : 1;\n" \
"}\n" \
"\n" \
"function sortfn_then_by_name(a,b,sort_column) {\n" \
" if (sort_column == name_column || sort_column == type_column) {\n" \
" var ad = (a.cells[type_column].innerHTML === 'Directory');\n" \
" var bd = (b.cells[type_column].innerHTML === 'Directory');\n" \
" if (ad != bd) return (ad ? -1 : 1);\n" \
" }\n" \
" var at = get_inner_text(a.cells[sort_column]);\n" \
" var bt = get_inner_text(b.cells[sort_column]);\n" \
" var cmp;\n" \
" if (a.cells[sort_column].className == 'int') {\n" \
" cmp = parseInt(at)-parseInt(bt);\n" \
" } else if (sort_column == date_column) {\n" \
" cmp = Date.parse(at.replace(/-/g, '/'))\n" \
" - Date.parse(bt.replace(/-/g, '/'));\n" \
" var ad = isdigit(at.substr(0,1));\n" \
" var bd = isdigit(bt.substr(0,1));\n" \
" if (ad != bd) return (!ad ? -1 : 1);\n" \
" } else if (sort_column == size_column) {\n" \
" var ai = parseInt(at, 10) * unit_multiplier(at.substr(-1,1));\n" \
" var bi = parseInt(bt, 10) * unit_multiplier(bt.substr(-1,1));\n" \
" if (at.substr(0,1) == '-') ai = -1;\n" \
" if (bt.substr(0,1) == '-') bi = -1;\n" \
" cmp = ai - bi;\n" \
" } else {\n" \
" cmp = at.toLocaleUpperCase().localeCompare(bt.toLocaleUpperCase());\n" \
" if (0 != cmp) return cmp;\n" \
" cmp = at.localeCompare(bt);\n" \
" }\n" \
" if (0 != cmp || sort_column == name_column) return cmp;\n" \
" return sortfn_then_by_name(a,b,name_column);\n" \
"}\n" \
"\n" \
"function sortfn(a,b) {\n" \
" return sortfn_then_by_name(a,b,click_column);\n" \
"}\n" \
"\n" \
"function resort(lnk) {\n" \
" var span = lnk.childNodes[1];\n" \
" var table = lnk.parentNode.parentNode.parentNode.parentNode;\n" \
" var rows = new Array();\n" \
" for (j=1;j<table.rows.length;j++)\n" \
" rows[j-1] = table.rows[j];\n" \
" click_column = lnk.parentNode.cellIndex;\n" \
" rows.sort(sortfn);\n" \
"\n" \
" if (prev_span != null) prev_span.innerHTML = '';\n" \
" if (span.getAttribute('sortdir')=='down') {\n" \
" span.innerHTML = '&uarr;';\n" \
" span.setAttribute('sortdir','up');\n" \
" rows.reverse();\n" \
" } else {\n" \
" span.innerHTML = '&darr;';\n" \
" span.setAttribute('sortdir','down');\n" \
" }\n" \
" for (i=0;i<rows.length;i++)\n" \
" table.tBodies[0].appendChild(rows[i]);\n" \
" prev_span = span;\n" \
"}\n";
/* portions copied from mod_dirlist (lighttpd2) */
static const char js_simple_table_init_sort[] = \
"\n" \
"function init_sort(init_sort_column, ascending) {\n" \
" var tables = document.getElementsByTagName(\"table\");\n" \
" for (var i = 0; i < tables.length; i++) {\n" \
" var table = tables[i];\n" \
" //var c = table.getAttribute(\"class\")\n" \
" //if (-1 != c.split(\" \").indexOf(\"sort\")) {\n" \
" var row = table.rows[0].cells;\n" \
" for (var j = 0; j < row.length; j++) {\n" \
" var n = row[j];\n" \
" if (n.childNodes.length == 1 && n.childNodes[0].nodeType == 3) {\n" \
" var link = document.createElement(\"a\");\n" \
" var title = n.childNodes[0].nodeValue.replace(/:$/, \"\");\n" \
" link.appendChild(document.createTextNode(title));\n" \
" link.setAttribute(\"href\", \"#\");\n" \
" link.setAttribute(\"class\", \"sortheader\");\n" \
" link.setAttribute(\"onclick\", \"resort(this);return false;\");\n" \
" var arrow = document.createElement(\"span\");\n" \
" arrow.setAttribute(\"class\", \"sortarrow\");\n" \
" arrow.appendChild(document.createTextNode(\":\"));\n" \
" link.appendChild(arrow)\n" \
" n.replaceChild(link, n.firstChild);\n" \
" }\n" \
" }\n" \
" var lnk = row[init_sort_column].firstChild;\n" \
" if (ascending) {\n" \
" var span = lnk.childNodes[1];\n" \
" span.setAttribute('sortdir','down');\n" \
" }\n" \
" resort(lnk);\n" \
" //}\n" \
" }\n" \
"}\n";
static void http_dirlist_append_js_table_resort (buffer *b, connection *con) {
char col = '0';
char ascending = '0';
if (!buffer_string_is_empty(con->uri.query)) {
const char *qs = con->uri.query->ptr;
do {
if (qs[0] == 'C' && qs[1] == '=') {
switch (qs[2]) {
case 'N': col = '0'; break;
case 'M': col = '1'; break;
case 'S': col = '2'; break;
case 'T':
case 'D': col = '3'; break;
default: break;
}
}
else if (qs[0] == 'O' && qs[1] == '=') {
switch (qs[2]) {
case 'A': ascending = '1'; break;
case 'D': ascending = '0'; break;
default: break;
}
}
} while ((qs = strchr(qs, '&')) && *++qs);
}
buffer_append_string_len(b, CONST_STR_LEN("\n<script type=\"text/javascript\">\n// <!--\n\n"));
buffer_append_string_len(b, js_simple_table_resort, sizeof(js_simple_table_resort)-1);
buffer_append_string_len(b, js_simple_table_init_sort, sizeof(js_simple_table_init_sort)-1);
buffer_append_string_len(b, CONST_STR_LEN("\ninit_sort("));
buffer_append_string_len(b, &col, 1);
buffer_append_string_len(b, CONST_STR_LEN(", "));
buffer_append_string_len(b, &ascending, 1);
buffer_append_string_len(b, CONST_STR_LEN(");\n\n// -->\n</script>\n\n"));
}
static void http_list_directory_header(server *srv, connection *con, plugin_data *p, buffer *out) {
UNUSED(srv);
@ -592,7 +784,7 @@ static void http_list_directory_header(server *srv, connection *con, plugin_data
"</thead>\n"
"<tbody>\n"
"<tr class=\"d\">"
"<td class=\"n\"><a href=\"../\">Parent Directory</a>/</td>"
"<td class=\"n\"><a href=\"../\">..</a>/</td>"
"<td class=\"m\">&nbsp;</td>"
"<td class=\"s\">- &nbsp;</td>"
"<td class=\"t\">Directory</td>"
@ -620,6 +812,15 @@ static void http_list_directory_footer(server *srv, connection *con, plugin_data
}
if(p->conf.auto_layout) {
if (!buffer_string_is_empty(p->conf.external_js)) {
buffer_append_string_len(out, CONST_STR_LEN("<script type=\"text/javascript\" src=\""));
buffer_append_string_buffer(out, p->conf.external_js);
buffer_append_string_len(out, CONST_STR_LEN("\" />\n"));
} else if (buffer_is_empty(p->conf.external_js)) {
http_dirlist_append_js_table_resort(out, con);
}
buffer_append_string_len(out, CONST_STR_LEN(
"<div class=\"foot\">"
));