aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpetko <petko@524c5546-5005-0410-9a3e-e25e191bd360>2024-07-26 16:04:48 +0000
committerpetko <petko@524c5546-5005-0410-9a3e-e25e191bd360>2024-07-26 16:04:48 +0000
commit58a411afb156c962d2b62d0a01e23e769a3aacf3 (patch)
tree853e7d94aec68e31ad7ed19959725b714dfb8d4a
parent5ae73e69e396caa528808354e9f88ee5a93b5676 (diff)
downloadpmwiki.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.php48
-rw-r--r--pub/guiedit/pmwiki.syntax.css59
-rw-r--r--pub/pmwiki-utils.js174
-rw-r--r--scripts/upload.php35
-rw-r--r--scripts/utils.php77
5 files changed, 331 insertions, 62 deletions
diff --git a/pmwiki.php b/pmwiki.php
index 1bcba98b..5a897739 100644
--- a/pmwiki.php
+++ b/pmwiki.php
@@ -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, '&amp;').replace(/[<]/g, '&lt;').replace(/[>]/g, '&gt;'); }
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);