diff --git a/index.php b/index.php index cf9b139..7f8da1c 100644 --- a/index.php +++ b/index.php @@ -942,11 +942,12 @@ namespace KD2\WebDAV $alias = $root_namespaces[$ns] ?? null; $attributes = ''; - // The ownCloud Android app doesn't like formatted dates, it makes it crash. + // The ownCloud/OpenCloud Android app doesn't like formatted dates, it makes it crash. // so force it to have a timestamp + // see https://github.com/opencloud-eu/android/issues/74 if ($name == 'DAV::creationdate' && ($value instanceof \DateTimeInterface) - && false !== stripos($_SERVER['HTTP_USER_AGENT'] ?? '', 'owncloud')) { + && false !== preg_match('/owncloud|opencloud/', $_SERVER['HTTP_USER_AGENT'] ?? '')) { $value = $value->getTimestamp(); } // ownCloud app crashes if mimetype is provided for a directory @@ -2202,12 +2203,12 @@ RewriteRule ^.*$ /index.php [END] $fp = fopen(__FILE__, 'r'); if ($relative_uri == '.webdav/webdav.js') { - fseek($fp, 58874, SEEK_SET); - echo fread($fp, 31826); + fseek($fp, 58962, SEEK_SET); + echo fread($fp, 33638); } else { - fseek($fp, 58874 + 31826, SEEK_SET); - echo fread($fp, 7988); + fseek($fp, 58962 + 33638, SEEK_SET); + echo fread($fp, 16729); } fclose($fp); @@ -2298,61 +2299,69 @@ const WebDAVNavigator = (url, options) => { const microdown=function(){function l(n,e,r){return"<"+n+(r?" "+Object.keys(r).map(function(n){return r[n]?n+'="'+(a(r[n])||"")+'"':""}).join(" "):"")+">"+e+""}function c(n,e){return e=n.match(/^[+-]/m)?"ul":"ol",n?"<"+e+">"+n.replace(/(?:[+-]|\d+\.) +(.*)\n?(([ \t].*\n?)*)/g,function(n,e,r){return"
  • "+g(e+"\n"+(t=r||"").replace(new RegExp("^"+(t.match(/^\s+/)||"")[0],"gm"),"").replace(o,c))+"
  • ";var t})+"":""}function e(r,t,u,c){return function(n,e){return n=n.replace(t,u),l(r,c?c(n):n)}}function t(n,u){return f(n,[//g,"\x3c!--$1--\x3e",/^("""|```)(.*)\n((.*\n)*?)\1/gm,function(n,e,r,t){return'"""'===e?l("div",p(t,u),{class:r}):u&&u.preCode?l("pre",l("code",a(t),{class:r})):l("pre",a(t),{class:r})},/(^>.*\n?)+/gm,e("blockquote",/^> ?(.*)$/gm,"$1",r),/((^|\n)\|.+)+/g,e("table",/^.*(\n\|---.*?)?$/gm,function(n,t){return e("tr",/\|(-?)([^|]*)\1(\|$)?/gm,function(n,e,r){return l(e||t?"th":"td",g(r))})(n.slice(0,n.length-(t||"").length))}),o,c,/#\[([^\]]+?)]/g,'',/^(#+) +(.*)(?:$)/gm,function(n,e,r){return l("h"+e.length,g(r))},/^(===+|---+)(?=\s*$)/gm,"
    "],p,u)}var i=this,a=function(n){return n?n.replace(/"/g,""").replace(//g,">"):""},o=/(?:(^|\n)([+-]|\d+\.) +(.*(\n[ \t]+.*)*))+/g,g=function c(n,i){var o=[];return n=(n||"").trim().replace(/`([^`]*)`/g,function(n,e){return"\\"+o.push(l("code",a(e)))}).replace(/[!&]?\[([!&]?\[.*?\)|[^\]]*?)]\((.*?)( .*?)?\)|(\w+:\/\/[$\-.+!*'()/,\w]+)/g,function(n,e,r,t,u){return u?i?n:"\\"+o.push(l("a",u,{href:u})):"&"==n[0]?(e=e.match(/^(.+),(.+),([^ \]]+)( ?.+?)?$/),"\\"+o.push(l("iframe","",{width:e[1],height:e[2],frameborder:e[3],class:e[4],src:r,title:t}))):"\\"+o.push("!"==n[0]?l("img","",{src:r,alt:e,title:t}):l("a",c(e,1),{href:r,title:t}))}),n=function r(n){return n.replace(/\\(\d+)/g,function(n,e){return r(o[Number.parseInt(e)-1])})}(i?n:r(n))},r=function t(n){return f(n,[/([*_]{1,3})((.|\n)+?)\1/g,function(n,e,r){return e=e.length,r=t(r),1"],t)},f=function(n,e,r,t){for(var u,c=0;c typeof lang_strings != 'undefined' && key in lang_strings ? lang_strings[key] : key; - const rename_button = ``; - const delete_button = ``; + const rename_button = ``; + const delete_button = ``; - const edit_button = ``; + const edit_button = ``; const mkdir_dialog = ``; const mkfile_dialog = ``; const rename_dialog = ``; const paste_upload_dialog = `

    Upload this file?

    `; const edit_dialog = ``; - const markdown_dialog = `
    `; + const markdown_dialog = `
    `; const delete_dialog = `

    ${_('Confirm delete?')}

    `; - const wopi_dialog = ``; const dialog_tpl = `

    %s
    %b
    `; const html_tpl = ` - Files +
    `; - const body_tpl = `

    %title%

    -
    - - - + const body_tpl = ` +
    +
    + + +
    - %table%
    `; + + + + + + + + + + + %table% +
    `; - const create_buttons = ` + const create_buttons = ` - - `; + + `; - const dir_row_tpl = ` - %icon% - %name% - %modified% + const dir_row_tpl = ` + + %thumb% %name% + %modified%
    `; - const file_row_tpl = ` - %thumb% - %name% + const file_row_tpl = ` + + %thumb% %name% %size_bytes% - %modified% -
    ${_('Download')}
    + %modified% +
    ${_('Download')}
    `; const icon_tpl = `%icon%`; @@ -2646,14 +2655,18 @@ const WebDAVNavigator = (url, options) => { window.onbeforeunload = null; }; - const download_all = async () => { + const download_selected = async () => { + var items = document.querySelectorAll('tbody input[type=checkbox]:checked'); for (var i = 0; i < items.length; i++) { - var item = items[i]; - if (item.is_dir) { - continue; + var input = items[i]; + var row = input.parentNode.parentNode; + + // Skip directories + if (!row.dataset.mime) { + return; } - await download(item.name, item.size, item.uri) + await download(row.dataset.name, row.dataset.size, row.querySelector('th a').href); } }; @@ -2713,7 +2726,7 @@ const WebDAVNavigator = (url, options) => { return _('Yesterday, %s').replace(/%s/, date.toLocaleTimeString()); } - return date.toLocaleString(); + return date.toLocaleString([], {year: 'numeric', month: 'numeric', day: 'numeric'}); }; const openListing = (uri, push) => { @@ -2775,7 +2788,7 @@ const WebDAVNavigator = (url, options) => { const buildListing = (uri, xml) => { uri = normalizeURL(uri); - items = [[], []]; + items = []; var title = null; var root_permissions = null; @@ -2808,9 +2821,9 @@ const WebDAVNavigator = (url, options) => { } var is_dir = node.querySelector('resourcetype collection') ? true : false; - var index = sort_order == 'name' && is_dir ? 0 : 1; + var index = is_dir ? 0 : 1; - items[index].push({ + items.push({ 'uri': item_uri, 'path': item_uri.substring(base_url.length), 'name': name, @@ -2822,39 +2835,35 @@ const WebDAVNavigator = (url, options) => { }); }); - if (sort_order == 'name') { - items[0].sort((a, b) => a.name.localeCompare(b.name)); - } - - items[1].sort((a, b) => { - if (sort_order == 'date') { - return b.modified - a.modified; + items.sort((a, b) => { + if (sort_order === 'date') { + return a.modified - b.modified; } - else if (sort_order == 'size') { - return b.size - a.size; + else if (sort_order === 'size') { + return a.size - b.size; } else { return a.name.localeCompare(b.name); } }); - if (sort_order == 'name') { + if (sort_order !== 'date') { // Sort with directories first - items = items[0].concat(items[1]); - } - else { - items = items[1]; + items.sort((a, b) => b.is_dir - a.is_dir); } + if (sort_order_desc) { + items = items.reverse(); + } var table = ''; var parent = uri.replace(/\/+$/, '').split('/').slice(0, -1).join('/') + '/'; if (parent.length >= base_url.length) { - table += template(dir_row_tpl, {'name': _('Back'), 'uri': parent, 'icon': '↲'}); + table += template(dir_row_tpl, {'name': _('Back'), 'uri': parent, 'class': 'parent', 'thumb': template(icon_tpl, {})}); } else { - title = 'My files'; + title = _('My files'); } items.forEach(item => { @@ -2866,7 +2875,17 @@ const WebDAVNavigator = (url, options) => { var row = item.is_dir ? dir_row_tpl : file_row_tpl; item.size_bytes = item.size !== null ? formatBytes(item.size).replace(/ /g, ' ') : null; - item.icon = item.is_dir ? '📁' : (item.uri.indexOf('.') > 0 ? item.uri.replace(/^.*\.(\w+)$/, '$1').toUpperCase() : ''); + + if (!item.is_dir && (pos = item.uri.lastIndexOf('.'))) { + var ext = item.uri.substr(pos+1).toUpperCase(); + + if (ext.length > 4) { + ext = ''; + } + } + + item.icon = ext ?? ''; + item.class = item.is_dir ? 'dir' : 'file'; item.modified = item.modified !== null ? formatDate(item.modified) : null; item.name = html(item.name); @@ -2883,22 +2902,40 @@ const WebDAVNavigator = (url, options) => { document.title = title; document.querySelector('main').innerHTML = template(body_tpl, {'title': html(document.title), 'base_url': base_url, 'table': table}); - var select = $('.sortorder'); - select.value = sort_order; - select.onchange = () => { - sort_order = select.value; - window.localStorage.setItem('sort_order', sort_order); + var parent_check = document.querySelector('tbody tr.parent .check'); + + if (parent_check) { + parent_check.innerHTML = ''; + } + + var column = document.querySelector('thead td[data-sort="' + sort_order + '"]').className += ' selected ' + (sort_order_desc ? 'desc' : 'asc'); + + document.querySelectorAll('thead td[data-sort] button').forEach(elm => elm.onclick = (e) => { + var new_sort_order = e.target.parentNode.dataset.sort; + + if (sort_order == new_sort_order) { + sort_order_desc = !sort_order_desc; + } + + sort_order = new_sort_order; + + window.localStorage.setItem('sort_order', new_sort_order); + window.localStorage.setItem('sort_order_desc', sort_order_desc ? '1' : '0'); reloadListing(); + }); + + document.querySelector('thead td.check input').onchange = (e) => { + document.querySelectorAll('tbody td.check input').forEach(i => i.checked = e.target.checked); }; if (!items.length) { - $('.download_all').disabled = true; + $('div.buttons .download').disabled = true; } else { - $('.download_all').onclick = download_all; + $('div.buttons .download').onclick = download_selected; } - $('.deleted_selected').onclick = () => { + $('div.buttons .delete').onclick = () => { var l = document.querySelectorAll('input[name=delete]:checked'); if (!l.length) { @@ -2924,7 +2961,7 @@ const WebDAVNavigator = (url, options) => { }; if (!root_permissions || root_permissions.indexOf('C') != -1 || root_permissions.indexOf('K') != -1) { - $('.upload').insertAdjacentHTML('afterbegin', create_buttons); + $('.buttons').insertAdjacentHTML('beforeend', create_buttons); $('.mkdir').onclick = () => { openDialog(mkdir_dialog); @@ -2959,7 +2996,7 @@ const WebDAVNavigator = (url, options) => { var fi = $('input[type=file]'); - $('.uploadfile').onclick = () => fi.click(); + $('.upload').onclick = () => fi.click(); fi.onchange = () => { if (!fi.files.length) return; @@ -2968,10 +3005,10 @@ const WebDAVNavigator = (url, options) => { }; } - Array.from($('table').rows).forEach((tr) => { + document.querySelectorAll('table tbody tr').forEach(tr => { var $$ = (a) => tr.querySelector(a); var file_url = $$('a').href; - var file_name = $$('a').innerText; + var file_name = tr.dataset.name; var dir = $$('[colspan]'); var mime = !dir ? tr.getAttribute('data-mime') : 'dir'; var buttons = $$('td.buttons div'); @@ -3045,10 +3082,20 @@ const WebDAVNavigator = (url, options) => { } var view_url, edit_url; + var allow_preview = false; + + if (mime.match(PREVIEW_TYPES) + || file_name.match(PREVIEW_EXTENSIONS)) { + allow_preview = true; + } // Don't preview PDF in mobile - if (mime.match(PREVIEW_TYPES) - && !(mime == 'application/pdf' && window.navigator.userAgent.match(/Mobi|Tablet|Android|iPad|iPhone/))) { + if ((mime == 'application/pdf' || file_name.match(/\.pdf/i)) + && window.navigator.userAgent.match(/Mobi|Tablet|Android|iPad|iPhone/)) { + allow_preview = false; + } + + if (allow_preview) { $$('a').onclick = () => { if (file_url.match(/\.md$/)) { openDialog('
    ', false); @@ -3076,7 +3123,7 @@ const WebDAVNavigator = (url, options) => { else if (user && password && !dir) { $$('a').onclick = () => { download(file_name, size, file_url); return false; }; } - else { + else if (!dir) { $$('a').download = file_name; } @@ -3174,7 +3221,7 @@ const WebDAVNavigator = (url, options) => { // Markdown editor if (md) { - let pre = $('#md'); + let pre = $('.md_preview'); txt.oninput = () => { pre.innerHTML = microdown.parse(txt.value); @@ -3228,6 +3275,7 @@ const WebDAVNavigator = (url, options) => { var evt, paste_upload, popstate_evt, temp_object_url; var sort_order = window.localStorage.getItem('sort_order') || 'name'; + var sort_order_desc = !!parseInt(window.localStorage.getItem('sort_order_desc'), 10); var wopi_mimes = {}, wopi_extensions = {}; const wopi_discovery_url = options.wopi_discovery_url || null; @@ -3306,7 +3354,9 @@ const WebDAVNavigator = (url, options) => { document.body.classList.remove('dragging'); dragcounter = 0; - const files = [...e.dataTransfer.items].map(item => item.getAsFile()); + var files = [...e.dataTransfer.items].map(item => item.getAsFile()); + + files = files.filter(f => f !== null); if (!files.length) return; @@ -3323,24 +3373,29 @@ if (url = document.querySelector('html').getAttribute('data-webdav-url')) { :root { --bg-color: #fff; --fg-color: #000; - --g1-color: #eee; - --g2-color: #ccc; - --g3-color: #999; - --link-color: blue; - --visited-color: purple; - --active-color: darkred; + --g1-color: #f5f7f7; + --g2-color: #ccd8d6; + --g3-color: #7c9696; + --link-color: #2e3c3a; + --visited-color: #2e3c3a; + --active-color: #c47508; +} + +* { + margin: 0; + padding: 0; } body { text-align: center; - font-size: 1.1em; - font-family: Arial, Helvetica, sans-serif; + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; background: var(--bg-color); color: var(--fg-color); } a:link { color: var(--link-color); + text-decoration: none; } a:visited { @@ -3349,70 +3404,126 @@ a:visited { a:hover { color: var(--active-color); + text-decoration: underline; } table { - margin: 2em auto; + margin: 0 auto; border-collapse: collapse; - width: 90%; + width: 100%; } -th, td { - padding: .5em; +thead td { + height: 2em; + vertical-align: bottom; +} + +thead button { + width: 100%; + border: none; + text-align: inherit; + font: inherit; + background: none; + cursor: pointer; + font-size: .9em; + color: #999; + padding: .4em; +} + +thead .selected button::after { + content: "🠇"; + margin-left: .5em; +} + +thead .selected.desc button::after { + content: "🠅"; +} + +td.name, tbody th, td.date { text-align: left; - border: 2px solid var(--g2-color); } -th { +tbody tr:has(.check input:checked) { + background: #fdefdc; +} + +tbody th, tbody td { + padding: 0 .5em; + text-align: left; + border-top: 1px solid var(--g2-color); + border-bottom: 1px solid var(--g2-color); + vertical-align: middle; +} + +tbody th { word-break: break-all; + font-weight: normal; } -td.thumb { - width: 3.6em; - padding: 0; - text-align: center; +tbody th a { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 1em; +} + +tbody th img { + width: 2em; + height: 2em; + border-radius: .2em; +} + +tbody th .icon { + width: 2em; + height: 2em; + border-radius: .2em; + overflow: hidden; + background: no-repeat center center; + background-size: 100%; position: relative; } -td.thumb img { - width: 3.6em; - height: 3.6em; - display: block; -} - -td.thumb .icon { - margin: .5em; -} - -td.thumb input, td.thumb label span::before { +th .icon b { + font-size: 7pt; + font-weight: normal; + text-decoration: none; + display: inline-block; + background: #eee; + border-radius: .3em; + padding: 0 .2em; + color: #2e3c3a; position: absolute; - top: 0; - left: 0; - right: 0; bottom: 0; - margin: 0; - padding: 0; - opacity: 0; - display: block; + right: 0; + border: 1px solid #2e3c3a; +} + +td.check { + width: 1.5em; + text-align: center; +} + +td.check input { cursor: pointer; - user-select: none; + width: 1.5em; + height: 1.5em; + border: 2px solid #999; + border-radius: .2em; + background: none; + appearance: none; + outline: 0; } -td.thumb input:checked + span::before { - content: "✔️"; - font-size: 3em; - opacity: 1; - color: #fff; - text-shadow: 0px 0px 5px #000, 0px 0px 10px #000; - display: flex; - align-items: center; - justify-content: center; +td.check input:checked { + box-shadow: 0px 0px 5px darkorange; + background: no-repeat center center; + background-image: url('data:image/svg+xml;utf8,'); + background-size: contain; } - td.buttons { text-align: right; - width: 20em; + width: 5em; } td.buttons div { @@ -3420,60 +3531,65 @@ td.buttons div { flex-direction: row-reverse; } -table tr:nth-child(even) { +tbody tr:nth-child(even) { background: var(--g1-color); } -.icon { - width: 2.6em; - height: 2.6em; - display: inline-block; - border-radius: .2em; - background:var(--g3-color); - overflow: hidden; - color: var(--bg-color); - text-align: center; +tbody th .icon { + background-image: url('data:image/svg+xml;utf8,'); } -.icon b { - font-weight: normal; - display: inline-block; - transform: rotate(-30deg); - line-height: 2.6rem; +th .icon.JPEG, th .icon.PNG, th .icon.JPG, th .icon.GIF, th .icon.SVG, th .icon.WEBP { + background-image: url('data:image/svg+xml;utf8,'); } -.icon.JPEG, .icon.PNG, .icon.JPG, .icon.GIF, .icon.SVG, .icon.WEBP { - background: #966; +th .icon.TXT, th .icon.MD { + background-image: url('data:image/svg+xml;utf8,'); } -.icon.TXT, .icon.MD { - background: var(--fg-color); +th .icon.MP4, th .icon.MKV, th .icon.MP3, th .icon.M4A, th .icon.WAV, th .icon.FLAC, th .icon.OGG, th .icon.OGV, th .icon.AAC, th .icon.WEBM { + background-image: url('data:image/svg+xml;utf8,'); } -.icon.MP4, .icon.MKV, .icon.MP3, .icon.M4A, .icon.WAV, .icon.FLAC, .icon.OGG, .icon.OGV, .icon.AAC, .icon.WEBM { - background: #669; +th .icon.document { + background-image: url('data:image/svg+xml;utf8,'); } -.icon.document { - background: #696; +th .icon.document b { + position: relative; + background: none; + border: none; + color: #fff; + width: 100%; + height: 100%; + padding: 0; + display: inline-flex; + justify-content: center; + align-items: center; } -.icon.PDF { - background: #969; +.dir th .icon { + background-image: url('data:image/svg+xml;utf8,'); } -.icon.dir { - background: var(--g2-color); - color: var(--fg-color); +.parent th .icon { + background-image: url('data:image/svg+xml;utf8,'); } -.icon.dir b { - font-size: 2em; - transform: none; +.dir th .icon b, .parent th .icon b { + display: none; } -.size { +td.size { text-align: right; + font-size: .9em; + color: #666; +} + +td.date { + font-size: .9em; + color: #666; + min-width: 9em; } input[type=button], input[type=submit], .btn, a.btn { @@ -3511,7 +3627,7 @@ input:focus, textarea:focus { input[type=button]:hover, input[type=submit]:hover, .btn:hover { color: var(--active-color); text-decoration: underline; - background: var(--bg-color); + background-color: var(--bg-color); box-shadow: 0px 0px 5px var(--fg-color); } @@ -3571,8 +3687,31 @@ dialog form div { text-align: center; } -.upload { - margin: 1em 0; +div.buttons { + display: flex; + justify-content: flex-end; + margin: .5em; + margin-bottom: 0; +} + +div.buttons .selected { + margin-right: auto; + display: none; +} + +body:has(tbody .check input:checked) div.buttons .selected { + display: block; +} + +div.buttons input { + overflow: visible; + text-indent: 0; + width: auto; + height: auto; + background-position: .2em center; + padding: .3em .5em; + padding-left: 2em; + background-size: 1.3em; } #mdp div, #mdp textarea { @@ -3584,7 +3723,7 @@ dialog form div { margin: 0; } -#md { +#mdp .md_preview { overflow: hidden; overflow-x: auto; } @@ -3594,10 +3733,10 @@ dialog form div { grid-template-columns: 1fr 1fr; grid-gap: .2em; background: var(--g1-color); - height: 82vh; + height: 90vh; } -#md img, #md video, #md iframe, #md embed, #md object { +.md_preview img, .md_preview video, .md_preview iframe, .md_preview embed, .md_preview object { max-width: 100%; } @@ -3614,7 +3753,7 @@ dialog.preview { overflow: hidden; } -iframe, .md_preview { +iframe, dialog.preview .md_preview { overflow: auto; position: absolute; top: 0; @@ -3655,11 +3794,21 @@ iframe, iframe body, .md_preview { max-height: 100%; } -.md_preview { +dialog.preview .md_preview { width: calc(100vw - 2em); - height: calc(100vh - 2em); + height: calc(100vh - 4em); padding: 1em; text-align: left; + padding-bottom: 2em; +} + +.md_preview ul, .md_preview ol, .md_preview p, .md_preview blockquote, .md_preview pre, +.md_preview h1, .md_preview h2, .md_preview h3, .md_preview h4, .md_preview h5, .md_preview h6 { + margin: 1rem 0; +} + +.md_preview ul, .md_preview ol, .md_preview blockquote { + margin-left: 2em; } .preview .close { @@ -3748,6 +3897,19 @@ input[name=rename], input[name=paste_name] { dialog { transition: all .3s; + margin: auto; + padding: 1em; +} + +dialog:has(#mdp) { + margin: 0; + padding: .5em; + border-radius: none; + top: 0; + left: 0; + right: 0; + bottom: 0; + height: 100vh; } progress { @@ -3755,6 +3917,45 @@ progress { width: 90%; } +input.icon, .buttons a[download] { + background: #e0e6e6 no-repeat center center; + background-size: contain; + width: 1.8em; + height: 1.8em; + padding: 0; + overflow: hidden; + text-indent: 100em; + display: inline-block; +} + +input.delete { + background-image: url('data:image/svg+xml;utf8,'); +} + +input.rename { + background-image: url('data:image/svg+xml;utf8,'); +} + +.buttons a[download], input.download { + background-image: url('data:image/svg+xml;utf8,'); +} + +input.edit { + background-image: url('data:image/svg+xml;utf8,'); +} + +input.mkdir { + background-image: url('data:image/svg+xml;utf8,'); +} + +input.mkfile { + background-image: url('data:image/svg+xml;utf8,'); +} + +input.upload { + background-image: url('data:image/svg+xml;utf8,'); +} + @keyframes spin { to { transform: rotate(360deg); } } @media screen and (max-width: 800px) { @@ -3767,74 +3968,99 @@ progress { body { margin: 0; - font-size: 1em; + } + + div.buttons { + flex-wrap: wrap; + justify-content: center; + flex-direction: row; + font-size: .9em; + } + + div.buttons .selected { + margin: 0; + position: fixed; + top: 0; + left: 0; + right: 0; + padding: 1em; + background: var(--bg-color); + box-shadow: 0px 0px 2px 5px var(--g3-color); } table { - margin: 2em 0; width: 100%; display: flex; flex-direction: column; } - table tr { - display: block; - border-top: 5px solid var(--bg-color); + table tbody tr { + display: grid; + grid-template-columns: 3rem 1fr .3fr; + grid-template-areas: + "name name check" + "size date buttons"; padding: 0; - padding-left: 2em; - position: relative; - text-align: left; - min-height: 2.5em; } - table td, table th { + table tbody td, table tbody th { border: none; - display: inline-block; + display: block; padding: .2em .5em; + width: unset; } - table td.buttons { - display: block; - width: auto; - text-align: left; + table tbody th { + grid-area: name; } - td.buttons div { - display: inline-block; - } - - table td.thumb { - padding: 0; - width: 2em; - position: absolute; - left: 0; - top: 0; - bottom: 0; + table tbody td.check { + grid-area: check; + text-align: right; + width: unset; } - table th { - display: block; + table tbody td.buttons { + grid-area: buttons; + width: unset; } - .icon { - font-size: 12px; - height: 100%; - border-radius: 0; + table tbody td.date { + grid-area: date; + display: inline-block; + min-width: unset; } - .icon:not(.dir) b { - line-height: 3em; - display: block; - transform: translateX(-50%) translateY(-50%) rotate(-90deg); - font-size: 2em; - height: 3em; - position: absolute; - top: 50%; - left: 50%; + table tbody td.size { + grid-area: size; + font-size: .8em; + width: 3rem; + overflow: hidden; + text-align: center; } table th a { font-size: 1.2em; + gap: .5em; + } + + table thead tr { + display: block; + text-align: left; + } + + table thead tr td { + display: inline-block; + vertical-align: top; + } + + table thead td.check { + float: right; + margin-right: .4em; + } + + table thead button { + font-size: 1rem; } input[name=rename], input[name=paste_name] {