diff options
author | petko <petko@524c5546-5005-0410-9a3e-e25e191bd360> | 2024-07-26 16:04:48 +0000 |
---|---|---|
committer | petko <petko@524c5546-5005-0410-9a3e-e25e191bd360> | 2024-07-26 16:04:48 +0000 |
commit | 58a411afb156c962d2b62d0a01e23e769a3aacf3 (patch) | |
tree | 853e7d94aec68e31ad7ed19959725b714dfb8d4a | |
parent | 5ae73e69e396caa528808354e9f88ee5a93b5676 (diff) | |
download | pmwiki.svn-58a411afb156c962d2b62d0a01e23e769a3aacf3.tar.bz2 |
Add $EnableUploadDrop based on Cookbook:DragDropMultiUpload.
git-svn-id: svn://pmwiki.org/pmwiki/trunk@4739 524c5546-5005-0410-9a3e-e25e191bd360
-rw-r--r-- | pmwiki.php | 48 | ||||
-rw-r--r-- | pub/guiedit/pmwiki.syntax.css | 59 | ||||
-rw-r--r-- | pub/pmwiki-utils.js | 174 | ||||
-rw-r--r-- | scripts/upload.php | 35 | ||||
-rw-r--r-- | scripts/utils.php | 77 |
5 files changed, 331 insertions, 62 deletions
@@ -846,6 +846,47 @@ function PRCB($pat, $repl, $subj, $vars=null, $limit=-1, &$count=null, $flags=0) return preg_replace_callback($pat, $repl, $subj, $limit, $count, $flags); return preg_replace_callback($pat, $repl, $subj, $limit, $count); } + +## This is a replacement for json_encode+PHSC, but only for arrays that +## are used by the PmWiki core. It may or may not work in other cases. +## This may fail with international characters if UTF-8 is not enabled. +function pm_json_encode($x, $encodespecial=false) { + if (!isset($x) || is_null($x)) return 'null'; + if (is_bool($x)) return $x? "true" : "false"; + if (is_int($x) || is_float($x)) return strval($x); + + if (function_exists('json_encode')) + $out = json_encode($x); + + elseif (is_string($x)) ## escape controls and specials per RFC:8259 + $out = '"'.preg_replace_callback("/[\x00-\x1f\\/\\\\\"]/",'cb_rfc8259',$x).'"'; + + elseif (is_array($x)) { + $a = array(); + if (array_values($x) === $x) { # numeric sequential array + foreach($x as $v) + $a[] = pm_json_encode($v); + + $out = "[".implode(',', $a)."]"; + } + else { # associative array -> json object + foreach($x as $k=>$v) { + $jk = is_int($k)? "\"$k\"" : pm_json_encode($k); + $jv = pm_json_encode($v); + $a[] = "$jk:$jv"; + } + $out = "{".implode(',', $a)."}"; + } + } + else return 'null'; # other types not yet supported + + return $encodespecial? PHSC($out, ENT_QUOTES) : $out; +} +function cb_rfc8259($m) { + return sprintf('\\u00%02x', ord($m[0])); +} + + ## callback functions class PPRC { # PmWiki preg replace callbacks + pass local vars var $vars; @@ -1813,6 +1854,13 @@ function Redirect($pagename, $urlfmt='$PageUrl', $redirecturl=null) { exit; } +function PrintJSON($pagename, $out) { + $json = pm_json_encode($out); + header('Content-Type: application/json'); + print $json; + exit; +} + function PrintFmt($pagename,$fmt) { global $EnablePrePrintFmt; if (IsEnabled($EnablePrePrintFmt, 1)) PrePrintFmt($pagename,$fmt); diff --git a/pub/guiedit/pmwiki.syntax.css b/pub/guiedit/pmwiki.syntax.css index ce1346ab..188cca26 100644 --- a/pub/guiedit/pmwiki.syntax.css +++ b/pub/guiedit/pmwiki.syntax.css @@ -289,3 +289,62 @@ code.pmhlt, .pmhlt code, #htext.pmhlt { color: var(--pmsyntax-color); } +/* For $EnableUploadDrop, we reuse colors that are + * likely to be appropriate regardless of the skin. */ +.pmdropzone { + border-style: dashed; + border-width: 2px; + margin-bottom: .25rem; +} +.pmdropzone.over { + background-color: var(--pmsyntax-bulletbg); +} +.pmdropzone a { + margin-right: .5em; + display: inline-block; + text-decoration: none; +} +.pmdropzone a::before { + margin-right: .25em; +} +.pmdropzone a.queued { + color: var(--pmsyntax-keyword); + cursor: wait; +} +.pmdropzone a.uploading { + color: var(--pmsyntax-attr); + cursor: wait; +} +.pmdropzone a.error { + color: var(--pmsyntax-string); +} +.pmdropzone a.deleting { + opacity: 0.1; + transition: opacity 0.7s linear; +} +.pmdropzone a.error:hover { + text-decoration: line-through; +} +.pmdropzone a.queued::before { + content: "\23F8"; +} +.pmdropzone a.uploading::before { + content: "\25B2"; + animation: blnk .6s infinite; +} +@keyframes blnk { 50% { opacity: 0; } } +.pmdropzone a.success::before { + content: "\25A0"; + margin-right: 5px; +} +.pmdropzone a.error::before { + content: "\2326"; +} + +@media print { + .pmdropzone { + display: none; + } +} + + diff --git a/pub/pmwiki-utils.js b/pub/pmwiki-utils.js index 5b68101b..676e5049 100644 --- a/pub/pmwiki-utils.js +++ b/pub/pmwiki-utils.js @@ -10,7 +10,7 @@ -(function(__script__){ +(async function(__script__){ try { var Config = JSON.parse(__script__.dataset.config); Config.fullname = __script__.dataset.fullname; @@ -19,6 +19,13 @@ var Config = {}; } + if(Config.rediquiet) { + var url = location.href.replace(/\?from=[^?&#]*[&]quiet=1/, ''); + if(url != location.href) + history.replaceState(null, null, url); + } + + function aE(el, ev, fn) { if(typeof el == 'string') el = dqsa(el); for(var i=0; i<el.length; i++) el[i].addEventListener(ev, fn); @@ -59,7 +66,7 @@ function PHSC(x) { return x.replace(/[&]/g, '&').replace(/[<]/g, '<').replace(/[>]/g, '>'); } var wikitext; - var log = console.log; + var echo = console.log; function PmXMail() { var els = document.querySelectorAll('span._pmXmail'); @@ -566,7 +573,7 @@ } if(out) adjae(plus, out); }) - .catch(log); + .catch(echo); }); if(dqs('form[name="authform"]') || location.href.match(/action=/)) return; seenstamp[pagename] = Math.floor(Now.getTime()/1000); @@ -590,16 +597,165 @@ } }); } - - if(Config.rediquiet) { - var url = location.href.replace(/\?from=[^?&#]*[&]quiet=1/, ''); - if(url != location.href) - history.replaceState(null, null, url); + + var Dropzone = false; + var UploadQueue = []; + function init_dropzone(){ + if(!Config.updrop) return; + var form = dqs('form[action$="action=postupload"], form[action$="action=edit"]'); + if(!form) return; + Dropzone = dce('div'); + Dropzone.className = "frame pmdropzone"; + var label = dce('span'); + label.textContent = Config.updrop.label; + Dropzone.appendChild(label); + if(Config.updrop.areq) { + var areq = form.querySelector('input[name="author"]') + Dropzone.pmareq = areq; + } + + form.insertAdjacentElement('afterbegin', Dropzone); + aE([Dropzone], 'dragenter', dragenter); + aE([Dropzone], 'dragover', dragenter); + aE([Dropzone], 'dragleave', dragleave); + aE([Dropzone], 'drop', dragdrop); + tap([Dropzone], clickzone); + } + + async function postUpload(file) { + var url = Config.updrop.action; + var f = new FormData; + f.append('n', Config.fullname); + f.append('action', 'postupload'); + f.append('uploadfile', file); + f.append('pmdrop', 1); + f.append(Config.updrop.token[0], Config.updrop.token[1]); + if(Dropzone.pmareq) + f.append('author', Dropzone.pmareq.value); + try { + var response = await fetch(url, { method: 'POST', body: f }); + if (!response.ok) { + var msg = `HTTP error! Status: ${response.status}`; + throw new Error(msg); + return { error: 1, msg: msg}; + } + return await response.json(); + } + catch(err) { + console.error('Error posting form data:', err); + return {error: 1, msg: 'Error posting form data, see console'}; + } + } + + async function processUploads() { + while(true) { + var a = dqs('.pmdropzone a.queued'); + if(!a) return; + a.className = 'uploading'; + var result = await postUpload(a.pmfile); + if(result.error) { + a.className = 'error'; + a.title = result.msg; + } + else { + a.className = 'success'; + a.href = result.href; + a.textContent = result.uprname; + a.title = result.msg; + delete a.pmfile; + } + } + } + + function clickzone(e){ + var a = e.target.closest('a'); + if(!a) return; + e.preventDefault(); + e.stopPropagation(); + if(a.className.match(/uploading|queued|deleting/)) return; + else if(a.className == 'success' && typeof insMarkup == 'function') { + var pn = e.ctrlKey? Config.fullname + '/': ''; + var text = "Attach:"+pn+a.textContent; + if(text.match(/\s/)) text = '[['+text+']]'; + insMarkup(function(x){ + if(x.length) return text; + return { + mtext: text, + unselect: true + }; + }); + return; + } + else if(a.className == 'error' || typeof insMarkup == 'undefined') { + a.classList.add('deleting'); + setTimeout(function(){a.remove();}, 700); + return; + } + // this shouldn't happen + } + + function dragenter(e) { + e.preventDefault(); + Dropzone.classList.add('over'); + } + + function dragleave(e) { + e.preventDefault(); + Dropzone.classList.remove('over'); } + async function dragdrop(e) { + e.preventDefault(); + Dropzone.classList.remove('over'); + if(Dropzone.pmareq) { + var v = Dropzone.pmareq.value; + if(!v.length) { + appendUpload({size:-500,name:Config.updrop.areq}); + Dropzone.pmareq.focus(); + return; + } + } + var files = event.dataTransfer.files; + var pending = dqs('.pmdropzone a.uploading'); + for (var i = 0; i < files.length; i++) appendUpload(files[i]); + if(!pending) await processUploads(); + } + + function appendUpload(file) { + if(file.size==0) { return; } + var sizes = Config.updrop.sizes; + var ext = ''; + var m = file.name.match(/\.([^\.]+)$/); + if(m) ext = m[1].toLowerCase(); + + var a = dce('a'); + a.href = '#'; + a.textContent = file.name; + + if(file.size==-500) { + a.className = 'error'; + } + else if(typeof sizes[ext] == 'undefined') { + a.className = 'error'; + a.title = Config.updrop.badtype.replace(/\#upext/, ext); + } + else if(file.size > sizes[ext]) { + a.className = 'error'; + a.title = Config.updrop.toobig + .replace(/\#upmax/, sizes[ext]).replace(/\#upext/, ext); + } + else { + a.className = 'queued'; + a.pmfile = file; + } + Dropzone.appendChild(a); + } + + function ready(){ wikitext = document.getElementById('wikitext'); - var fn = [autotoc, inittoggle, PmXMail, localTimes, highlight_pre, copy_pre, confirmForms]; + var fn = [autotoc, inittoggle, PmXMail, localTimes, + highlight_pre, copy_pre, confirmForms, init_dropzone]; fn.forEach(function(a){a();}); makesortable(); } diff --git a/scripts/upload.php b/scripts/upload.php index e88472ac..910078d3 100644 --- a/scripts/upload.php +++ b/scripts/upload.php @@ -105,6 +105,7 @@ SDV($PageUploadFmt,array(" XLSDV('en',array( 'ULby' => 'uploaded by', 'ULsuccess' => 'successfully uploaded', + 'ULdroplabel' => 'Drop files to upload:', 'ULinvalidtoken' => 'Token invalid or missing.', 'ULmimemismatch' => 'extension \'$upext\' doesn\'t match file type \'$upmime\'', 'ULfileinfo' => 'EnableUploadMimeMatch requires PHP Fileinfo functions to be enabled, see https://php.net/fileinfo.installation', @@ -200,8 +201,14 @@ function UploadAuth($pagename, $auth, $cache=0){ SDV($GroupAttributesFmt,'$Group/GroupAttributes'); $pn_upload = FmtPageName($GroupAttributesFmt, $pagename); } else $pn_upload = $pagename; - $page = RetrieveAuthPage($pn_upload, $auth, true, READPAGE_CURRENT); - if (!$page) Abort("?No '$auth' permissions for $pagename"); + $authprompt = @$_POST['pmdrop']? false: true; + $page = RetrieveAuthPage($pn_upload, $auth, $authprompt, READPAGE_CURRENT); + if (!$page) { + $msg = "?No '$auth' permissions for $pagename"; + if (@$_POST['pmdrop']) + return PrintJSON($pagename, array('error'=>1,'msg'=>$msg)); + Abort($msg); + } if ($cache) PCache($pn_upload,$page); return true; } @@ -354,6 +361,17 @@ function HandlePostUpload($pagename, $auth = 'upload') { } } $FmtV['$upresult'] = $result; + if (@$_POST['pmdrop']) { + $out = array('uprname'=>$upname); + preg_match('/^upresult=([a-zA-Z]+)(.*)$/', $result, $m); + $out['msg'] = "$upname: ".FmtPageName(XL("UL{$m[1]}"), $pagename); + + if ($m[1] != 'success') $out['error'] = 1; + else $out['href'] = $FmtV['$upurl']; + + PrintJSON($pagename, $out); + exit; + } SDV($UploadRedirectFunction, 'Redirect'); $UploadRedirectFunction($pagename,"{\$PageUrl}?action=upload&uprname=$upname&$result"); } @@ -362,7 +380,7 @@ function UploadVerifyBasic($pagename,$uploadfile,&$filepath,&$upname=null) { global $EnableUploadOverwrite, $UploadExtSize, $UploadPrefixQuota, $EnableUploadVersions, $UploadDirQuota, $UploadDir, $UploadBlacklist, $Author, $EnablePostAuthorRequired, $EnableUploadAuthorRequired, - $UploadExts, $EnableUploadMimeMatch, $Now; + $UploadExts, $EnableUploadMimeMatch, $Now, $FmtV; if (! pmtoken(1)) { return 'upresult=invalidtoken'; @@ -382,7 +400,7 @@ function UploadVerifyBasic($pagename,$uploadfile,&$filepath,&$upname=null) { } } if (IsEnabled($EnableUploadVersions, 0)==2 && file_exists($filepath)) { - if(preg_match('!^(.*/([^/]+))(\\.[a-z0-9]+)$!i', $filepath, $m)) { + if (preg_match('!^(.*/([^/]+))(\\.[a-z0-9]+)$!i', $filepath, $m)) { $stamp36 = base_convert($Now, 10, 36); $filepath = "{$m[1]}-$stamp36{$m[3]}"; $upname = "{$m[2]}-$stamp36{$m[3]}"; @@ -391,9 +409,12 @@ function UploadVerifyBasic($pagename,$uploadfile,&$filepath,&$upname=null) { if (!$EnableUploadOverwrite && file_exists($filepath)) return 'upresult=exists'; preg_match('/\\.([^.\\/]+)$/',$filepath,$match); $ext=@$match[1]; - if(!isset($UploadExtSize[$ext])) + + $FmtV['$upext'] = $ext; + if (!isset($UploadExtSize[$ext])) return "upresult=badtype&upext=$ext"; $maxsize = $UploadExtSize[$ext]; + $FmtV['$upmax'] = $maxsize; if ($maxsize<=0) return "upresult=badtype&upext=$ext"; if (intval(@$uploadfile['size'])>$maxsize) return "upresult=toobigext&upext=$ext&upmax=$maxsize"; @@ -409,12 +430,14 @@ function UploadVerifyBasic($pagename,$uploadfile,&$filepath,&$upname=null) { return "upresult=fileinfo"; $mime = mime_content_type($uploadfile['tmp_name']); + $FmtV['$upmime'] = $mime; if ($mime != $UploadExts[$ext]) { if (!is_array($EnableUploadMimeMatch) || !isset($EnableUploadMimeMatch[$ext]) - || !preg_match($EnableUploadMimeMatch[$ext], $mime)) + || !preg_match($EnableUploadMimeMatch[$ext], $mime)) { return "upresult=mimemismatch&upext=$ext&upmime=$mime"; + } } } diff --git a/scripts/utils.php b/scripts/utils.php index bcbdcf10..a26b786c 100644 --- a/scripts/utils.php +++ b/scripts/utils.php @@ -21,17 +21,18 @@ To disable all these functions, add to config.php: $EnablePmUtils = 0; */ -function PmUtilsJS() { +function PmUtilsJS($pagename) { global $PmTOC, $EnableSortable, $EnableHighlight, $EnableLocalTimes, $ToggleNextSelector, $LinkFunctions, $FarmD, $HTMLStylesFmt, $HTMLHeaderFmt, $EnablePmSyntax, $CustomSyntax, - $EnableCopyCode, $EnableDarkThemeToggle, $EnableRedirectQuiet; + $EnableCopyCode, $EnableDarkThemeToggle, $EnableRedirectQuiet, + $EnableUploadDrop, $UploadExtSize, $EnableUploadAuthorRequired; $utils = "$FarmD/pub/pmwiki-utils.js"; $dark = "$FarmD/pub/pmwiki-darktoggle.js"; $cc = IsEnabled($EnableCopyCode, 0)? XL('Copy code') : ''; - if($cc) { + if ($cc) { SDVA($HTMLStylesFmt, array('copycode'=>' .pmcopycode { cursor:pointer; display:block; border-radius:.2em; opacity:.2; position:relative; z-index:2; } .pmcopycode::before { content:"+"; display:block; width:.8em; height:.8em; line-height:.8em; text-align:center; } @@ -41,6 +42,24 @@ function PmUtilsJS() { pre:hover .pmcopycode { opacity:1; } ')); } + if (IsEnabled($EnableUploadDrop, 0) && CondAuth($pagename, 'upload', 1)) { + $ddmu = array( + 'action' => '{$PageUrl}?action=postupload', + 'token' => ['$TokenName', pmtoken()], + 'label' => XL('ULdroplabel'), + 'badtype' => str_replace('$', '#', XL('ULbadtype')), + 'toobig' => str_replace('$', '#', preg_replace('/\\s+/',' ', XL('ULtoobigext'))), + 'sizes' => array(), + ); + if(IsEnabled($EnableUploadAuthorRequired, 0)) { + $ddmu['areq'] = XL('ULauthorrequired'); + } + + foreach($UploadExtSize as $ext=>$bytes) { + if($bytes>0) $ddmu['sizes'][$ext] = $bytes; + } + } + else $ddmu = false; if (file_exists($utils)) { $mtime = filemtime($utils); @@ -51,6 +70,7 @@ function PmUtilsJS() { 'toggle' => IsEnabled($ToggleNextSelector, 0), 'localtimes' => IsEnabled($EnableLocalTimes, 0), 'rediquiet' => IsEnabled($EnableRedirectQuiet, 0), + 'updrop' => $ddmu, ); $enabled = $PmTOC['Enable']; foreach($config as $i) { @@ -69,12 +89,15 @@ function PmUtilsJS() { if (IsEnabled($EnablePmSyntax, 0)) { # inject before skins and local.css $cs = is_array(@$CustomSyntax) ? pm_json_encode(array_values($CustomSyntax), true) : ''; - array_unshift($HTMLHeaderFmt, "<link rel='stylesheet' - href='\$FarmPubDirUrl/guiedit/pmwiki.syntax.css'> - <script src='\$FarmPubDirUrl/guiedit/pmwiki.syntax.js' data-imap='{\$EnabledIMap}' + array_unshift($HTMLHeaderFmt, "<script data-imap='{\$EnabledIMap}' + src='\$FarmPubDirUrl/guiedit/pmwiki.syntax.js' data-label=\"$[Highlight]\" data-mode='$EnablePmSyntax' data-custom=\"$cs\"></script>"); } + if (IsEnabled($EnablePmSyntax, 0) || $ddmu) { + array_unshift($HTMLHeaderFmt, "<link rel='stylesheet' + href='\$FarmPubDirUrl/guiedit/pmwiki.syntax.css'>"); + } // Dark theme toggle, needs to be very early $enabled = IsEnabled($EnableDarkThemeToggle, 0); @@ -89,45 +112,5 @@ function PmUtilsJS() { data-config='$json'></script>"); } } -PmUtilsJS(); - -## This is a replacement for json_encode+PHSC, but only for arrays that -## are used by the PmWiki core. It may or may not work in other cases. -## This may fail with international characters if UTF-8 is not enabled. -function pm_json_encode($x, $encodespecial=false) { - if (!isset($x) || is_null($x)) return 'null'; - if (is_bool($x)) return $x? "true" : "false"; - if (is_int($x) || is_float($x)) return strval($x); - - if (function_exists('json_encode')) - $out = json_encode($x); - - elseif (is_string($x)) ## escape controls and specials per RFC:8259 - $out = '"'.preg_replace_callback("/[\x00-\x1f\\/\\\\\"]/",'cb_rfc8259',$x).'"'; - - elseif (is_array($x)) { - $a = array(); - if (array_values($x) === $x) { # numeric sequential array - foreach($x as $v) - $a[] = pm_json_encode($v); - - $out = "[".implode(',', $a)."]"; - } - else { # associative array -> json object - foreach($x as $k=>$v) { - $jk = is_int($k)? "\"$k\"" : pm_json_encode($k); - $jv = pm_json_encode($v); - $a[] = "$jk:$jv"; - } - $out = "{".implode(',', $a)."}"; - } - } - - else return 'null'; # other types not yet supported - - return $encodespecial? PHSC($out, ENT_QUOTES) : $out; -} -function cb_rfc8259($m) { - return sprintf('\\u00%02x', ord($m[0])); -} +PmUtilsJS($pagename); |