| applyUploadLimits(); | |
| session_start(); | |
| $p='1668'; | |
| $ud='./'; | |
| $n='YK文件管理器'; | |
| $qn='[]'; | |
| if(!is_dir($ud))$ud=dirname(__FILE__); | |
| function qnPack($s){return str_replace(['\\',"'"],['\\\\',"\\'"],$s);} | |
| function qnUnpack($s){return str_replace(["\\\\","\\'"],["\\","'"],$s);} | |
| function getQn($qn){ | |
| $l=json_decode($qn,true); | |
| if(is_array($l))return $l; | |
| $l=json_decode(stripslashes($qn),true); | |
| return is_array($l)?$l:[]; | |
| } | |
| function refreshQnFromDisk(){ | |
| global $qn; | |
| clearstatcache(true,__FILE__); | |
| if(function_exists('opcache_invalidate'))@opcache_invalidate(__FILE__,true); | |
| $src=@file_get_contents(__FILE__); | |
| if($src&&preg_match('/^\$qn=\'((?:[^\'\\\\]|\\\\.)*)\'\s*;/m',$src,$m))$qn=qnUnpack($m[1]); | |
| return getQn($qn); | |
| } | |
| function qnBaseDir(){ | |
| global $ud; | |
| if($ud==='.'||$ud===''||$ud==='./')$b=__DIR__; | |
| else{ | |
| $b=str_replace('\\','/',$ud); | |
| if($b[0]!=='/'&&!(PHP_OS_FAMILY==='Windows'&&preg_match('#^[A-Za-z]:/#',$b)))$b=__DIR__.'/'.$b; | |
| } | |
| $r=realpath($b); | |
| return $r?:__DIR__; | |
| } | |
| function qnStorePath($p){ | |
| $p=sp($p); | |
| if($p==='.'||$p===''||strpos($p,'..')!==false)return false; | |
| $base=qnBaseDir(); | |
| $baseNorm=str_replace('\\','/',$base); | |
| $real=null; | |
| if(is_file($p))$real=realpath($p); | |
| if(!$real){$try=$baseNorm.'/'.ltrim($p,'./');if(is_file($try))$real=realpath($try);} | |
| if(!$real||!is_file($real))return false; | |
| $realNorm=str_replace('\\','/',$real); | |
| if($realNorm!==$baseNorm&&strpos($realNorm,$baseNorm.'/')!==0)return false; | |
| if($realNorm===$baseNorm)return false; | |
| return substr($realNorm,strlen($baseNorm)+1); | |
| } | |
| function saveQn(&$qn,$list){ | |
| $new=json_encode(array_values($list),JSON_UNESCAPED_UNICODE); | |
| $fp=file_get_contents(__FILE__); | |
| if($fp===false)return false; | |
| $rep="\$qn='".qnPack($new)."';"; | |
| $fp2=preg_replace('/^\$qn=\'(?:[^\'\\\\]|\\\\.)*\'\s*;/m',$rep,$fp,1,$n); | |
| if(!$n)return false; | |
| if(file_put_contents(__FILE__,$fp2)===false)return false; | |
| clearstatcache(true,__FILE__); | |
| if(function_exists('opcache_invalidate'))@opcache_invalidate(__FILE__,true); | |
| $qn=$new; | |
| return true; | |
| } | |
| function generateCaptcha(){ | |
| $chars='abcdefghjkmnpqrstuvwxyz23456789'; | |
| $len=4; | |
| $code=''; | |
| for($i=0;$i<$len;$i++)$code.=$chars[rand(0,strlen($chars)-1)]; | |
| $_SESSION['captcha']=['code'=>strtolower($code),'time'=>time()]; | |
| $w=120;$h=40; | |
| $im=imagecreatetruecolor($w,$h); | |
| $bg=imagecolorallocate($im,255,255,255); | |
| imagefill($im,0,0,$bg); | |
| for($i=0;$i<5;$i++){$c=imagecolorallocate($im,rand(100,200),rand(100,200),rand(100,200));imageline($im,rand(0,$w),rand(0,$h),rand(0,$w),rand(0,$h),$c);} | |
| for($i=0;$i<$len;$i++){$c=imagecolorallocate($im,rand(0,150),rand(0,150),rand(0,150));imagestring($im,5,15+$i*22,rand(8,15),$code[$i],$c);} | |
| for($i=0;$i<50;$i++){$c=imagecolorallocate($im,rand(150,220),rand(150,220),rand(150,220));imagesetpixel($im,rand(0,$w),rand(0,$h),$c);} | |
| ob_start();imagepng($im);$data=ob_get_clean();imagedestroy($im); | |
| header('Content-Type: image/png'); | |
| echo $data; | |
| exit; | |
| } | |
| function validateCaptcha($input){ | |
| if(!isset($_SESSION['captcha'])||time()-$_SESSION['captcha']['time']>300){unset($_SESSION['captcha']);return false;} | |
| $r=$_SESSION['captcha']['code']===strtolower($input); | |
| unset($_SESSION['captcha']); | |
| return$r; | |
| } | |
| function fs($b){$u=['B','KB','MB','GB'];$i=0;while($b>=1024&&$i<3){$b/=1024;$i++;}return round($b,2).' '.$u[$i];} | |
| function applyUploadLimits(){ | |
| @ini_set('upload_max_filesize','1024M'); | |
| @ini_set('post_max_size','1024M'); | |
| @ini_set('max_input_time','3600'); | |
| } | |
| function unlimitedRun(){@set_time_limit(0);@ini_set('max_execution_time','0');@ignore_user_abort(true);} | |
| function fmtElapsed($sec){$sec=max(0,(float)$sec);if($sec<60)return round($sec,2).' 秒';$m=(int)floor($sec/60);$s=round($sec-$m*60,1);if($m<60)return $m.' 分 '.$s.' 秒';$h=(int)floor($m/60);$m=$m%60;return $h.' 小时 '.$m.' 分 '.$s.' 秒';} | |
| function pp($pt){ | |
| $pt=sp($pt); | |
| if($pt==='.'||$pt==='')return null; | |
| $p=str_replace('\\','/',dirname($pt)); | |
| if($p==='.'||$p==='')return '.'; | |
| return sp($p); | |
| } | |
| function sp($pt){$pt=str_replace('\\','/',$pt);$pt=preg_replace('/\.{2,}/','.',$pt);$pt=preg_replace('/\/+/','/',$pt);$pt=rtrim($pt,'/');return $pt===''?'.':$pt;} | |
| function safeUploadRel($rel){$rel=str_replace('\\','/',trim($rel));if($rel===''||strpos($rel,'..')!==false)return false;$parts=array_filter(explode('/',$rel),function($p){return $p!==''&&$p!=='.';});if(!$parts)return false;foreach($parts as $p){if($p==='..'||strpos($p,'/')!==false||strpos($p,'\\')!==false)return false;}return implode('/',$parts);} | |
| function rc($s,$d){if(is_dir($s)){mkdir($d,0755,true);foreach(scandir($s)as $it)if($it!=='.'&&$it!=='..')rc($s.'/'.$it,$d.'/'.$it);}else{copy($s,$d);}return true;} | |
| function ce($cmd){if(!function_exists('exec'))return false;$df=trim(ini_get('disable_functions'));if($df){$da=array_map('trim',explode(',',$df));if(in_array('exec',$da))return false;}$o=[];$c=0;exec("command -v ".escapeshellarg($cmd)." 2>/dev/null",$o,$c);return$c===0&&!empty($o);} | |
| function rr($d){if(!is_dir($d))return@unlink($d);foreach(scandir($d)as $it){if($it==='.'||$it==='..')continue;$p=$d.'/'.$it;is_dir($p)?rr($p):@unlink($p);}return@rmdir($d);} | |
| function progressCallback($ch, $dt, $dn, $ut, $un){ | |
| static $lastUpdate = 0; | |
| $now = time(); | |
| if($now > $lastUpdate || $dn == $dt){ | |
| if(session_status() !== PHP_SESSION_ACTIVE) session_start(); | |
| $_SESSION['dl_progress'] = ['start'=>$_SESSION['dl_start_time']??time(), 'downloaded'=>$dn, 'total'=>$dt]; | |
| session_write_close(); | |
| $lastUpdate = $now; | |
| } | |
| return 0; | |
| } | |
| function dlrf($u, $sp){ | |
| if(session_status() !== PHP_SESSION_ACTIVE) session_start(); | |
| $_SESSION['dl_start_time'] = time(); | |
| $_SESSION['dl_progress'] = ['start'=>time(), 'downloaded'=>0, 'total'=>0]; | |
| session_write_close(); | |
| $fp = fopen($sp, 'w+'); | |
| if(!$fp) return ['s'=>false, 'e'=>'无法打开目标文件写入']; | |
| $ch = curl_init(); | |
| curl_setopt_array($ch, [ | |
| CURLOPT_URL => $u, | |
| CURLOPT_FILE => $fp, | |
| CURLOPT_FOLLOWLOCATION => true, | |
| CURLOPT_SSL_VERIFYPEER => false, | |
| CURLOPT_SSL_VERIFYHOST => false, | |
| CURLOPT_TIMEOUT => 3600, | |
| CURLOPT_NOPROGRESS => false, | |
| CURLOPT_PROGRESSFUNCTION => 'progressCallback' | |
| ]); | |
| $s = curl_exec($ch); | |
| $hc = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
| $er = curl_error($ch); | |
| curl_close($ch); | |
| fclose($fp); | |
| if(!$s || $hc >= 400){ | |
| @unlink($sp); | |
| return ['s'=>false, 'e'=>$er ?: "HTTP $hc"]; | |
| } | |
| return ['s'=>true]; | |
| } | |
| function get_dl_progress(){ | |
| if(session_status() !== PHP_SESSION_ACTIVE) session_start(); | |
| if(isset($_SESSION['dl_progress']) && is_array($_SESSION['dl_progress'])){ | |
| $p = $_SESSION['dl_progress']; | |
| session_write_close(); | |
| $elapsed = time() - $p['start']; | |
| $speed = $elapsed > 0 ? ($p['downloaded'] / $elapsed) : 0; | |
| return [ | |
| 's' => true, | |
| 'downloaded' => $p['downloaded'], | |
| 'total' => $p['total'], | |
| 'speed' => $speed, | |
| 'pct' => $p['total'] > 0 ? round(($p['downloaded'] / $p['total']) * 100, 2) : 0 | |
| ]; | |
| } | |
| session_write_close(); | |
| return ['s' => false]; | |
| } | |
| class SZ{private $f,$fn,$e=[],$tf; | |
| public function __construct($f,$fn){$this->f=$f;$this->fn=$fn;$this->tf=tempnam(sys_get_temp_dir(),'z');} | |
| public function __destruct(){if($this->tf&&file_exists($this->tf))@unlink($this->tf);} | |
| public function c(){$fl=$this->sf($this->f);foreach($fl as $fi)if(is_file($fi)){$co=file_get_contents($fi);$rp=$this->fn.'/'.substr($fi,strlen($this->f)+1);$this->e[]=['n'=>$rp,'c'=>$co,'cr'=>crc32($co),'sz'=>strlen($co)];}return!empty($this->e);} | |
| private function sf($d){$r=[];foreach(scandir($d)as $it)if($it!=='.'&&$it!=='..'){$pt=$d.'/'.$it;$r[]=$pt;if(is_dir($pt))$r=array_merge($r,$this->sf($pt));}return $r;} | |
| public function sv($pt){$fp=fopen($this->tf,'wb');$cd='';$off=0; | |
| foreach($this->e as $en){$nl=strlen($en['n']);$lh="PK\x03\x04".pack('vvvvVVvv',20,0,0,0,$en['cr'],$en['sz'],$en['sz'],$nl).pack('v',0).$en['n'];fwrite($fp,$lh);fwrite($fp,$en['c']);$ch="PK\x01\x02".pack('vvvvvvVVvvvvVV',20,20,0,0,0,0,$en['cr'],$en['sz'],$en['sz'],$nl,0,0,0,0,0,$off).$en['n'];$cd.=$ch;$off+=strlen($lh)+$en['sz'];} | |
| fwrite($fp,$cd);$eocd="PK\x05\x06".pack('vvVVvv',0,0,count($this->e),count($this->e),strlen($cd),$off).pack('vv',0,0);fwrite($fp,$eocd);fclose($fp);copy($this->tf,$pt);return true;} | |
| } | |
| function jsonResponse($data){ | |
| while(ob_get_level()>0)@ob_end_clean(); | |
| header('Content-Type: application/json; charset=UTF-8'); | |
| header('Cache-Control: no-store, no-cache, must-revalidate'); | |
| $flags=JSON_UNESCAPED_UNICODE; | |
| if(defined('JSON_INVALID_UTF8_SUBSTITUTE'))$flags|=JSON_INVALID_UTF8_SUBSTITUTE; | |
| $j=json_encode($data,$flags); | |
| if($j===false)$j='{"s":false,"msg":"响应编码失败"}'; | |
| echo $j; | |
| exit; | |
| } | |
| function fmCleanMsg($s){ | |
| $s=(string)$s; | |
| if($s==='')return''; | |
| if(function_exists('mb_convert_encoding'))@$s=mb_convert_encoding($s,'UTF-8','UTF-8'); | |
| return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/u','',substr($s,0,800)); | |
| } | |
| function fmZipEntrySafe($name){ | |
| $name=str_replace('\\','/',(string)$name); | |
| if($name===''||strpos($name,"\0")!==false)return false; | |
| if($name[0]==='/'||preg_match('#(^|/)\.\.(/|$)#',$name))return false; | |
| return true; | |
| } | |
| function fmExtractZip($zf,$ed){ | |
| if(!is_file($zf))return['s'=>false,'e'=>'压缩文件不存在']; | |
| $edReal=realpath($ed); | |
| if($edReal===false||!is_dir($edReal)||!is_writable($edReal))return['s'=>false,'e'=>'目标目录不可写']; | |
| if(class_exists('ZipArchive')){ | |
| $z=new ZipArchive(); | |
| $open=@$z->open($zf); | |
| if($open===true){ | |
| for($i=0;$i<$z->numFiles;$i++){ | |
| $name=$z->getNameIndex($i); | |
| if(!fmZipEntrySafe($name)){$z->close();return['s'=>false,'e'=>'压缩包内含非法路径']; } | |
| } | |
| $ok=@$z->extractTo($edReal); | |
| $err=''; | |
| if(!$ok&&method_exists($z,'getStatusString'))$err=$z->getStatusString(); | |
| $z->close(); | |
| if($ok)return['s'=>true,'e'=>'']; | |
| return['s'=>false,'e'=>fmCleanMsg($err?:'ZipArchive 解压失败')]; | |
| } | |
| } | |
| if(ce('unzip')){ | |
| $o=[];$c=1; | |
| @exec('unzip -o '.escapeshellarg($zf).' -d '.escapeshellarg($edReal).' 2>&1',$o,$c); | |
| if($c===0)return['s'=>true,'e'=>'']; | |
| return['s'=>false,'e'=>fmCleanMsg(implode("\n",$o)?:'unzip 解压失败')]; | |
| } | |
| if(class_exists('ZipArchive'))return['s'=>false,'e'=>'无法打开 ZIP 文件或文件已损坏']; | |
| return['s'=>false,'e'=>'未安装 ZipArchive 扩展或 unzip 命令']; | |
| } | |
| function fmValidName($n){return$n!==''&&$n!=='.'&&$n!=='..'&&strpos($n,'/')===false&&strpos($n,'\\')===false;} | |
| function fmDestPath($cd,$rel){return $cd==='.'?$rel:$cd.'/'.$rel;} | |
| function fmUniqueCopyRel($cd,$relPath){ | |
| $relPath=str_replace('\\','/',$relPath); | |
| $parent=dirname($relPath); | |
| if($parent==='.'||$parent==='')$parent=''; | |
| $base=basename($relPath); | |
| $pos=strrpos($base,'.'); | |
| if($pos===false){$bn=$base;$ext='';} | |
| else{$bn=substr($base,0,$pos);$ext=substr($base,$pos);} | |
| $nn=$bn.' - 副本'.$ext; | |
| $i=2; | |
| while($i<10000){ | |
| $tryRel=$parent===''?$nn:$parent.'/'.$nn; | |
| if(!file_exists(fmDestPath($cd,$tryRel)))return $tryRel; | |
| $nn=$bn.' - 副本 ('.$i.')'.$ext; | |
| $i++; | |
| } | |
| return false; | |
| } | |
| function fmPostNames(){ | |
| if(isset($_POST['n'])){ | |
| if(is_array($_POST['n']))return array_values(array_filter($_POST['n'],function($x){return $x!==''&&$x!==null;})); | |
| if($_POST['n']!=='')return[$_POST['n']]; | |
| } | |
| if(isset($_POST['n[]'])){ | |
| if(is_array($_POST['n[]']))return array_values(array_filter($_POST['n[]'],function($x){return $x!==''&&$x!==null;})); | |
| if($_POST['n[]']!=='')return[$_POST['n[]']]; | |
| } | |
| return[]; | |
| } | |
| function fmZipAddPath($z,$fsPath,$entryName){ | |
| if(!file_exists($fsPath))return false; | |
| $entryName=str_replace('\\','/',$entryName); | |
| if(is_file($fsPath)){ | |
| if(!fmZipEntrySafe($entryName))return false; | |
| return@$z->addFile($fsPath,$entryName); | |
| } | |
| if(!is_dir($fsPath))return false; | |
| $realTg=str_replace('\\','/',realpath($fsPath)); | |
| if($realTg===false)return false; | |
| $base=rtrim($entryName,'/'); | |
| if(!fmZipEntrySafe($base))return false; | |
| try{ | |
| foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fsPath,FilesystemIterator::SKIP_DOTS),RecursiveIteratorIterator::SELF_FIRST)as $f){ | |
| $realPath=str_replace('\\','/',$f->getRealPath()); | |
| $relativePath=substr($realPath,strlen($realTg)+1); | |
| $zipName=$base.($relativePath!==''?'/'.$relativePath:''); | |
| if(!fmZipEntrySafe($zipName))return false; | |
| if($f->isDir()){if($relativePath)$z->addEmptyDir($zipName);}else{@$z->addFile($f->getPathname(),$zipName);} | |
| } | |
| }catch(\Throwable $e){return false;} | |
| return true; | |
| } | |
| function fmZipOneDir($cd,$tg,$fn,$outZip=null){ | |
| $zipName=$outZip?:($fn.'.zip'); | |
| $zf=$cd==='.'?$zipName:$cd.'/'.$zipName; | |
| $zc=false; | |
| if(ce('zip')){exec("cd ".escapeshellarg($cd)." && zip -9 -r ".escapeshellarg($zf)." ".escapeshellarg($fn)." 2>&1",$o,$c);if($c===0&&file_exists($zf))$zc=true;} | |
| if(!$zc&&class_exists('ZipArchive')){ | |
| $z=new ZipArchive();if($z->open($zf,ZipArchive::CREATE|ZipArchive::OVERWRITE)===true){ | |
| $realTg=str_replace('\\','/',realpath($tg)); | |
| foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tg),RecursiveIteratorIterator::SELF_FIRST)as $f){ | |
| $realPath=str_replace('\\','/',$f->getRealPath()); | |
| $relativePath=substr($realPath,strlen($realTg)+1); | |
| if($f->isDir()){if($relativePath)$z->addEmptyDir($fn.'/'.$relativePath);}else{$z->addFile($f->getPathname(),$fn.'/'.$relativePath);} | |
| } | |
| $z->close();$zc=true; | |
| } | |
| } | |
| if(!$zc){$sz=new SZ($tg,$fn);if($sz->c())$sz->sv($zf);$zc=true;} | |
| return['zc'=>$zc,'zf'=>$zf,'n'=>$zipName]; | |
| } | |
| function fmParseZipName($s){ | |
| $s=trim((string)$s); | |
| if($s==='')return false; | |
| $s=basename(str_replace('\\','/',$s)); | |
| if($s===''||$s==='.'||$s==='..'||strpos($s,'/')!==false||strpos($s,"\0")!==false)return false; | |
| if(strtolower(substr($s,-4))!=='.zip')$s.='.zip'; | |
| $base=pathinfo($s,PATHINFO_FILENAME); | |
| if($base===''||$base==='.'||$base==='..')return false; | |
| return $s; | |
| } | |
| function fmTri($v){return($v&4?'r':'-').($v&2?'w':'-').($v&1?'x':'-');} | |
| function fmPermInfo($path){ | |
| if(!file_exists($path))return null; | |
| $p=@fileperms($path); | |
| if($p===false)return null; | |
| $m=$p&0777; | |
| $isD=is_dir($path); | |
| return['oct'=>substr(sprintf('%o',$p),-4),'sym'=>($isD?'d':'-').fmTri($m>>6).fmTri(($m>>3)&7).fmTri($m&7),'d'=>$isD]; | |
| } | |
| function fmParseChmodMode($s){ | |
| $s=trim($s); | |
| if($s==='')return false; | |
| if(preg_match('/^[0-7]{3,4}$/',$s))return octdec($s); | |
| if(preg_match('/^[rwx-]{9}$/',$s)){ | |
| $o=0; | |
| for($j=0;$j<3;$j++){ | |
| $g=substr($s,$j*3,3); | |
| $v=0; | |
| if($g[0]==='r')$v|=4; | |
| if($g[1]==='w')$v|=2; | |
| if($g[2]==='x')$v|=1; | |
| $o|=$v<<(6-$j*3); | |
| } | |
| return $o; | |
| } | |
| return false; | |
| } | |
| function fmChmodPath($path,$mode,$recursive){ | |
| if(!file_exists($path))return false; | |
| if(!@chmod($path,$mode))return false; | |
| if($recursive&&is_dir($path)){ | |
| try{ | |
| $it=new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path,FilesystemIterator::SKIP_DOTS),RecursiveIteratorIterator::SELF_FIRST); | |
| foreach($it as $f)@chmod($f->getPathname(),$mode); | |
| }catch(\Throwable $e){} | |
| } | |
| return true; | |
| } | |
| function getFileList($cd){ | |
| $it=[]; | |
| if(is_dir($cd)){ | |
| $dh=opendir($cd); | |
| while(($itnm=readdir($dh))!==false)if($itnm!=='.'&&$itnm!=='..'){ | |
| $fp=$cd==='.'?$itnm:$cd.'/'.$itnm; | |
| $pi=fmPermInfo($fp); | |
| $it[]=['n'=>$itnm,'p'=>$fp,'d'=>is_dir($fp),'sz'=>is_dir($fp)?'-':filesize($fp),'szf'=>is_dir($fp)?'-':fs(filesize($fp)),'mt'=>date('Y-m-d H:i',filemtime($fp)),'pm'=>$pi?$pi['oct']:'','ps'=>$pi?$pi['sym']:'']; | |
| } | |
| closedir($dh); | |
| } | |
| usort($it,function($a,$b){if($a['d']!==$b['d'])return$a['d']?-1:1;return strcasecmp($a['n'],$b['n']);}); | |
| return$it; | |
| } | |
| if(isset($_POST['a'])||isset($_GET['a'])){ | |
| @ini_set('display_errors','0'); | |
| if(ob_get_level())@ob_end_clean(); | |
| ob_start(); | |
| $a=isset($_POST['a'])?$_POST['a']:$_GET['a']; | |
| $cd=isset($_POST['dir'])?sp($_POST['dir']):(isset($_GET['dir'])?sp($_GET['dir']):'.'); | |
| $cd=$cd==='.'||empty($cd)?'.':$cd; | |
| if($a==='captcha'){generateCaptcha();} | |
| elseif($a==='progress'){jsonResponse(get_dl_progress());} | |
| elseif($a==='lg'&&$_POST['pw']===$p){if(!validateCaptcha($_POST['cp'])){jsonResponse(['s'=>false,'c'=>true]);}else{$_SESSION['li']=true;jsonResponse(['s'=>true]);}} | |
| elseif($a==='lo'){session_destroy();jsonResponse(['s'=>true]);} | |
| elseif($a==='cpw'&&$_SESSION['li']){$old=$_POST['old'];$new=$_POST['new'];if($old!==$p){jsonResponse(['s'=>false,'msg'=>'原密码错误']);}elseif(strlen($new)<1){jsonResponse(['s'=>false,'msg'=>'新密码不能为空']);}else{$fp=file_get_contents(__FILE__);$fp=str_replace("\$p='".addslashes($p)."';","\$p='".addslashes($new)."';",$fp);file_put_contents(__FILE__,$fp);$p=$new;jsonResponse(['s'=>true,'msg'=>'密码修改成功']);}} | |
| elseif($a==='cf'&&$_SESSION['li']){ | |
| $nf=$cd.'/'.$_POST['n'];$created=false;$error=''; | |
| if(is_file($nf)){$error='文件已存在';} | |
| elseif(is_dir($nf)){$error='存在同名文件夹';} | |
| else{$r=file_put_contents($nf,'');if($r===false){$error='写入失败';}else{$created=true;}} | |
| jsonResponse(['s'=>$created,'n'=>$_POST['n'],'e'=>$error,'path'=>$nf]); | |
| } | |
| elseif($a==='cd'&&$_SESSION['li']){ | |
| $nf=$cd.'/'.$_POST['n'];$created=false;$error=''; | |
| if(is_dir($nf)){$error='文件夹已存在';} | |
| elseif(is_file($nf)){$error='存在同名文件';} | |
| else{$r=mkdir($nf,0755,true);if(!$r){$error='创建失败';}else{$created=true;}} | |
| jsonResponse(['s'=>$created,'n'=>$_POST['n'],'e'=>$error,'path'=>$nf]); | |
| } | |
| elseif($a==='del'&&$_SESSION['li']){ | |
| $names=isset($_POST['n'])?(is_array($_POST['n'])?$_POST['n']:[$_POST['n']]):[]; | |
| $sc=0;$fc=0; | |
| foreach($names as $n){if(!$n)continue;$tg=$cd.'/'.$n; | |
| if(is_dir($tg)){ | |
| $ok=false; | |
| if(PHP_OS==='WINNT'){@exec('rd /s /q '.escapeshellarg($tg),$o,$c);if($c===0||!file_exists($tg))$ok=true;} | |
| elseif(ce('rm')){@exec('rm -rf '.escapeshellarg($tg),$o,$c);if($c===0||!file_exists($tg))$ok=true;} | |
| if(!$ok)try{rr($tg);if(!file_exists($tg))$ok=true;}catch(\Throwable $e){} | |
| $ok?$sc++:$fc++; | |
| }else{if(@unlink($tg))$sc++;else $fc++;} | |
| } | |
| jsonResponse(['s'=>$fc===0,'sc'=>$sc,'fc'=>$fc,'msg'=>$fc?"部分失败: $sc 成功, $fc 失败":"删除成功: $sc 个项目"]); | |
| } | |
| elseif($a==='cp'&&$_SESSION['li']){ | |
| $names=isset($_POST['n'])?(is_array($_POST['n'])?$_POST['n']:[$_POST['n']]):[]; | |
| $dst=sp($_POST['d']);$sc=0;$fc=0; | |
| foreach($names as $n){if(!$n)continue;$src=$cd.'/'.$n;$dd=$dst.'/'.$n;if(file_exists($dd)){$fc++;continue;}if(@rc($src,$dd))$sc++;else $fc++;} | |
| jsonResponse(['s'=>$fc===0,'sc'=>$sc,'fc'=>$fc,'msg'=>$fc?"部分失败: $sc 成功, $fc 失败":"复制成功: $sc 个项目"]); | |
| } | |
| elseif($a==='mv'&&$_SESSION['li']){ | |
| $names=isset($_POST['n'])?(is_array($_POST['n'])?$_POST['n']:[$_POST['n']]):[]; | |
| $dst=sp($_POST['d']);$sc=0;$fc=0; | |
| foreach($names as $n){if(!$n)continue;$src=$cd.'/'.$n;$dd=$dst.'/'.$n;if($src===$dd){$fc++;continue;}if(file_exists($dd)){$fc++;continue;}if(@rename($src,$dd))$sc++;else $fc++;} | |
| jsonResponse(['s'=>$fc===0,'sc'=>$sc,'fc'=>$fc,'msg'=>$fc?"部分失败: $sc 成功, $fc 失败":"移动成功: $sc 个项目"]); | |
| } | |
| elseif($a==='rn'&&$_SESSION['li']){$r=rename($cd.'/'.$_POST['on'],$cd.'/'.$_POST['nn']);jsonResponse(['s'=>$r]);} | |
| elseif($a==='chmod'&&$_SESSION['li']){ | |
| $names=isset($_POST['n'])?(is_array($_POST['n'])?$_POST['n']:[$_POST['n']]):[]; | |
| $modeStr=trim($_POST['mode']??''); | |
| $recursive=!empty($_POST['recursive']); | |
| $items=[]; | |
| foreach($names as $n){ | |
| if(!fmValidName($n))continue; | |
| $tg=$cd==='.'?$n:$cd.'/'.$n; | |
| if(!file_exists($tg)){$items[]=['n'=>$n,'e'=>'不存在'];continue;} | |
| if($modeStr===''){ | |
| $pi=fmPermInfo($tg); | |
| $items[]=['n'=>$n,'d'=>is_dir($tg),'oct'=>$pi?$pi['oct']:'','sym'=>$pi?$pi['sym']:'']; | |
| continue; | |
| } | |
| $items[]=['n'=>$n,'d'=>is_dir($tg)]; | |
| } | |
| if($modeStr===''){jsonResponse(['s'=>!empty($items),'items'=>$items,'msg'=>empty($items)?'没有可读取的项目':'']);} | |
| if(PHP_OS_FAMILY==='Windows'){jsonResponse(['s'=>false,'msg'=>'当前系统不支持修改 Unix 权限']);} | |
| $mode=fmParseChmodMode($modeStr); | |
| if($mode===false){jsonResponse(['s'=>false,'msg'=>'权限格式无效,请使用 755、0755 或 rwxr-xr-x']);} | |
| $sc=0;$fc=0;$errs=[]; | |
| foreach($names as $n){ | |
| if(!fmValidName($n))continue; | |
| $tg=$cd==='.'?$n:$cd.'/'.$n; | |
| if(!file_exists($tg)){$fc++;continue;} | |
| $rec=$recursive&&is_dir($tg); | |
| if(fmChmodPath($tg,$mode,$rec))$sc++;else{$fc++;$errs[]=$n;} | |
| } | |
| jsonResponse(['s'=>$fc===0,'sc'=>$sc,'fc'=>$fc,'msg'=>$fc?('部分失败: '.$sc.' 成功, '.$fc.' 失败'.($errs?' ('.implode(', ',array_slice($errs,0,5)).')':'')):'已修改 '.$sc.' 项权限']); | |
| } | |
| elseif($a==='dup'&&$_SESSION['li']){ | |
| $n=isset($_POST['n'])?trim($_POST['n']):''; | |
| if($n===''||$n==='.'||$n==='..'){jsonResponse(['s'=>false,'msg'=>'名称无效']);} | |
| $src=$cd.'/'.$n; | |
| if(!file_exists($src)){jsonResponse(['s'=>false,'msg'=>'源不存在']);} | |
| $isDir=is_dir($src); | |
| $pos=strrpos($n,'.'); | |
| if($isDir||$pos===false){$base=$n;$ext='';} | |
| else{$base=substr($n,0,$pos);$ext=substr($n,$pos);} | |
| $nn=$base.' - 副本'.$ext; | |
| $i=2; | |
| while(file_exists($cd.'/'.$nn)){$nn=$base.' - 副本 ('.$i.')'.$ext;$i++;} | |
| $dst=$cd.'/'.$nn; | |
| if(!@rc($src,$dst)){jsonResponse(['s'=>false,'msg'=>'复制失败']);} | |
| jsonResponse(['s'=>true,'n'=>$nn,'path'=>$dst,'d'=>$isDir]); | |
| } | |
| elseif($a==='upchk'&&$_SESSION['li']){ | |
| $rel=isset($_POST['rel'])?safeUploadRel($_POST['rel']):false; | |
| if(isset($_POST['rel'])&&$_POST['rel']!==''&&$rel===false){jsonResponse(['s'=>false,'e'=>'路径非法']);} | |
| $fn=$rel!==false?$rel:trim($_POST['n']??''); | |
| if($fn===''||$fn==='.'||$fn==='..'){jsonResponse(['s'=>false,'e'=>'无效文件名']);} | |
| if($rel===false&&strpos($fn,'/')!==false){jsonResponse(['s'=>false,'e'=>'无效文件名']);} | |
| $dest=fmDestPath($cd,$fn); | |
| jsonResponse(['s'=>true,'exists'=>file_exists($dest),'d'=>is_dir($dest),'n'=>$fn]); | |
| } | |
| elseif($a==='up'){ | |
| applyUploadLimits(); | |
| unlimitedRun(); | |
| if(!$_SESSION['li']){jsonResponse(['s'=>false,'n'=>'','e'=>'未登录或会话已过期']);} | |
| if(!isset($_FILES['fl'])||!is_array($_FILES['fl'])){ | |
| $cl=(int)($_SERVER['CONTENT_LENGTH']??0); | |
| $pm=ini_get('post_max_size'); | |
| $pmB=is_numeric($pm)?(int)$pm:(int)$pm*(['K'=>1024,'M'=>1048576,'G'=>1073741824][strtoupper(substr($pm,-1))]??1); | |
| jsonResponse(['s'=>false,'n'=>'','e'=>($cl>0&&$pmB>0&&$cl>$pmB)?'文件大小超过 post_max_size 限制('.$pm.')':'未收到上传文件']); | |
| } | |
| $f=$_FILES['fl']; | |
| $ue=is_array($f['error']??null)?($f['error'][0]??UPLOAD_ERR_NO_FILE):($f['error']??UPLOAD_ERR_NO_FILE); | |
| if($ue!==UPLOAD_ERR_OK){ | |
| $em=[UPLOAD_ERR_INI_SIZE=>'文件大小超过 upload_max_filesize 限制',UPLOAD_ERR_FORM_SIZE=>'文件大小超过表单限制',UPLOAD_ERR_PARTIAL=>'文件仅部分上传',UPLOAD_ERR_NO_FILE=>'未选择文件',UPLOAD_ERR_NO_TMP_DIR=>'缺少临时目录',UPLOAD_ERR_CANT_WRITE=>'临时文件写入失败',UPLOAD_ERR_EXTENSION=>'扩展阻止上传']; | |
| jsonResponse(['s'=>false,'n'=>'','e'=>$em[$ue]??"上传错误($ue)"]); | |
| } | |
| $rel=isset($_POST['rel'])?safeUploadRel($_POST['rel']):false; | |
| if(isset($_POST['rel'])&&$_POST['rel']!==''&&$rel===false){jsonResponse(['s'=>false,'n'=>'','e'=>'路径非法']);} | |
| $fn=$rel!==false?$rel:basename(is_array($f['name'])?$f['name'][0]:$f['name']); | |
| if($fn===''||$fn==='.'||$fn==='..'){jsonResponse(['s'=>false,'n'=>'','e'=>'无效文件名']);} | |
| $mode=trim($_POST['mode']??''); | |
| $dest=fmDestPath($cd,$fn); | |
| if(file_exists($dest)){ | |
| if($mode==='copy'){ | |
| $newRel=fmUniqueCopyRel($cd,$fn); | |
| if($newRel===false){jsonResponse(['s'=>false,'n'=>$fn,'e'=>'无法生成副本文件名']);} | |
| $fn=$newRel; | |
| $dest=fmDestPath($cd,$fn); | |
| }elseif($mode==='replace'){ | |
| if(is_dir($dest)&&!@rr($dest)){jsonResponse(['s'=>false,'n'=>$fn,'e'=>'无法替换现有文件夹']);} | |
| }else{jsonResponse(['s'=>false,'conflict'=>true,'n'=>$fn,'e'=>'文件已存在']);} | |
| } | |
| if($cd!=='.'&&!is_dir($cd)){jsonResponse(['s'=>false,'n'=>'','e'=>'目标目录不存在']);} | |
| if(!is_writable($cd==='.'?'.':$cd)){jsonResponse(['s'=>false,'n'=>'','e'=>'目标目录不可写']);} | |
| $dp=dirname($dest); | |
| if($dp!=='.'&&$dp!==''&&!is_dir($dp)&&!@mkdir($dp,0755,true)){jsonResponse(['s'=>false,'n'=>$fn,'e'=>'无法创建子目录']);} | |
| $tmp=is_array($f['tmp_name'])?$f['tmp_name'][0]:$f['tmp_name']; | |
| if(!move_uploaded_file($tmp,$dest)){jsonResponse(['s'=>false,'n'=>$fn,'e'=>'保存文件失败,请检查目录权限']);} | |
| jsonResponse(['s'=>true,'n'=>basename($fn),'path'=>$dest]); | |
| } | |
| elseif($a==='sf'&&$_SESSION['li']){$r=file_put_contents($cd.'/'.$_POST['fl'],$_POST['ct'])!==false;jsonResponse(['s'=>$r]);} | |
| elseif($a==='gf'&&$_SESSION['li']){ | |
| $fp=sp($_POST['p']??$_GET['p']??''); | |
| if(!$fp||!is_file($fp)||is_dir($fp)){jsonResponse(['s'=>false,'msg'=>'文件不存在']);} | |
| $sz=filesize($fp); | |
| if($sz>2097152){jsonResponse(['s'=>false,'msg'=>'文件超过 2MB,请单独打开']);} | |
| jsonResponse(['s'=>true,'p'=>$fp,'n'=>basename($fp),'ct'=>file_get_contents($fp)]); | |
| } | |
| elseif($a==='zp'&&$_SESSION['li']){ | |
| unlimitedRun();@ini_set('memory_limit','512M'); | |
| $t0=microtime(true); | |
| $names=fmPostNames(); | |
| if(empty($names)){jsonResponse(['s'=>false,'msg'=>'请选择要压缩的项目','timef'=>'0 秒']);} | |
| $items=[]; | |
| foreach($names as $n){ | |
| if(!fmValidName($n))continue; | |
| $tg=$cd==='.'?$n:$cd.'/'.$n; | |
| if(file_exists($tg))$items[]=['n'=>$n,'p'=>$tg]; | |
| } | |
| if(empty($items)){jsonResponse(['s'=>false,'msg'=>'没有可压缩的项目','timef'=>'0 秒']);} | |
| $zfnRaw=trim($_POST['zfn']??''); | |
| $customZip=$zfnRaw!==''?fmParseZipName($zfnRaw):null; | |
| if($zfnRaw!==''&&$customZip===false){jsonResponse(['s'=>false,'msg'=>'压缩包名称无效','timef'=>'0 秒']);} | |
| $zc=false;$zipName=''; | |
| if(count($items)===1&&is_dir($items[0]['p'])){ | |
| $fn=pathinfo($items[0]['p'],PATHINFO_FILENAME); | |
| $r=fmZipOneDir($cd,$items[0]['p'],$fn,$customZip); | |
| $zc=$r['zc'];$zipName=$r['n']; | |
| }else{ | |
| $zipName=$customZip?:((count($items)===1?pathinfo($items[0]['n'],PATHINFO_FILENAME):('selected_'.date('Ymd_His'))).'.zip'); | |
| $zf=$cd==='.'?$zipName:$cd.'/'.$zipName; | |
| if(ce('zip')){ | |
| $args=implode(' ',array_map('escapeshellarg',array_column($items,'n'))); | |
| exec("cd ".escapeshellarg($cd)." && zip -9 -r ".escapeshellarg($zipName).' '.$args." 2>&1",$o,$c); | |
| if($c===0&&file_exists($zf))$zc=true; | |
| } | |
| if(!$zc&&class_exists('ZipArchive')){ | |
| $z=new ZipArchive(); | |
| if($z->open($zf,ZipArchive::CREATE|ZipArchive::OVERWRITE)===true){ | |
| $ok=true; | |
| foreach($items as $it){if(!fmZipAddPath($z,$it['p'],$it['n']))$ok=false;} | |
| $z->close(); | |
| $zc=$ok&&file_exists($zf); | |
| } | |
| } | |
| } | |
| $elapsed=microtime(true)-$t0;$timef=fmtElapsed($elapsed); | |
| jsonResponse(['s'=>$zc,'msg'=>$zc?('压缩成功: '.$zipName.',耗时 '.$timef):'压缩失败','n'=>$zipName,'time'=>$elapsed,'timef'=>$timef]); | |
| } | |
| elseif($a==='ex'&&$_SESSION['li']){ | |
| session_write_close(); | |
| unlimitedRun();@ini_set('memory_limit','512M'); | |
| $t0=microtime(true); | |
| $fn=trim($_POST['n']??''); | |
| if(!fmValidName($fn)){jsonResponse(['s'=>false,'msg'=>'文件名无效','timef'=>'0 秒']);} | |
| $zf=$cd==='.'?$fn:$cd.'/'.$fn; | |
| $ed=$cd; | |
| $s=false;$e=''; | |
| $ext=strtolower(pathinfo($zf,PATHINFO_EXTENSION)); | |
| $bn=strtolower(basename($zf)); | |
| if($ext==='zip'){ | |
| $xr=fmExtractZip($zf,$ed); | |
| $s=$xr['s'];$e=$xr['e']; | |
| }elseif(substr($bn,-7)==='.tar.gz'||substr($bn,-4)==='.tgz'||substr($bn,-8)==='.tar.bz2'||substr($bn,-7)==='.tar.xz'||substr($bn,-5)==='.tbz2'||substr($bn,-4)==='.txz'||$ext==='tar'){ | |
| $tarOpt='-xf';if(substr($bn,-7)==='.tar.gz'||substr($bn,-4)==='.tgz')$tarOpt='-xzf';elseif(substr($bn,-8)==='.tar.bz2'||substr($bn,-5)==='.tbz2')$tarOpt='-xjf';elseif(substr($bn,-7)==='.tar.xz'||substr($bn,-4)==='.txz')$tarOpt='-xJf'; | |
| $edReal=realpath($ed)?:$ed; | |
| if(ce('tar')){exec("tar ".$tarOpt." ".escapeshellarg($zf)." -C ".escapeshellarg($edReal)." 2>&1",$o,$c);$s=$c===0;$e=fmCleanMsg(implode("\n",$o));} | |
| }elseif($ext==='gz'||$ext==='bz2'||$ext==='xz'){ | |
| $edReal=realpath($ed)?:$ed; | |
| if(ce('tar')){exec("tar -xf ".escapeshellarg($zf)." -C ".escapeshellarg($edReal)." 2>&1",$o,$c);$s=$c===0;$e=fmCleanMsg(implode("\n",$o));} | |
| if(!$s&&$ext==='gz'&&ce('gunzip')){exec("gunzip -c ".escapeshellarg($zf)." > ".escapeshellarg($edReal.'/'.pathinfo($zf,PATHINFO_FILENAME))." 2>&1",$o,$c);$s=$c===0;$e=fmCleanMsg(implode("\n",$o));} | |
| if(!$s&&$ext==='bz2'&&ce('bunzip2')){exec("bunzip2 -c ".escapeshellarg($zf)." > ".escapeshellarg($edReal.'/'.pathinfo($zf,PATHINFO_FILENAME))." 2>&1",$o,$c);$s=$c===0;$e=fmCleanMsg(implode("\n",$o));} | |
| if(!$s&&$ext==='xz'&&ce('xz')){exec("xz -dc ".escapeshellarg($zf)." > ".escapeshellarg($edReal.'/'.pathinfo($zf,PATHINFO_FILENAME))." 2>&1",$o,$c);$s=$c===0;$e=fmCleanMsg(implode("\n",$o));} | |
| }elseif($ext==='rar'){ | |
| $edReal=realpath($ed)?:$ed; | |
| if(ce('unrar')){exec("unrar x -o+ ".escapeshellarg($zf)." ".escapeshellarg($edReal)."/ 2>&1",$o,$c);$s=$c===0;$e=fmCleanMsg(implode("\n",$o));} | |
| }elseif($ext==='7z'){ | |
| $edReal=realpath($ed)?:$ed; | |
| if(ce('7z')||ce('7za')){$cmd=ce('7z')?'7z':'7za';exec($cmd." x -o".escapeshellarg($edReal)." ".escapeshellarg($zf)." 2>&1",$o,$c);$s=$c===0;$e=fmCleanMsg(implode("\n",$o));} | |
| } | |
| if(!$s&&!$e)$e='不支持该压缩格式('.$ext.')或解压工具未安装'; | |
| $elapsed=microtime(true)-$t0;$timef=fmtElapsed($elapsed); | |
| $disp=pathinfo($zf,PATHINFO_FILENAME); | |
| jsonResponse(['s'=>$s,'msg'=>$s?('解压成功: '.$disp.',耗时 '.$timef):('解压失败: '.fmCleanMsg($e).',耗时 '.$timef),'n'=>$disp,'time'=>$elapsed,'timef'=>$timef]); | |
| } | |
| elseif($a==='dlurl'&&$_SESSION['li']){ | |
| if(!function_exists('curl_init')){jsonResponse(['s'=>false,'msg'=>'下载失败: cURL扩展未安装','n'=>'']);} | |
| @ini_set('max_execution_time', 0);@set_time_limit(0);@ignore_user_abort(true);@ini_set('memory_limit', '512M'); | |
| $u=$_POST['u'];$fn=basename(parse_url($u,PHP_URL_PATH));if(!$fn||strpos($fn,'.')===false)$fn='downloaded_'.time().'.txt';$r=dlrf($u,$cd.'/'.$fn);jsonResponse(['s'=>$r['s'],'msg'=>$r['s']?"下载成功: $fn":"下载失败: ".$r['e'],'n'=>$fn]); | |
| } | |
| elseif($a==='ls'&&$_SESSION['li']){ | |
| $it=getFileList($cd);$pp=pp($cd); | |
| jsonResponse(['s'=>true,'items'=>$it,'dir'=>$cd,'parent'=>$pp,'breadcrumbs'=>array_map(function($p)use($cd){return htmlspecialchars($p);},explode('/',$cd))]); | |
| } | |
| elseif($a==='qnls'&&$_SESSION['li']){jsonResponse(['s'=>true,'items'=>refreshQnFromDisk()]);} | |
| elseif($a==='qnadd'&&$_SESSION['li']){ | |
| $l=trim($_POST['l']??'');$p=qnStorePath($_POST['p']??''); | |
| if($l===''||$p===false){jsonResponse(['s'=>false,'msg'=>'名称无效或文件不存在']);} | |
| $list=getQn($qn); | |
| foreach($list as $it){$ep=qnStorePath($it['p']??'');if($ep!==false&&$ep===$p){jsonResponse(['s'=>false,'msg'=>'该文件已在导航中']);}} | |
| $list[]=['l'=>$l,'p'=>$p]; | |
| if(!saveQn($qn,$list)){jsonResponse(['s'=>false,'msg'=>'保存到 ftp.php 失败,请检查 ftp.php 是否可写']);} | |
| jsonResponse(['s'=>true,'items'=>refreshQnFromDisk()]); | |
| } | |
| elseif($a==='qndel'&&$_SESSION['li']){ | |
| $list=getQn($qn); | |
| $ps=trim($_POST['p']??''); | |
| if($ps!==''){ | |
| $tp=qnStorePath($ps); | |
| if($tp===false){$tp=sp($ps);} | |
| $before=count($list); | |
| $list=array_values(array_filter($list,function($it)use($tp){ | |
| $ep=qnStorePath($it['p']??''); | |
| return ($ep===false?sp($it['p']??''):$ep)!==$tp; | |
| })); | |
| if(count($list)===$before){jsonResponse(['s'=>false,'msg'=>'导航项不存在']);} | |
| }else{ | |
| $i=(int)($_POST['i']??-1); | |
| if($i<0||$i>=count($list)){jsonResponse(['s'=>false,'msg'=>'无效的导航项']);} | |
| array_splice($list,$i,1); | |
| } | |
| if(!saveQn($qn,$list)){jsonResponse(['s'=>false,'msg'=>'保存到 ftp.php 失败,请检查 ftp.php 是否可写']);} | |
| jsonResponse(['s'=>true,'items'=>refreshQnFromDisk()]); | |
| } | |
| exit; | |
| } | |
| if(isset($_GET['dw'])&&$_SESSION['li']){ | |
| $cd=isset($_GET['dir'])?sp($_GET['dir']):'.'; | |
| $tg=$cd.'/'.$_GET['dw']; | |
| if(is_file($tg)){header('Content-Type: application/octet-stream');header('Content-Disposition: attachment; filename="'.basename($tg).'"');header('Content-Length: '.filesize($tg));readfile($tg);exit;} | |
| elseif(is_dir($tg)){unlimitedRun();@ini_set('memory_limit','512M');$fn=pathinfo($tg,PATHINFO_FILENAME);$zf=$cd.'/'.$fn.'.zip';$zc=false; | |
| if(ce('zip')){exec("cd ".escapeshellarg($cd)." && zip -9 -r ".escapeshellarg($zf)." ".escapeshellarg($fn)." 2>&1",$o,$c);if($c===0&&file_exists($zf))$zc=true;} | |
| if(!$zc&&class_exists('ZipArchive')){ | |
| $z=new ZipArchive();if($z->open($zf,ZipArchive::CREATE|ZipArchive::OVERWRITE)===true){ | |
| $realTg=str_replace('\\','/',realpath($tg)); | |
| foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tg),RecursiveIteratorIterator::SELF_FIRST)as $f){ | |
| $realPath=str_replace('\\','/',$f->getRealPath()); | |
| $relativePath=substr($realPath,strlen($realTg)+1); | |
| if($f->isDir()){ | |
| if($relativePath)$z->addEmptyDir($fn.'/'.$relativePath); | |
| }else{ | |
| $z->addFile($f->getPathname(),$fn.'/'.$relativePath); | |
| } | |
| } | |
| $z->close();$zc=true; | |
| } | |
| } | |
| if(!$zc){$sz=new SZ($tg,$fn);if($sz->c())$sz->sv($zf);$zc=true;} | |
| if($zc){header('Content-Type: application/zip');header('Content-Disposition: attachment; filename="'.$fn.'.zip"');header('Content-Length: '.filesize($zf));readfile($zf);unlink($zf);exit;} | |
| } | |
| } | |
| if(!isset($_SESSION['li'])){ | |
| <!DOCTYPE html> | |
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1.0"> | |
| <title> echo $n - 登录</title> | |
| <style> | |
| *,*::before,*::after{margin:0;padding:0;box-sizing:border-box} | |
| html{font-size:16px;-webkit-text-size-adjust:100%} | |
| @supports (-webkit-touch-callout:none){ | |
| input:not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=file]):not([type=button]):not([type=submit]):not([type=reset]), | |
| select,textarea{font-size:16px!important} | |
| } | |
| body{ | |
| font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif; | |
| background:linear-gradient(135deg,#faf5ff 0%,#f5f3ff 50%,#ede9fe 100%); | |
| min-height:100vh; | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| line-height:1.5; | |
| color:#1f2937; | |
| -webkit-font-smoothing:antialiased; | |
| } | |
| .svg-sprite{position:absolute;width:0;height:0;overflow:hidden} | |
| .login-container{ | |
| background:#ffffff; | |
| border-radius:16px; | |
| box-shadow:0 4px 6px -1px rgba(139,92,246,0.1),0 2px 4px -2px rgba(139,92,246,0.1),0 0 0 1px rgba(139,92,246,0.05); | |
| padding:48px 40px; | |
| width:100%; | |
| max-width:400px; | |
| margin:20px; | |
| } | |
| .login-header{ | |
| text-align:center; | |
| margin-bottom:32px; | |
| } | |
| .login-icon{ | |
| width:64px; | |
| height:64px; | |
| margin:0 auto 16px; | |
| background:linear-gradient(135deg,#8b5cf6,#7c3aed); | |
| border-radius:16px; | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| box-shadow:0 4px 12px rgba(139,92,246,0.3); | |
| } | |
| .login-icon svg{width:32px;height:32px;fill:#ffffff} | |
| .login-title{ | |
| font-size:24px; | |
| font-weight:600; | |
| color:#111827; | |
| margin-bottom:4px; | |
| } | |
| .login-subtitle{ | |
| font-size:14px; | |
| color:#6b7280; | |
| } | |
| .form-group{ | |
| margin-bottom:20px; | |
| } | |
| .form-group label{ | |
| display:block; | |
| font-size:14px; | |
| font-weight:500; | |
| color:#374151; | |
| margin-bottom:8px; | |
| } | |
| .form-group input{ | |
| width:100%; | |
| padding:12px 16px; | |
| font-size:16px; | |
| border:2px solid #e5e7eb; | |
| border-radius:10px; | |
| background:#fafafa; | |
| transition:all 0.2s ease; | |
| color:#1f2937; | |
| } | |
| .form-group input::placeholder{color:#9ca3af} | |
| .form-group input:hover{border-color:#d1d5db} | |
| .form-group input:focus{ | |
| outline:none; | |
| border-color:#8b5cf6; | |
| background:#ffffff; | |
| box-shadow:0 0 0 4px rgba(139,92,246,0.1); | |
| } | |
| .captcha-row{ | |
| display:flex; | |
| gap:12px; | |
| align-items:flex-end; | |
| } | |
| .captcha-row .form-group{flex:1;margin-bottom:0} | |
| .captcha-img{ | |
| width:120px; | |
| height:44px; | |
| border:2px solid #e5e7eb; | |
| border-radius:10px; | |
| cursor:pointer; | |
| transition:border-color 0.2s; | |
| object-fit:cover; | |
| } | |
| .captcha-img:hover{border-color:#8b5cf6} | |
| .btn-primary{ | |
| width:100%; | |
| padding:14px 24px; | |
| font-size:16px; | |
| font-weight:600; | |
| border:none; | |
| border-radius:10px; | |
| background:linear-gradient(135deg,#8b5cf6,#7c3aed); | |
| color:#ffffff; | |
| cursor:pointer; | |
| transition:all 0.2s ease; | |
| box-shadow:0 4px 6px -1px rgba(139,92,246,0.2),0 2px 4px -2px rgba(139,92,246,0.1); | |
| } | |
| .btn-primary:hover{ | |
| transform:translateY(-1px); | |
| box-shadow:0 6px 12px -2px rgba(139,92,246,0.25),0 4px 6px -3px rgba(139,92,246,0.15); | |
| } | |
| .btn-primary:active{transform:translateY(0)} | |
| .btn-primary:disabled{opacity:0.6;cursor:not-allowed;transform:none} | |
| @media(max-width:480px){ | |
| .login-container{padding:32px 24px;margin:16px} | |
| .login-icon{width:56px;height:56px} | |
| .login-icon svg{width:28px;height:28px} | |
| .login-title{font-size:20px} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <svg class="svg-sprite"> | |
| <symbol id="icon-folder" viewBox="0 0 24 24"><path d="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2z"/></symbol> | |
| </svg> | |
| <div class="login-container"> | |
| <div class="login-header"> | |
| <div class="login-icon"><svg><use href="#icon-folder"></use></svg></div> | |
| <h1 class="login-title">文件管理器</h1> | |
| <p class="login-subtitle">请输入密码以继续</p> | |
| </div> | |
| <form id="loginForm" method="POST" onsubmit="return false"> | |
| <input type="hidden" name="a" value="lg"> | |
| <div class="form-group"> | |
| <label for="pw">访问密码</label> | |
| <input type="password" name="pw" id="pw" required autofocus placeholder="输入密码"> | |
| </div> | |
| <div class="form-group"> | |
| <label for="cp">验证码 <a href="javascript:refreshCaptcha()" style="font-size:13px;color:#8b5cf6;text-decoration:none;margin-left:8px;float:right">[刷新]</a></label> | |
| <div class="captcha-row"> | |
| <div class="form-group"> | |
| <input type="text" name="cp" id="cp" required placeholder="请输入验证码" style="ime-mode:disabled"> | |
| </div> | |
| <img id="captchaImg" class="captcha-img" src="?a=captcha" onclick="this.src='?a=captcha&t='+Date.now()" title="点击刷新验证码"> | |
| </div> | |
| </div> | |
| <button type="submit" class="btn-primary" id="loginBtn">进入系统</button> | |
| </form> | |
| </div> | |
| <script> | |
| function refreshCaptcha(){document.getElementById('captchaImg').src='?a=captcha&t='+Date.now();} | |
| document.getElementById("loginForm").addEventListener("submit", async function(e) { | |
| e.preventDefault(); | |
| var btn = document.getElementById("loginBtn"); | |
| var pw = document.getElementById("pw"); | |
| var cp = document.getElementById("cp"); | |
| btn.disabled = true; | |
| btn.textContent = "登录中..."; | |
| var fd = new FormData(); | |
| fd.append("a", "lg"); | |
| fd.append("pw", pw.value); | |
| fd.append("cp", cp.value); | |
| try { | |
| var r = await fetch("", {method: "POST", body: fd}); | |
| var d = await r.json(); | |
| if (d.s) { | |
| location.reload(); | |
| } else { | |
| if(d.c){alert("验证码错误");refreshCaptcha();cp.value='';cp.focus();} | |
| else{alert("密码错误");pw.value='';pw.focus();} | |
| } | |
| } catch (x) { | |
| alert("网络错误"); | |
| } | |
| btn.disabled = false; | |
| btn.textContent = "进入系统"; | |
| }); | |
| </script> | |
| </body> | |
| </html> exit;} | |
| $cd=isset($_GET['dir'])?sp($_GET['dir']):'.'; | |
| $cd=$cd==='.'||empty($cd)?'.':$cd; | |
| $it=getFileList($cd); | |
| $pp=pp($cd); | |
| $ef=isset($_GET['edit'])?sp($_GET['edit']):null; | |
| if($ef&&file_exists($ef)&&!is_dir($ef)){$fc=file_get_contents($ef); | |
| <!DOCTYPE html> | |
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1.0"> | |
| <title>编辑 - echo $n</title> | |
| <script> | |
| var EDIT_FILE_PATH = echo json_encode($ef); ; | |
| var EDIT_FILE_NAME = echo json_encode(basename($ef)); ; | |
| var EDIT_DIR = echo json_encode($cd); ; | |
| var EDIT_FILE_CONTENT = echo json_encode($fc, JSON_HEX_TAG | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE); ; | |
| var quickNav = echo json_encode(getQn($qn), JSON_UNESCAPED_UNICODE); ; | |
| document.title = '编辑 ' + EDIT_FILE_NAME + ' - 文件管理器'; | |
| </script> | |
| <style> | |
| *,*::before,*::after{margin:0;padding:0;box-sizing:border-box} | |
| html{font-size:16px;-webkit-text-size-adjust:100%;height:100%} | |
| @supports (-webkit-touch-callout:none){ | |
| input:not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=file]):not([type=button]):not([type=submit]):not([type=reset]), | |
| select,textarea{font-size:16px!important} | |
| } | |
| :root{ | |
| --primary:#8b5cf6; | |
| --primary-hover:#7c3aed; | |
| --bg:#fafafa; | |
| --bg-white:#ffffff; | |
| --text:#1f2937; | |
| --text-secondary:#6b7280; | |
| --border:#e5e7eb; | |
| --shadow:0 1px 3px rgba(0,0,0,0.06),0 1px 2px -1px rgba(0,0,0,0.06); | |
| --shadow-lg:0 4px 6px -1px rgba(139,92,246,0.1),0 2px 4px -2px rgba(139,92,246,0.1); | |
| --radius:12px; | |
| --radius-sm:8px; | |
| } | |
| body{ | |
| font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif; | |
| background:#272822; | |
| color:var(--text); | |
| line-height:1.5; | |
| height:100vh; | |
| overflow:hidden; | |
| display:flex; | |
| flex-direction:column; | |
| -webkit-font-smoothing:antialiased; | |
| } | |
| .svg-sprite{position:absolute;width:0;height:0;overflow:hidden} | |
| .header{ | |
| background:var(--bg-white); | |
| border-bottom:1px solid var(--border); | |
| padding:16px 24px; | |
| display:flex; | |
| align-items:center; | |
| justify-content:space-between; | |
| flex-shrink:0; | |
| z-index:100; | |
| box-shadow:var(--shadow); | |
| } | |
| .header-left{ | |
| display:flex; | |
| align-items:center; | |
| gap:16px; | |
| } | |
| .back-btn{ | |
| display:flex; | |
| align-items:center; | |
| gap:8px; | |
| padding:10px 16px; | |
| font-size:14px; | |
| font-weight:500; | |
| color:var(--text); | |
| background:var(--bg); | |
| border:1px solid var(--border); | |
| border-radius:var(--radius-sm); | |
| cursor:pointer; | |
| transition:all 0.2s ease; | |
| } | |
| .back-btn:hover{ | |
| background:var(--bg-white); | |
| border-color:var(--primary); | |
| color:var(--primary); | |
| } | |
| .file-title{ | |
| font-size:18px; | |
| font-weight:600; | |
| color:var(--text); | |
| } | |
| .header-right{ | |
| display:flex; | |
| align-items:center; | |
| gap:12px; | |
| } | |
| .btn{ | |
| display:inline-flex; | |
| align-items:center; | |
| justify-content:center; | |
| gap:8px; | |
| padding:10px 20px; | |
| font-size:14px; | |
| font-weight:500; | |
| border:none; | |
| border-radius:var(--radius-sm); | |
| cursor:pointer; | |
| transition:all 0.2s ease; | |
| } | |
| .btn-secondary{ | |
| background:var(--bg); | |
| color:var(--text); | |
| border:1px solid var(--border); | |
| } | |
| .btn-secondary:hover{ | |
| background:var(--bg-white); | |
| border-color:#d1d5db; | |
| } | |
| .btn-primary{ | |
| background:linear-gradient(135deg,var(--primary),var(--primary-hover)); | |
| color:#ffffff; | |
| box-shadow:0 2px 4px rgba(139,92,246,0.2); | |
| } | |
| .btn-primary:hover{ | |
| transform:translateY(-1px); | |
| box-shadow:0 4px 8px rgba(139,92,246,0.25); | |
| } | |
| .btn-primary:active{transform:translateY(0)} | |
| .btn-primary:disabled{opacity:0.6;cursor:not-allowed;transform:none;box-shadow:none} | |
| .icon{ | |
| width:18px; | |
| height:18px; | |
| display:inline-flex; | |
| align-items:center; | |
| justify-content:center; | |
| flex-shrink:0; | |
| } | |
| .icon svg{width:100%;height:100%;fill:currentColor} | |
| .editor-container{ | |
| flex:1; | |
| min-height:0; | |
| display:flex; | |
| flex-direction:row; | |
| max-width:none; | |
| margin:0; | |
| padding:0; | |
| overflow:hidden; | |
| } | |
| .se-drawer-backdrop{display:none} | |
| .se-sidebar{ | |
| width:252px; | |
| flex-shrink:0; | |
| background:#1e1f1c; | |
| border-right:1px solid #3e3d32; | |
| display:flex; | |
| flex-direction:column; | |
| min-height:0; | |
| position:relative; | |
| z-index:5; | |
| transition:width .2s ease,margin .2s ease,transform .25s ease; | |
| } | |
| .se-sidebar.collapsed{width:0;min-width:0;overflow:hidden;border-right:none} | |
| .se-drawer-btn{display:inline-flex} | |
| .se-drawer-close{display:inline-flex;align-items:center;justify-content:center} | |
| .se-tree-head{ | |
| display:flex; | |
| flex-direction:column; | |
| align-items:stretch; | |
| gap:0; | |
| padding:0; | |
| background:#2a2b26; | |
| border-bottom:1px solid #3e3d32; | |
| flex-shrink:0; | |
| } | |
| .se-tree-path-row{ | |
| display:flex; | |
| align-items:center; | |
| gap:2px; | |
| padding:8px 10px 7px; | |
| font-size:12px; | |
| line-height:1.45; | |
| border-bottom:1px solid #3e3d32; | |
| min-width:0; | |
| } | |
| .se-drawer-close{ | |
| margin-left:auto; | |
| flex-shrink:0; | |
| width:26px; | |
| height:26px; | |
| border:none; | |
| border-radius:4px; | |
| background:transparent; | |
| color:#90908a; | |
| font-size:18px; | |
| line-height:1; | |
| cursor:pointer; | |
| font-family:inherit; | |
| } | |
| .se-drawer-close:hover{background:#3e3d32;color:#f8f8f2} | |
| .se-tree-path-label{color:#90908a;flex-shrink:0;font-size:12px} | |
| .se-tree-dir{ | |
| flex:1; | |
| overflow:hidden; | |
| text-overflow:ellipsis; | |
| white-space:nowrap; | |
| font-size:12px; | |
| color:#f8f8f2; | |
| font-family:'Consolas','Monaco','Courier New',monospace; | |
| } | |
| .se-tree-actions{ | |
| display:grid; | |
| grid-template-columns:repeat(4,1fr); | |
| align-items:center; | |
| padding:6px 8px; | |
| gap:4px; | |
| } | |
| .se-tree-act{ | |
| display:inline-flex; | |
| flex-direction:row; | |
| align-items:center; | |
| justify-content:center; | |
| gap:3px; | |
| padding:6px 2px; | |
| margin:0; | |
| border:none; | |
| background:transparent; | |
| color:#e8e8e2; | |
| cursor:pointer; | |
| font-size:11px; | |
| line-height:1.2; | |
| border-radius:4px; | |
| min-width:0; | |
| font-family:inherit; | |
| white-space:nowrap; | |
| width:100%; | |
| box-sizing:border-box; | |
| } | |
| .se-tree-act:hover:not(:disabled){background:#3e3d32;color:#fff} | |
| .se-tree-act:disabled{opacity:0.35;cursor:not-allowed} | |
| .se-tree-act.on{background:rgba(102,217,239,0.12);color:#66d9ef} | |
| .se-tree-act-lbl{flex-shrink:1;min-width:0} | |
| .se-tree-act-ico{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;flex-shrink:0;opacity:0.92} | |
| .se-tree-act-ico svg{width:15px;height:15px;fill:currentColor;display:block} | |
| .se-tree-toggle{display:none!important} | |
| .se-tree-search-box{display:none;padding:0 8px 8px} | |
| .se-tree-search-box.open{display:block} | |
| .se-tree-search-box input{ | |
| width:100%; | |
| box-sizing:border-box; | |
| padding:6px 8px; | |
| font-size:12px; | |
| border:1px solid #3e3d32; | |
| border-radius:4px; | |
| background:#1e1f1c; | |
| color:#f8f8f2; | |
| font-family:inherit; | |
| } | |
| .se-tree-search-box input:focus{outline:none;border-color:#66d9ef} | |
| .se-tree-list{ | |
| flex:1; | |
| overflow:auto; | |
| padding:4px 0; | |
| -webkit-overflow-scrolling:touch; | |
| } | |
| .se-tree-item{ | |
| display:flex; | |
| align-items:center; | |
| gap:6px; | |
| padding:5px 10px 5px 12px; | |
| font-size:12px; | |
| color:#c5c5b8; | |
| cursor:pointer; | |
| user-select:none; | |
| line-height:1.4; | |
| } | |
| .se-tree-item:hover{background:#3e3d32;color:#f8f8f2} | |
| .se-tree-item.dir{color:#e6db74} | |
| .se-tree-item.dir .se-tree-ico{color:#e6db74} | |
| .se-tree-item.file.open{color:#e6db74} | |
| .se-tree-item.file.cur{background:rgba(102,217,239,0.12);color:#66d9ef} | |
| .se-tree-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1} | |
| .se-tree-badge{font-size:10px;color:#75715e;flex-shrink:0} | |
| .se-tree-item.file.open .se-tree-badge{color:#e6db74} | |
| .se-ctx-menu{ | |
| position:fixed; | |
| z-index:10050; | |
| min-width:148px; | |
| padding:4px 0; | |
| background:#272822; | |
| border:1px solid #3e3d32; | |
| border-radius:6px; | |
| box-shadow:0 8px 24px rgba(0,0,0,0.45); | |
| display:none; | |
| } | |
| .se-ctx-menu.open{display:block} | |
| .se-ctx-item{ | |
| display:block; | |
| width:100%; | |
| padding:7px 14px; | |
| font-size:12px; | |
| color:#c5c5b8; | |
| background:transparent; | |
| border:none; | |
| text-align:left; | |
| cursor:pointer; | |
| font-family:inherit; | |
| white-space:nowrap; | |
| } | |
| .se-ctx-item:hover{background:#3e3d32;color:#f8f8f2} | |
| .se-ctx-item.danger:hover{background:rgba(249,38,114,0.15);color:#f92672} | |
| .se-ctx-sep{height:1px;background:#3e3d32;margin:4px 0} | |
| .se-mv-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.55);z-index:10060;display:none;align-items:center;justify-content:center;padding:16px} | |
| .se-mv-overlay.open{display:flex} | |
| .se-mv-box{width:100%;max-width:400px;max-height:min(520px,85vh);background:#272822;border:1px solid #3e3d32;border-radius:10px;box-shadow:0 12px 40px rgba(0,0,0,0.5);display:flex;flex-direction:column;overflow:hidden} | |
| .se-mv-head{padding:12px 14px;font-size:14px;font-weight:600;color:#f8f8f2;border-bottom:1px solid #3e3d32} | |
| .se-mv-info{padding:8px 14px;font-size:12px;color:#c5c5b8;line-height:1.5} | |
| .se-mv-info em{font-style:normal;color:#66d9ef} | |
| .se-mv-dest{padding:0 14px 8px;font-size:11px;color:#90908a;font-family:'Consolas','Monaco','Courier New',monospace;word-break:break-all} | |
| .se-mv-dest strong{color:#a6e22e;font-weight:600} | |
| .se-mv-nav{display:flex;align-items:center;gap:6px;padding:6px 10px;border-bottom:1px solid #3e3d32;flex-shrink:0} | |
| .se-mv-up{padding:2px 8px;border:1px solid #3e3d32;border-radius:4px;background:#1e1f1c;color:#c5c5b8;cursor:pointer;font-size:12px} | |
| .se-mv-up:hover:not(:disabled){border-color:#66d9ef;color:#66d9ef} | |
| .se-mv-up:disabled{opacity:0.35;cursor:not-allowed} | |
| .se-mv-cur{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;color:#90908a;font-family:'Consolas','Monaco','Courier New',monospace} | |
| .se-mv-list{flex:1;overflow:auto;padding:4px 0;min-height:120px;max-height:280px;-webkit-overflow-scrolling:touch} | |
| .se-mv-item{display:flex;align-items:center;gap:8px;padding:7px 14px;font-size:12px;color:#e6db74;cursor:pointer;user-select:none} | |
| .se-mv-item:hover{background:#3e3d32;color:#f8f8f2} | |
| .se-mv-item.disabled{opacity:0.35;cursor:not-allowed;pointer-events:none} | |
| .se-mv-empty{padding:16px;text-align:center;font-size:12px;color:#75715e} | |
| .se-mv-foot{display:flex;justify-content:flex-end;gap:8px;padding:10px 14px;border-top:1px solid #3e3d32;flex-shrink:0} | |
| .se-mv-foot button{padding:6px 14px;font-size:12px;border-radius:6px;cursor:pointer;font-family:inherit;border:1px solid #3e3d32;background:#1e1f1c;color:#c5c5b8} | |
| .se-mv-foot button:hover{background:#3e3d32;color:#f8f8f2} | |
| .se-mv-foot .se-mv-ok{border-color:#66d9ef;color:#272822;background:#66d9ef;font-weight:600} | |
| .se-mv-foot .se-mv-ok:hover{background:#a6e22e;border-color:#a6e22e;color:#272822} | |
| .se-mv-foot .se-mv-ok:disabled{opacity:0.4;cursor:not-allowed} | |
| .editor-main{ | |
| flex:1; | |
| min-width:0; | |
| min-height:0; | |
| display:flex; | |
| flex-direction:column; | |
| } | |
| .editor-wrapper{ | |
| flex:1; | |
| min-height:0; | |
| display:flex; | |
| flex-direction:column; | |
| background:#272822; | |
| border-radius:0; | |
| box-shadow:none; | |
| border:none; | |
| overflow:hidden; | |
| } | |
| .se-tabs-wrap{background:#1e1f1c;border-bottom:1px solid #3e3d32;display:flex;align-items:stretch;min-height:36px} | |
| .se-tabs-bar{display:flex;align-items:stretch;gap:2px;padding:4px 8px 0;overflow-x:auto;flex:1;min-width:0;scrollbar-width:thin} | |
| .se-tabs-bar::-webkit-scrollbar{height:4px} | |
| .se-tab{display:inline-flex;align-items:center;gap:6px;max-width:200px;padding:6px 8px 6px 12px;font-size:12px;color:#c5c5b8;background:#272822;border:1px solid #3e3d32;border-bottom:none;border-radius:6px 6px 0 0;cursor:pointer;flex-shrink:0;user-select:none;transition:background .15s,color .15s} | |
| .se-tab:hover{background:#3e3d32;color:#f8f8f2} | |
| .se-tab.active{background:#272822;color:#66d9ef;border-color:#66d9ef;border-bottom-color:#272822;margin-bottom:-1px;z-index:1} | |
| .se-tab-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap} | |
| .se-tab-dot{width:6px;height:6px;border-radius:50%;background:#f92672;flex-shrink:0;display:none} | |
| .se-tab.dirty .se-tab-dot{display:block} | |
| .se-tab-x{width:18px;height:18px;border:none;background:transparent;color:#75715e;cursor:pointer;border-radius:3px;font-size:14px;line-height:1;padding:0;flex-shrink:0;display:flex;align-items:center;justify-content:center} | |
| .se-tab-x:hover{background:rgba(239,68,68,0.2);color:#f92672} | |
| .editor-host{position:relative;background:#272822;flex:1;min-height:0;display:flex;flex-direction:column} | |
| .se-wrap{display:flex;flex:1;min-height:0;height:auto;font-family:'Consolas','Monaco','Courier New',monospace;font-size:14px;line-height:1.5} | |
| .se-gutter{flex:0 0 50px;background:#272822;border-right:1px solid #3e3d32;color:#75715e;text-align:right;padding:12px 6px;overflow:hidden;user-select:none} | |
| .se-gutter div{height:21px;line-height:21px;padding-right:4px} | |
| .se-gutter div.cur{color:#f8f8f2;background:rgba(255,255,255,0.06)} | |
| .se-body{flex:1;min-height:0;position:relative;overflow:hidden;background:#272822} | |
| .se-scroll{position:relative;width:100%;height:100%;min-height:0} | |
| .se-hl,.se-ta{margin:0;padding:12px;border:0;width:100%;font:inherit;font-size:14px;line-height:21px;tab-size:4;-moz-tab-size:4;white-space:pre;word-wrap:normal;overflow-wrap:normal;letter-spacing:normal} | |
| .se-hl{position:absolute;top:0;left:0;min-width:100%;width:max-content;min-height:100%;color:#f8f8f2;pointer-events:none;z-index:1;overflow:visible} | |
| .se-ta{position:absolute;top:0;left:0;right:0;bottom:0;z-index:2;resize:none;outline:none;background:transparent;color:transparent;caret-color:#f8f8f2;-webkit-text-fill-color:transparent;overflow:auto;overflow-x:auto;overflow-y:auto;scrollbar-width:thin;scrollbar-color:#49483e #1e1f1c} | |
| .se-ta::-webkit-scrollbar{width:10px;height:10px} | |
| .se-ta::-webkit-scrollbar-thumb{background:#49483e;border-radius:5px} | |
| .se-ta::-webkit-scrollbar-thumb:hover{background:#75715e} | |
| .se-ta::-webkit-scrollbar-track{background:#1e1f1c} | |
| .se-ta::selection{background:rgba(102,217,239,0.28)} | |
| .se-hl .kw{color:#f92672}.se-hl .fn{color:#a6e22e}.se-hl .str{color:#e6db74}.se-hl .cm{color:#75715e;font-style:italic}.se-hl .num{color:#ae81ff}.se-hl .tag{color:#f92672}.se-hl .attr{color:#a6e22e}.se-hl .op{color:#f92672} | |
| .editor-toolbar{display:flex;align-items:center;padding:8px 12px;background:#1e1f1c;border-bottom:1px solid #3e3d32;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none} | |
| .editor-toolbar::-webkit-scrollbar{display:none} | |
| .editor-toolbar .et-tools{display:flex;align-items:center;gap:6px;flex-wrap:nowrap} | |
| .editor-toolbar .et-tools>.et-btn,.editor-toolbar .et-tools>.et-sep,.editor-toolbar .et-tools>.qn-wrap{flex-shrink:0} | |
| .editor-toolbar .qn-wrap{display:inline-flex;align-items:center} | |
| .editor-toolbar .qn-wrap.open{z-index:10070} | |
| .editor-toolbar .qn-menu{display:none;z-index:10070;min-width:260px;max-width:min(360px,calc(100vw - 20px));background:#272822;border:1px solid #3e3d32;border-radius:6px;box-shadow:0 10px 28px rgba(0,0,0,0.45);padding:4px 0;max-height:min(70vh,calc(100vh - 80px));overflow:auto;-webkit-overflow-scrolling:touch} | |
| .editor-toolbar .qn-wrap.open .qn-menu{display:block} | |
| .editor-toolbar .qn-item{padding:8px 12px;font-size:12px;color:#f8f8f2} | |
| .editor-toolbar .qn-item:hover{background:#3e3d32;color:#f8f8f2} | |
| .editor-toolbar .qn-item.cur{background:rgba(102,217,239,0.1);color:#66d9ef} | |
| .editor-toolbar .qn-rm{color:#75715e} | |
| .editor-toolbar .qn-rm:hover{background:rgba(249,38,114,0.15);color:#f92672} | |
| .editor-toolbar .qn-empty{color:#75715e;padding:10px 12px;font-size:12px} | |
| .editor-toolbar .qn-sep{background:#3e3d32;margin:4px 0} | |
| .editor-toolbar .qn-add-cur{padding:8px 12px;font-size:12px;color:#a6e22e} | |
| .editor-toolbar .qn-add-cur:hover{background:#3e3d32;color:#f8f8f2} | |
| .editor-toolbar .qn-form{padding:8px 12px 10px} | |
| .editor-toolbar .qn-form input{background:#1e1f1c;border-color:#3e3d32;color:#f8f8f2;font-size:12px} | |
| .editor-toolbar .qn-form input:focus{border-color:#66d9ef} | |
| .editor-toolbar .qn-form button{background:#49483e;font-size:12px} | |
| .editor-toolbar .qn-form button:hover{background:#75715e} | |
| .editor-toolbar .qn-btn{min-width:0;padding:4px 10px;font-size:12px;white-space:nowrap} | |
| .editor-toolbar .qn-label-short{display:none} | |
| .et-btn{padding:4px 10px;font-size:12px;border:1px solid #3e3d32;border-radius:4px;background:#272822;color:#f8f8f2;cursor:pointer;font-family:inherit} | |
| .et-btn:hover{background:#3e3d32} | |
| .et-btn:disabled{opacity:0.35;cursor:not-allowed} | |
| .et-btn-save{border-color:#66d9ef;color:#66d9ef} | |
| .et-btn-save:hover{background:#3e3d32;color:#a6e22e;border-color:#a6e22e} | |
| .et-sep{width:1px;height:18px;background:#3e3d32;margin:0 2px} | |
| .et-sep-drawer{display:inline-block} | |
| .se-chmod-body{padding:0 14px 12px;font-size:12px;color:#c5c5b8} | |
| .se-chmod-cur{margin-bottom:12px;padding:8px 10px;background:#1e1f1c;border:1px solid #3e3d32;border-radius:4px;font-family:'Consolas','Monaco','Courier New',monospace;line-height:1.5} | |
| .se-chmod-cur .sym{color:#75715e;margin-left:8px} | |
| .se-chmod-mode{width:100%;padding:8px 10px;border:1px solid #3e3d32;border-radius:4px;background:#1e1f1c;color:#f8f8f2;font-size:12px;font-family:'Consolas','Monaco','Courier New',monospace;box-sizing:border-box} | |
| .se-chmod-mode:focus{outline:none;border-color:#66d9ef} | |
| .se-chmod-presets{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px} | |
| .se-chmod-presets button{padding:4px 10px;font-size:11px;border:1px solid #3e3d32;border-radius:4px;background:#272822;color:#c5c5b8;cursor:pointer;font-family:inherit} | |
| .se-chmod-presets button:hover{border-color:#66d9ef;color:#66d9ef} | |
| .se-chmod-rec{margin-top:10px;display:flex;align-items:center;gap:6px;font-size:12px;color:#90908a;cursor:pointer} | |
| .se-chmod-rec input{width:14px;height:14px;margin:0} | |
| .se-findbar{display:none;align-items:center;gap:6px;padding:8px 12px;background:#2f303a;border-bottom:1px solid #3e3d32;flex-wrap:wrap} | |
| .se-findbar.open{display:flex} | |
| .se-findbar input{padding:5px 8px;font-size:12px;border:1px solid #3e3d32;border-radius:4px;background:#272822;color:#f8f8f2;min-width:120px;font-family:'Consolas','Monaco','Courier New',monospace} | |
| .se-findbar input:focus{outline:none;border-color:#66d9ef} | |
| #seReplace,#seReplaceBtn,#seReplaceAllBtn{display:none} | |
| .se-findbar.rep #seReplace,.se-findbar.rep #seReplaceBtn,.se-findbar.rep #seReplaceAllBtn{display:inline-block} | |
| .se-findbar button{padding:4px 10px;font-size:12px;border:1px solid #3e3d32;border-radius:4px;background:#272822;color:#f8f8f2;cursor:pointer;font-family:inherit} | |
| .se-findbar button:hover{background:#3e3d32} | |
| .se-find-stat{font-size:12px;color:#90908a;min-width:60px} | |
| .se-find-close{margin-left:auto;padding:2px 8px;font-size:16px;line-height:1} | |
| .status-bar{ | |
| padding:8px 16px; | |
| background:#1e1f1c; | |
| border-top:1px solid #3e3d32; | |
| display:flex; | |
| justify-content:space-between; | |
| align-items:center; | |
| font-size:12px; | |
| color:#90908a; | |
| font-weight:500; | |
| font-family:'Consolas','Monaco','Courier New',monospace; | |
| flex-shrink:0; | |
| } | |
| .status-bar span{ | |
| display:flex; | |
| align-items:center; | |
| gap:8px; | |
| } | |
| .status-bar span::before{ | |
| content:''; | |
| width:6px; | |
| height:6px; | |
| background:#a6e22e; | |
| border-radius:50%; | |
| } | |
| @media(max-width:768px){ | |
| body{height:100vh;min-height:100vh;overflow:hidden;background:#272822} | |
| .header{padding:12px 16px;flex-wrap:wrap;gap:12px} | |
| .header-left{order:1} | |
| .header-right{order:2} | |
| .editor-container{padding:0;flex-direction:row;position:relative;min-height:0} | |
| .se-drawer-backdrop{display:block;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:10030;opacity:0;pointer-events:none;transition:opacity .25s ease} | |
| .se-drawer-backdrop.show{opacity:1;pointer-events:auto} | |
| .se-sidebar{ | |
| position:fixed; | |
| left:0;top:0;bottom:0; | |
| width:min(86vw,300px); | |
| max-width:300px; | |
| max-height:none; | |
| min-height:0; | |
| z-index:10040; | |
| transform:translateX(-105%); | |
| border-right:1px solid #3e3d32; | |
| border-bottom:none; | |
| box-shadow:none; | |
| } | |
| .se-sidebar.drawer-open{transform:translateX(0);box-shadow:6px 0 28px rgba(0,0,0,0.45)} | |
| .se-sidebar.collapsed{width:min(86vw,300px);min-width:0;transform:translateX(-105%);overflow:visible;border-right:1px solid #3e3d32} | |
| .se-sidebar:not(.drawer-open):not(.collapsed){transform:translateX(-105%)} | |
| .se-drawer-close{display:inline-flex;align-items:center;justify-content:center} | |
| .se-drawer-btn{display:inline-flex} | |
| .se-drawer-btn.on{color:#66d9ef;border-color:#66d9ef} | |
| .editor-main{flex:1;min-width:0;min-height:0;width:100%;display:flex;flex-direction:column} | |
| .editor-wrapper{flex:1;min-height:0;display:flex;flex-direction:column} | |
| .editor-wrapper{border-radius:0;border:none;box-shadow:none} | |
| .btn{padding:8px 16px;font-size:13px} | |
| .editor-host{flex:1;min-height:0} | |
| .editor-host .se-wrap{height:auto;flex:1;min-height:0} | |
| .se-tabs-bar{padding:4px 4px 0} | |
| .se-tab{max-width:140px;padding:6px 6px 6px 10px} | |
| .se-findbar input{min-width:0;flex:1} | |
| .se-hl,.se-ta{font-size:13px;line-height:20px} | |
| .se-gutter div{height:20px;line-height:20px} | |
| .editor-toolbar{padding:6px 10px} | |
| .editor-toolbar .et-tools{gap:4px} | |
| .editor-toolbar .et-btn{padding:4px 8px;font-size:11px} | |
| .editor-toolbar .qn-label-full{display:none} | |
| .editor-toolbar .qn-label-short{display:inline} | |
| .se-tree-actions{padding:5px 6px;gap:3px} | |
| .se-tree-act{padding:5px 1px;font-size:10px;gap:2px} | |
| .se-tree-act-ico{width:14px;height:14px} | |
| .se-tree-act-ico svg{width:14px;height:14px} | |
| } | |
| .qn-wrap{position:relative;display:inline-flex} | |
| .qn-btn{min-width:0} | |
| .qn-menu{position:absolute;top:calc(100% + 6px);right:0;min-width:280px;max-width:360px;background:var(--bg-white);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-lg);padding:6px 0;display:none;z-index:200;max-height:70vh;overflow:auto} | |
| .qn-wrap.open .qn-menu{display:block} | |
| .qn-item{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;color:var(--text);font-size:14px;text-decoration:none;transition:background .15s} | |
| .qn-item-row span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1} | |
| .qn-item:hover{background:var(--primary-light);color:var(--primary)} | |
| .qn-item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1} | |
| .qn-rm{width:22px;height:22px;border:none;background:transparent;color:var(--text-secondary);cursor:pointer;border-radius:4px;font-size:16px;line-height:1;flex-shrink:0} | |
| .qn-rm:hover{background:rgba(239,68,68,0.12);color:#dc2626} | |
| .qn-empty{padding:14px;font-size:13px;color:var(--text-secondary);text-align:center} | |
| .qn-sep{height:1px;background:var(--border);margin:6px 0} | |
| .qn-add-cur{display:block;width:100%;padding:10px 14px;border:none;background:transparent;text-align:left;font-size:13px;color:var(--primary);cursor:pointer} | |
| .qn-add-cur:hover{background:var(--primary-light)} | |
| .qn-form{padding:10px 14px 12px;display:flex;flex-direction:column;gap:8px} | |
| .qn-form input{padding:8px 10px;font-size:13px;border:1px solid var(--border);border-radius:6px;background:var(--bg)} | |
| .qn-form input:focus{outline:none;border-color:var(--primary)} | |
| .qn-form button{padding:8px;font-size:13px;border:none;border-radius:6px;background:var(--primary);color:#fff;cursor:pointer} | |
| .qn-form button:hover{background:var(--primary-hover)} | |
| @media(max-width:768px){ | |
| .editor-toolbar .qn-wrap.open .qn-menu{position:fixed!important;left:10px!important;right:10px!important;width:auto!important;min-width:0!important;max-width:none!important;z-index:10070!important;max-height:min(75vh,calc(100vh - 72px));-webkit-overflow-scrolling:touch;box-shadow:0 12px 32px rgba(0,0,0,0.5)} | |
| .qn-wrap.open .qn-menu{position:fixed;left:12px;right:12px;min-width:0;max-width:none;z-index:10070;max-height:min(75vh,calc(100vh - 72px));-webkit-overflow-scrolling:touch} | |
| .qn-btn{min-width:0;font-size:13px;padding:8px 12px} | |
| .qn-form input,.qn-form button{width:100%;box-sizing:border-box} | |
| .qn-form button{padding:10px;white-space:nowrap} | |
| .header-right .qn-wrap{flex:1;min-width:0} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <svg class="svg-sprite"> | |
| <symbol id="icon-refresh" viewBox="0 0 24 24"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></symbol> | |
| <symbol id="icon-save" viewBox="0 0 24 24"><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/></symbol> | |
| <symbol id="icon-up" viewBox="0 0 24 24"><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.59 5.58L20 12l-8-8-8 8z"/></symbol> | |
| <symbol id="icon-add" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></symbol> | |
| <symbol id="icon-search" viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C8.01 14 6 11.99 6 9.5S8.01 5 10.5 5 15 7.01 15 9.5 12.99 14 10.5 14z"/></symbol> | |
| </svg> | |
| <div class="editor-container" id="editorContainer"> | |
| <div class="se-drawer-backdrop" id="seDrawerBackdrop" onclick="seToggleTree(false)"></div> | |
| <aside class="se-sidebar" id="seSidebar"> | |
| <div class="se-tree-head"> | |
| <div class="se-tree-path-row"> | |
| <span class="se-tree-path-label">目录:</span> | |
| <span class="se-tree-dir" id="seTreeDir" title="">/</span> | |
| <button type="button" class="se-drawer-close" onclick="seToggleTree(false)" title="收起">×</button> | |
| </div> | |
| <div class="se-tree-actions"> | |
| <button type="button" class="se-tree-act" id="seTreeUpBtn" onclick="seTreeUp()" title="上一级" disabled><span class="se-tree-act-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#icon-up"/></svg></span><span class="se-tree-act-lbl">上级</span></button> | |
| <button type="button" class="se-tree-act" onclick="seTreeRefresh()" title="刷新"><span class="se-tree-act-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#icon-refresh"/></svg></span><span class="se-tree-act-lbl">刷新</span></button> | |
| <button type="button" class="se-tree-act" id="seTreeNewBtn" onclick="seTreeToolbarNew(event)" title="新建"><span class="se-tree-act-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#icon-add"/></svg></span><span class="se-tree-act-lbl">新建</span></button> | |
| <button type="button" class="se-tree-act" id="seTreeSearchBtn" onclick="seTreeToggleSearch()" title="搜索"><span class="se-tree-act-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#icon-search"/></svg></span><span class="se-tree-act-lbl">搜索</span></button> | |
| </div> | |
| <div class="se-tree-search-box" id="seTreeSearchBox"><input type="text" id="seTreeSearchIn" placeholder="筛选文件名…" autocomplete="off"></div> | |
| </div> | |
| <div class="se-tree-list" id="seTreeList"></div> | |
| <div class="se-ctx-menu" id="seCtxMenu" role="menu"></div> | |
| <input type="file" id="seUpFile" multiple style="display:none"> | |
| <input type="file" id="seUpDir" multiple webkitdirectory directory style="display:none"> | |
| </aside> | |
| <div class="editor-main"> | |
| <div class="editor-wrapper"> | |
| <div class="se-tabs-wrap"> | |
| <div class="se-tabs-bar" id="seTabsBar"></div> | |
| </div> | |
| <div class="editor-toolbar" id="editorToolbar"> | |
| <div class="et-tools"> | |
| <button type="button" class="et-btn se-drawer-btn" id="seDrawerBtn" onclick="seToggleTree()" title="展开文件列表">☰ 文件</button> | |
| <span class="et-sep et-sep-drawer"></span> | |
| <button type="button" class="et-btn" onclick="history.back()" title="返回文件列表">返回</button> | |
| <span class="et-sep"></span> | |
| <button type="button" class="et-btn" id="btnFormat" onclick="seFormat()" title="格式化 Ctrl+Alt+F">格式化</button> | |
| <button type="button" class="et-btn" id="btnMinify" onclick="seMinify()" title="压缩 Ctrl+Alt+M">压缩</button> | |
| <span class="et-sep"></span> | |
| <button type="button" class="et-btn" onclick="seFindOpen(false)" title="搜索 Ctrl+F">搜索</button> | |
| <button type="button" class="et-btn" onclick="seFindOpen(true)" title="替换 Ctrl+H">替换</button> | |
| <button type="button" class="et-btn et-btn-save" id="seToolbarSave" onclick="saveContent()" title="保存 Ctrl+S">保存</button> | |
| <span class="et-sep"></span> | |
| <div class="qn-wrap" id="qnWrap"> | |
| <button type="button" class="et-btn qn-btn" onclick="toggleQn(event)" title="常用文件快捷打开"><span class="qn-label-full">快速编辑 ▾</span><span class="qn-label-short">快捷 ▾</span></button> | |
| <div class="qn-menu" id="qnMenu"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="se-findbar" id="seFindbar"> | |
| <input type="text" id="seFind" placeholder="搜索"> | |
| <input type="text" id="seReplace" placeholder="替换为"> | |
| <button type="button" onmousedown="event.preventDefault()" onclick="seFindPrev()">上一个</button> | |
| <button type="button" onmousedown="event.preventDefault()" onclick="seFindNext()">下一个</button> | |
| <button type="button" id="seReplaceBtn" onmousedown="event.preventDefault()" onclick="seReplaceOne()">替换</button> | |
| <button type="button" id="seReplaceAllBtn" onmousedown="event.preventDefault()" onclick="seReplaceAll()">全部</button> | |
| <span class="se-find-stat" id="seFindStat"></span> | |
| <button type="button" class="se-find-close" onclick="seFindClose()" title="关闭 Esc">×</button> | |
| </div> | |
| <div class="editor-host"> | |
| <div class="se-wrap" id="seWrap"> | |
| <div class="se-gutter" id="seGutter"></div> | |
| <div class="se-body"><div class="se-scroll"><pre class="se-hl" id="seHl"></pre><textarea id="tx" class="se-ta" spellcheck="false"></textarea></div></div> | |
| </div> | |
| </div> | |
| <div class="status-bar"> | |
| <span id="cc">字符: </span> | |
| <span id="lc">行数: </span> | |
| <span id="mc">Sublime · Monokai · Ctrl+Tab 切换 · Ctrl+W 关闭</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="se-mv-overlay" id="seMvOverlay" onclick="if(event.target===this)seMvClose()"> | |
| <div class="se-mv-box" onclick="event.stopPropagation()"> | |
| <div class="se-mv-head">选择移动目标</div> | |
| <div class="se-mv-info" id="seMvInfo"></div> | |
| <div class="se-mv-dest" id="seMvDest"></div> | |
| <div class="se-mv-nav"> | |
| <button type="button" class="se-mv-up" id="seMvUp" onclick="seMvUp()">↑</button> | |
| <span class="se-mv-cur" id="seMvCur" title="">.</span> | |
| </div> | |
| <div class="se-mv-list" id="seMvList"></div> | |
| <div class="se-mv-foot"> | |
| <button type="button" onclick="seMvClose()">取消</button> | |
| <button type="button" class="se-mv-ok" id="seMvOk" onclick="seMvConfirm()">确认移动</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="se-mv-overlay" id="seChmodOverlay" onclick="if(event.target===this)seChmodClose()"> | |
| <div class="se-mv-box" onclick="event.stopPropagation()"> | |
| <div class="se-mv-head">修改权限</div> | |
| <div class="se-mv-info" id="seChmodInfo"></div> | |
| <div id="seChmodBody"></div> | |
| <div class="se-mv-foot"> | |
| <button type="button" onclick="seChmodClose()">取消</button> | |
| <button type="button" class="se-mv-ok" id="seChmodOk" onclick="seChmodApply()">应用</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="se-mv-overlay" id="upConflictOverlay" onclick="if(event.target===this)uploadConflictPick('cancel')"> | |
| <div class="se-mv-box" onclick="event.stopPropagation()"> | |
| <div class="se-mv-head">文件已存在</div> | |
| <div class="se-mv-info"><strong id="upConflictName" style="color:#e6db74"></strong> 已存在,请选择处理方式</div> | |
| <div style="padding:0 14px 12px;font-size:12px;color:#90908a"><label style="cursor:pointer;display:flex;align-items:center;gap:6px"><input type="checkbox" id="upConflictAll" style="width:14px;height:14px;margin:0"> 全部应用此选择(本次上传)</label></div> | |
| <div class="se-mv-foot" style="flex-direction:column;align-items:stretch;gap:8px"> | |
| <button type="button" class="se-mv-ok" onclick="uploadConflictPick('replace')">替换</button> | |
| <button type="button" onclick="uploadConflictPick('copy')">保留为副本</button> | |
| <button type="button" onclick="uploadConflictPick('cancel')">取消</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| var seTa=document.getElementById('tx'),seHl=document.getElementById('seHl'),seGutter=document.getElementById('seGutter'),seLang='txt',seHlTimer=0,seTabs=[],seActiveId=null,SE_TABS_KEY='se_editor_tabs_v1',SE_MAX_TABS=20,seTreeDir='.',seTreeParent='.',seSilent=0; | |
| function seApi(data){var fd=new FormData();Object.keys(data).forEach(function(k){fd.append(k,data[k]);});return fetch(location.pathname||'',{method:'POST',body:fd,credentials:'same-origin',cache:'no-store'}).then(function(r){if(!r.ok)throw new Error('HTTP '+r.status);return r.json();});} | |
| function seNormText(s){return String(s).replace(/\r\n/g,'\n').replace(/\r/g,'\n');} | |
| function seNormPath(p){p=String(p).replace(/\\/g,'/').replace(/\/+/g,'/');if(!p||p==='.')return '.';var abs=p.charAt(0)==='/',parts=p.split('/'),out=[],i,s;for(i=0;i<parts.length;i++){s=parts[i];if(!s||s==='.')continue;if(s==='..'){if(out.length&&out[out.length-1]!=='..')out.pop();else if(!abs)out.push('..');}else out.push(s);}p=abs?'/'+out.join('/'):out.join('/');return p||'.';} | |
| function seTextEq(a,b){return seNormText(a)===seNormText(b);} | |
| function sePathEq(a,b){return seNormPath(a)===seNormPath(b);} | |
| function seFindTabByPath(path){var np=seNormPath(path);for(var i=0;i<seTabs.length;i++){if(seNormPath(seTabs[i].path)===np)return seTabs[i];}return null;} | |
| function seSyncTabDirty(t,cur){if(cur===undefined)cur=getEditorValue();t.content=cur;t.dirty=!seTextEq(cur,t.saved);return t.dirty;} | |
| function seHasUnsaved(){for(var i=0;i<seTabs.length;i++){if(!seTextEq(seTabs[i].content,seTabs[i].saved))return true;}return false;} | |
| function seLangFromName(n){var e=(n.split('.').pop()||'').toLowerCase();var m={php:1,js:1,jsx:1,ts:1,css:1,scss:1,html:1,htm:1,json:1,xml:1,sql:1,py:1,java:1,c:1,cpp:1,h:1,go:1,md:1,yaml:1,yml:1,sh:1,bash:1,ini:1,conf:1};return m[e]?e:'txt';} | |
| function seDirFromPath(p){var d=String(p).replace(/\\/g,'/');var i=d.lastIndexOf('/');return i>0?d.substring(0,i):'.';} | |
| function seTabId(p){var n=seNormPath(p),h=0,i;for(i=0;i<n.length;i++)h=((h<<5)-h)+n.charCodeAt(i)|0;return 't_'+(h>>>0).toString(36)+'_'+n.replace(/[^a-zA-Z0-9]+/g,'_').slice(0,48);} | |
| function seGetTab(id){for(var i=0;i<seTabs.length;i++){if(seTabs[i].id===id)return seTabs[i];}return null;} | |
| function seGetActiveTab(){return seActiveId?seGetTab(seActiveId):null;} | |
| function seFlushTab(){if(!seActiveId)return;var t=seGetTab(seActiveId);if(!t)return;seSyncTabDirty(t);t.sel={s:seTa.selectionStart,e:seTa.selectionEnd,st:seTa.scrollTop,sl:seTa.scrollLeft};} | |
| function seUpdateHeader(){var t=seGetActiveTab();if(t){document.title='编辑 '+t.name+(t.dirty?' *':'')+' - 文件管理器';}else{document.title='编辑 - 文件管理器';}} | |
| function seLoadTab(id){var t=seGetTab(id);if(!t)return;seActiveId=id;seLang=seLangFromName(t.name);seSilent++;setEditorValue(t.content);seSilent=0;seSyncTabDirty(t);seToolbarUpdate();if(t.sel){try{seTa.setSelectionRange(t.sel.s,t.sel.e);seTa.scrollTop=t.sel.st;seTa.scrollLeft=t.sel.sl;seGutter.scrollTop=t.sel.st;}catch(x){}}seRenderTabs();seUpdateHeader();seTreeSyncTab();seTreeMarkActive();} | |
| function seRenderTabs(){var bar=document.getElementById('seTabsBar');if(!bar)return;var h='';seTabs.forEach(function(t){h+='<div class="se-tab'+(t.id===seActiveId?' active':'')+(t.dirty?' dirty':'')+'" data-id="'+t.id+'" title="'+t.path.replace(/"/g,'"')+'" onclick="seSwitchTab(\''+t.id+'\')"><span class="se-tab-dot"></span><span class="se-tab-name">'+t.name.replace(/</g,'<')+'</span><button type="button" class="se-tab-x" onclick="seCloseTab(\''+t.id+'\',event)" title="关闭">×</button></div>';});bar.innerHTML=h;seTreeMarkActive();if(window.qnMarkMenu)qnMarkMenu();} | |
| function seTreeEsc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/"/g,'"');} | |
| function seTreeMarkActive(){var list=document.getElementById('seTreeList');if(!list)return;var open={};seTabs.forEach(function(t){open[seNormPath(t.path)]=t.id;});list.querySelectorAll('.se-tree-item.file').forEach(function(el){var p=el.getAttribute('data-path'),np=seNormPath(p);el.classList.remove('cur','open');var b=el.querySelector('.se-tree-badge');if(!open[np]){if(b)b.remove();return;}el.classList.add('open');if(open[np]===seActiveId)el.classList.add('cur');if(!b){b=document.createElement('span');b.className='se-tree-badge';b.textContent='●';el.appendChild(b);}});} | |
| function seTreeAttr(s){return String(s).replace(/&/g,'&').replace(/"/g,'"');} | |
| function seTreeFmtPath(dir){dir=seNormPath(dir||'.');return dir==='.'?'/':'/'+dir.replace(/^\.?\//,'');} | |
| function seTreeUpdatePath(){var el=document.getElementById('seTreeDir');if(!el)return;var p=seTreeFmtPath(seTreeDir);el.textContent=p;el.title=p;} | |
| function seTreeUpdateUpBtn(){var btn=document.getElementById('seTreeUpBtn');if(btn)btn.disabled=seTreeDir==='.'||!seTreeParent;} | |
| function seTreeRefresh(){seTreeLoad(seTreeDir);} | |
| function seTreeFilter(){var q=document.getElementById('seTreeSearchIn'),list=document.getElementById('seTreeList');if(!list)return;var s=q?(q.value||'').trim().toLowerCase():'';list.querySelectorAll('.se-tree-item').forEach(function(el){var n=(el.getAttribute('data-name')||'').toLowerCase();el.style.display=!s||n.indexOf(s)>=0?'':'none';});} | |
| function seTreeToggleSearch(){var box=document.getElementById('seTreeSearchBox'),btn=document.getElementById('seTreeSearchBtn'),inp=document.getElementById('seTreeSearchIn');if(!box)return;var open=box.classList.toggle('open');if(btn)btn.classList.toggle('on',open);if(open&&inp){inp.focus();}else if(inp){inp.value='';seTreeFilter();}} | |
| function seTreeToolbarNew(e){e.stopPropagation();var btn=document.getElementById('seTreeNewBtn');if(!btn)return;var r=btn.getBoundingClientRect();seCtxShow(r.left,r.bottom+2,{el:null,path:null,parentDir:seTreeDir,name:null,isDir:false});} | |
| function seTreeLoad(dir){dir=dir||'.';seTreeDir=dir;seTreeUpdatePath();var list=document.getElementById('seTreeList');if(list)list.innerHTML='<div class="qn-empty" style="padding:12px;color:#75715e">加载中...</div>';seApi({a:'ls',dir:dir}).then(function(d){if(!d.s){seTip(d.msg||'无法加载目录',1);if(list)list.innerHTML='<div class="qn-empty" style="padding:12px;color:#f92672">加载失败</div>';return;}seTreeParent=d.parent!=null?d.parent:'.';seTreeUpdateUpBtn();var h='';d.items.forEach(function(it){var p=String(it.p).replace(/\\/g,'/');if(it.d){h+='<div class="se-tree-item dir" data-path="'+seTreeAttr(p)+'" data-name="'+seTreeAttr(it.n)+'"><span class="se-tree-ico">📁</span><span class="se-tree-name">'+seTreeEsc(it.n)+'</span></div>';}else{var ex=seFindTabByPath(p),open=!!ex;h+='<div class="se-tree-item file'+(open?' open':'')+(ex&&ex.id===seActiveId?' cur':'')+'" data-path="'+seTreeAttr(p)+'" data-name="'+seTreeAttr(it.n)+'"><span class="se-tree-ico">📄</span><span class="se-tree-name">'+seTreeEsc(it.n)+'</span>'+(open?'<span class="se-tree-badge">●</span>':'')+'</div>';}});if(list)list.innerHTML=h||'<div class="qn-empty" style="padding:12px;color:#75715e">空目录</div>';seTreeMarkActive();seTreeFilter();}).catch(function(){seTip('加载目录失败',1);if(list)list.innerHTML='<div class="qn-empty" style="padding:12px;color:#f92672">加载失败</div>';});} | |
| function seTreeUp(){if(seTreeDir==='.'||!seTreeParent)return;seTreeLoad(seTreeParent);} | |
| function seTreeSyncTab(){var t=seGetActiveTab();if(t&&t.dir!==seTreeDir)seTreeLoad(t.dir);} | |
| function seTreeDrawerMode(){return window.matchMedia('(max-width:768px)').matches;} | |
| function seSidebarOpen(){var s=document.getElementById('seSidebar');if(!s)return false;if(seTreeDrawerMode())return s.classList.contains('drawer-open');return !s.classList.contains('collapsed');} | |
| function seSetSidebar(open){var s=document.getElementById('seSidebar'),bd=document.getElementById('seDrawerBackdrop'),btn=document.getElementById('seDrawerBtn');if(!s)return;if(seTreeDrawerMode()){s.classList.toggle('drawer-open',!!open);s.classList.remove('collapsed');if(bd)bd.classList.toggle('show',!!open);document.body.classList.toggle('se-drawer-open',!!open);}else{s.classList.toggle('collapsed',!open);s.classList.remove('drawer-open');if(bd)bd.classList.remove('show');document.body.classList.remove('se-drawer-open');}if(btn){btn.classList.toggle('on',!!open);btn.title=open?'收起文件列表':'展开文件列表';}} | |
| function seSetDrawer(open){seSetSidebar(open);} | |
| function seToggleTree(force){var open;if(force===true)open=true;else if(force===false)open=false;else open=!seSidebarOpen();seSetSidebar(open);} | |
| function seTreeOnClick(e){var el=e.target.closest('.se-tree-item');if(!el)return;var path=el.getAttribute('data-path');if(!path)return;if(el.classList.contains('dir'))seTreeLoad(path);else{seOpenFile(path);if(seTreeDrawerMode())seSetSidebar(false);}} | |
| var seCtxTarget=null; | |
| function seTreeNewParent(el){if(!el)return seTreeDir;var path=el.getAttribute('data-path');if(el.classList.contains('dir'))return path;return seDirFromPath(path);} | |
| function seTreeItemParent(el){if(!el)return seTreeDir;var path=el.getAttribute('data-path');return seDirFromPath(path);} | |
| function seTreeItemName(el){return el?(el.getAttribute('data-name')||''):'';} | |
| function seCtxHide(){var m=document.getElementById('seCtxMenu');if(m)m.classList.remove('open');seCtxTarget=null;} | |
| function seCtxShow(x,y,target){seCtxTarget=target;var m=document.getElementById('seCtxMenu');if(!m)return;var h='<button type="button" class="se-ctx-item" data-act="cf">新建文件</button><button type="button" class="se-ctx-item" data-act="cd">新建文件夹</button><div class="se-ctx-sep"></div><button type="button" class="se-ctx-item" data-act="upf">上传文件</button><button type="button" class="se-ctx-item" data-act="upd">上传文件夹</button>';if(target.path){h+='<div class="se-ctx-sep"></div><button type="button" class="se-ctx-item" data-act="dup">复制为副本</button><button type="button" class="se-ctx-item" data-act="mv">移动到</button><button type="button" class="se-ctx-item" data-act="chmod">修改权限</button><button type="button" class="se-ctx-item" data-act="rn">重命名</button><button type="button" class="se-ctx-item danger" data-act="del">删除</button>';}m.innerHTML=h;m.classList.add('open');var r=m.getBoundingClientRect(),vw=window.innerWidth,vh=window.innerHeight,l=x,t=y;if(l+r.width>vw-8)l=Math.max(8,vw-r.width-8);if(t+r.height>vh-8)t=Math.max(8,vh-r.height-8);m.style.left=l+'px';m.style.top=t+'px';} | |
| function seTreeOnCtx(e){var list=document.getElementById('seTreeList'),head=e.target.closest('.se-tree-head');if(!list&&!head)return;if(e.target.closest('.se-tree-act,.se-tree-search-box'))return;e.preventDefault();var el=e.target.closest('.se-tree-item');var parentDir=seTreeNewParent(el);seCtxShow(e.clientX,e.clientY,{el:el,path:el?el.getAttribute('data-path'):null,parentDir:parentDir,name:el?seTreeItemName(el):null,isDir:el?el.classList.contains('dir'):false});} | |
| function seTreeTabsClosePath(path,isDir){var p=seNormPath(path);if(isDir){var pre=p==='.'?'./':p+'/';var rm=[];seTabs.forEach(function(t){var tp=seNormPath(t.path);if(tp===p||tp.indexOf(pre)===0||(p!=='.'&&tp.indexOf(p+'/')===0))rm.push(t.id);});rm.forEach(function(id){seCloseTab(id,null,1);});}else{var t=seFindTabByPath(p);if(t)seCloseTab(t.id,null,1);}} | |
| function seTreeTabsRename(oldPath,newPath,isDir){var o=seNormPath(oldPath),n=seNormPath(newPath);if(isDir){var pre=o==='.'?'./':o+'/';seTabs.forEach(function(t){var tp=seNormPath(t.path);if(tp===o||tp.indexOf(pre)===0||tp.indexOf(o+'/')===0){var suf=tp===o?'':tp.slice(o.length);t.path=seNormPath(n+suf);t.name=t.path.slice(t.path.lastIndexOf('/')+1);t.dir=seDirFromPath(t.path);var nid=seTabId(t.path);if(t.id===seActiveId)seActiveId=nid;t.id=nid;}});}else{var t=seFindTabByPath(o);if(t){t.path=n;t.name=n.slice(n.lastIndexOf('/')+1);t.dir=seDirFromPath(n);var nid=seTabId(n);if(t.id===seActiveId)seActiveId=nid;t.id=nid;}}seRenderTabs();seStoreTabs();seUpdateHeader();} | |
| function seTreeNewFile(){var t=seCtxTarget;if(!t)return;var dir=t.parentDir,hint=dir&&dir!=='.'?'在 '+dir+' 下创建':'在当前目录创建';var n=prompt(hint+'\n输入文件名','newfile.txt');seCtxHide();if(!n)return;n=String(n).trim().replace(/\\/g,'/');if(!n||n.indexOf('/')>=0||n.indexOf('..')>=0){seTip('文件名无效',1);return;}seApi({a:'cf',dir:dir,n:n}).then(function(d){if(!d.s){seTip(d.e||'创建失败',1);return;}seTip('已创建 '+n);seTreeLoad(dir);var fp=d.path||((dir==='.'||!dir)?n:dir+'/'+n);seOpenFile(fp);}).catch(function(){seTip('创建失败',1);});} | |
| function seTreeNewDir(){var t=seCtxTarget;if(!t)return;var dir=t.parentDir,hint=dir&&dir!=='.'?'在 '+dir+' 下创建':'在当前目录创建';var n=prompt(hint+'\n输入文件夹名','newfolder');seCtxHide();if(!n)return;n=String(n).trim().replace(/\\/g,'/');if(!n||n.indexOf('/')>=0||n.indexOf('..')>=0){seTip('文件夹名无效',1);return;}seApi({a:'cd',dir:dir,n:n}).then(function(d){if(!d.s){seTip(d.e||'创建失败',1);return;}seTip('已创建文件夹 '+n);seTreeLoad(dir);}).catch(function(){seTip('创建失败',1);});} | |
| function seTreeRename(){var t=seCtxTarget;if(!t||!t.el)return;var on=t.name,dir=seTreeItemParent(t.el),oldPath=t.path;var nn=prompt('重命名为',on);seCtxHide();if(!nn||nn===on)return;nn=String(nn).trim().replace(/\\/g,'/');if(!nn||nn.indexOf('/')>=0||nn.indexOf('..')>=0){seTip('名称无效',1);return;}seApi({a:'rn',dir:dir,on:on,nn:nn}).then(function(d){if(!d.s){seTip('重命名失败',1);return;}var newPath=(dir==='.'||!dir)?nn:dir+'/'+nn;seTreeTabsRename(oldPath,newPath,t.isDir);seTip('已重命名为 '+nn);seTreeLoad(seTreeDir);}).catch(function(){seTip('重命名失败',1);});} | |
| function seTreeDelete(){var t=seCtxTarget;if(!t||!t.path)return;var label=t.isDir?'文件夹「'+t.name+'」及其内容':'文件「'+t.name+'」';if(!confirm('确定删除'+label+'?\n此操作不可恢复!')){seCtxHide();return;}seCtxHide();var dir=seTreeItemParent(t.el),path=t.path;seApi({a:'del',dir:dir,n:t.name}).then(function(d){if(!d.s){seTip(d.msg||'删除失败',1);return;}seTreeTabsClosePath(path,t.isDir);seTip(d.msg||'已删除');seTreeLoad(seTreeDir);}).catch(function(){seTip('删除失败',1);});} | |
| function seTreeDuplicate(){var t=seCtxTarget;if(!t||!t.path)return;seCtxHide();var dir=seTreeItemParent(t.el);seApi({a:'dup',dir:dir,n:t.name}).then(function(d){if(!d.s){seTip(d.msg||'复制失败',1);return;}seTip('已复制为 '+d.n);seTreeLoad(seTreeDir);if(!d.d)seOpenFile(seNormPath(d.path));}).catch(function(){seTip('复制失败',1);});} | |
| function seTreeDirtyUnder(path,isDir){var p=seNormPath(path);if(!isDir){var t=seFindTabByPath(p);return t?!!t.dirty:false;}var pre=p+'/';for(var i=0;i<seTabs.length;i++){var tp=seNormPath(seTabs[i].path);if((tp===p||tp.indexOf(pre)===0)&&seTabs[i].dirty)return true;}return false;} | |
| var seMvPending=null,seMvPickDir='.',seMvParent=null; | |
| function seMvInvalidMsg(destDir){if(!seMvPending)return'';var dest=seNormPath(destDir),oldPath=seMvPending.path,newPath=seNormPath(dest==='.'?seMvPending.name:dest+'/'+seMvPending.name);if(sePathEq(oldPath,newPath))return'目标与当前位置相同';if(seMvPending.isDir){var op=oldPath+'/';if(sePathEq(dest,oldPath)||(dest!=='.'&&dest.indexOf(op)===0))return'不能移动到自身或其子目录';}return'';} | |
| function seMvUpdateDest(){var el=document.getElementById('seMvDest'),ok=document.getElementById('seMvOk');if(!el)return;var dest=seNormPath(seMvPickDir),msg=seMvInvalidMsg(dest);el.innerHTML='移动到:<strong>'+seTreeEsc(dest)+'</strong>'+(msg?'<br><span style="color:#f92672">'+seTreeEsc(msg)+'</span>':'');if(ok)ok.disabled=!!msg;} | |
| function seMvClose(){var o=document.getElementById('seMvOverlay');if(o)o.classList.remove('open');seMvPending=null;} | |
| function seMvRowInvalid(path){if(!seMvPending||!seMvPending.isDir)return false;var p=seNormPath(path),op=seMvPending.path+'/';return sePathEq(p,seMvPending.path)||p.indexOf(op)===0;} | |
| function seMvLoad(dir){dir=dir||'.';seMvPickDir=dir;var cur=document.getElementById('seMvCur'),up=document.getElementById('seMvUp'),list=document.getElementById('seMvList');if(cur){cur.textContent=dir;cur.title=dir;}seMvUpdateDest();if(list)list.innerHTML='<div class="se-mv-empty">加载中...</div>';seApi({a:'ls',dir:dir}).then(function(d){if(!d.s){if(list)list.innerHTML='<div class="se-mv-empty">加载失败</div>';return;}seMvParent=d.parent!=null?d.parent:(dir!=='.'?'.':null);if(up)up.disabled=dir==='.';var dirs=d.items.filter(function(it){return it.d;});if(!list)return;if(!dirs.length){list.innerHTML='<div class="se-mv-empty">无子文件夹<br><span style="font-size:11px;color:#75715e">可点「确认移动」移到当前目录</span></div>';return;}var h='';dirs.forEach(function(it){var p=String(it.p).replace(/\\/g,'/'),bad=seMvRowInvalid(p);h+='<div class="se-mv-item'+(bad?' disabled':'')+'" data-path="'+seTreeAttr(p)+'"><span>📁</span><span>'+seTreeEsc(it.n)+'</span></div>';});list.innerHTML=h;}).catch(function(){if(list)list.innerHTML='<div class="se-mv-empty">加载失败</div>';});} | |
| function seMvUp(){if(seMvPickDir==='.')return;seMvLoad(seMvParent||'.');} | |
| function seMvOpen(){var t=seCtxTarget;if(!t||!t.path)return;seCtxHide();seMvPending={name:t.name,path:seNormPath(t.path),isDir:t.isDir,srcDir:seTreeItemParent(t.el)};seMvPickDir=seMvPending.srcDir;var info=document.getElementById('seMvInfo');if(info)info.innerHTML='选择要将 <em>'+seTreeEsc(t.name)+'</em> 移动到的文件夹,然后点击「确认移动」';var o=document.getElementById('seMvOverlay');if(o)o.classList.add('open');seMvLoad(seMvPickDir);} | |
| function seMvConfirm(){if(!seMvPending)return;var destDir=seNormPath(seMvPickDir),dir=seMvPending.srcDir,oldPath=seMvPending.path,msg=seMvInvalidMsg(destDir);if(msg){seTip(msg,1);return;}var newPath=seNormPath(destDir==='.'?seMvPending.name:destDir+'/'+seMvPending.name);if(seTreeDirtyUnder(oldPath,seMvPending.isDir)&&!confirm('有已打开的文件未保存,移动后路径会变更,确定继续?'))return;var btn=document.getElementById('seMvOk');if(btn)btn.disabled=true;seApi({a:'mv',dir:dir,d:destDir,n:seMvPending.name}).then(function(d){if(!d.s){seTip(d.msg||'移动失败',1);if(btn)btn.disabled=false;return;}seTreeTabsRename(oldPath,newPath,seMvPending.isDir);seTip(d.msg||'已移动');seMvClose();seTreeLoad(destDir);}).catch(function(){seTip('移动失败',1);if(btn)btn.disabled=false;});} | |
| function seTreeMove(){seMvOpen();} | |
| var seChmodPending=null; | |
| function seChmodClose(){var o=document.getElementById('seChmodOverlay');if(o)o.classList.remove('open');seChmodPending=null;} | |
| function seChmodPreset(v){var inp=document.getElementById('seChmodMode');if(inp)inp.value=v;} | |
| function seChmodRender(it){var body=document.getElementById('seChmodBody'),info=document.getElementById('seChmodInfo');if(!body||!seChmodPending)return;if(!it||!it.oct){body.innerHTML='<div class="se-chmod-body" style="color:#f92672">读取权限失败</div>';return;}if(info)info.innerHTML='修改 <em>'+seTreeEsc(seChmodPending.name)+'</em> 的权限';var h='<div class="se-chmod-body"><div class="se-chmod-cur">'+seTreeEsc(it.oct)+'<span class="sym">'+seTreeEsc(it.sym||'')+'</span></div><input type="text" class="se-chmod-mode" id="seChmodMode" placeholder="新权限,如 755 或 rwxr-xr-x" value="'+seTreeAttr(it.oct)+'" autocomplete="off"><div class="se-chmod-presets"><button type="button" onclick="seChmodPreset(\'755\')">755</button><button type="button" onclick="seChmodPreset(\'644\')">644</button><button type="button" onclick="seChmodPreset(\'775\')">775</button><button type="button" onclick="seChmodPreset(\'600\')">600</button><button type="button" onclick="seChmodPreset(\'rwxr-xr-x\')">rwxr-xr-x</button></div>';if(seChmodPending.isDir)h+='<label class="se-chmod-rec"><input type="checkbox" id="seChmodRec"> 递归修改子目录</label>';h+='</div>';body.innerHTML=h;} | |
| function seChmodFetch(fd){return fetch(location.pathname||'',{method:'POST',body:fd,credentials:'same-origin',cache:'no-store'}).then(function(r){if(!r.ok)throw new Error('HTTP '+r.status);return r.json();});} | |
| function seChmodLoad(){if(!seChmodPending)return;var fd=new FormData();fd.append('a','chmod');fd.append('dir',seChmodPending.dir);fd.append('n[]',seChmodPending.name);seChmodFetch(fd).then(function(d){if(!d.s||!d.items||!d.items.length){seChmodRender(null);return;}seChmodRender(d.items[0]);}).catch(function(){seChmodRender(null);});} | |
| function seChmodOpen(){var t=seCtxTarget;if(!t||!t.path)return;seCtxHide();seChmodPending={name:t.name,path:seNormPath(t.path),isDir:!!t.isDir,dir:seTreeItemParent(t.el)};var info=document.getElementById('seChmodInfo'),body=document.getElementById('seChmodBody'),o=document.getElementById('seChmodOverlay');if(info)info.innerHTML='正在读取 <em>'+seTreeEsc(t.name)+'</em> 的权限…';if(body)body.innerHTML='';if(o)o.classList.add('open');seChmodLoad();} | |
| function seChmodApply(){if(!seChmodPending)return;var modeEl=document.getElementById('seChmodMode'),mode=modeEl?modeEl.value.trim():'';if(!mode)return seTip('请输入新权限',1);var recEl=document.getElementById('seChmodRec'),fd=new FormData();fd.append('a','chmod');fd.append('dir',seChmodPending.dir);fd.append('mode',mode);fd.append('n[]',seChmodPending.name);if(recEl&&recEl.checked)fd.append('recursive','1');var btn=document.getElementById('seChmodOk');if(btn){btn.disabled=true;btn.textContent='应用中…';}seChmodFetch(fd).then(function(d){if(btn){btn.disabled=false;btn.textContent='应用';}seTip(d.msg||'完成',d.s?0:1);if(d.s){seChmodClose();seTreeLoad(seTreeDir);}}).catch(function(){if(btn){btn.disabled=false;btn.textContent='应用';}seTip('修改权限失败',1);});} | |
| function seTreeUploadPick(isDir){var t=seCtxTarget;if(!t)return;seCtxHide();var inp=document.getElementById(isDir?'seUpDir':'seUpFile');if(!inp)return;inp._seUploadDir=t.parentDir||seTreeDir;inp.value='';inp.click();} | |
| var upConflictAll=null,upConflictPending=null; | |
| function uploadConflictReset(){upConflictAll=null;var el=document.getElementById('upConflictAll');if(el)el.checked=false;} | |
| function uploadConflictClose(){var o=document.getElementById('upConflictOverlay');if(o)o.classList.remove('open');upConflictPending=null;} | |
| function uploadConflictPick(mode){var p=upConflictPending;if(!p)return;var allEl=document.getElementById('upConflictAll');if(allEl&&allEl.checked&&mode!=='cancel')upConflictAll=mode;uploadConflictClose();if(p.resolve)p.resolve(mode);} | |
| function uploadConflictAsk(name){return new Promise(function(resolve){if(upConflictAll){resolve(upConflictAll);return;}upConflictPending={resolve:resolve};var nm=document.getElementById('upConflictName');if(nm)nm.textContent=name;var o=document.getElementById('upConflictOverlay');if(o)o.classList.add('open');else resolve('replace');});} | |
| function uploadCheckExists(dir,rel,name,url){var fd=new FormData();fd.append('a','upchk');fd.append('dir',dir);if(rel)fd.append('rel',rel);else fd.append('n',name||'');return fetch(url||'',{method:'POST',body:fd,credentials:'same-origin',cache:'no-store'}).then(function(r){return r.json();});} | |
| function uploadSendFile(dir,file,rel,mode,url){return new Promise(function(resolve){var xhr=new XMLHttpRequest();var fd=new FormData();fd.append('a','up');fd.append('dir',dir);fd.append('fl',file,file.name||'file');if(rel)fd.append('rel',rel);if(mode)fd.append('mode',mode);xhr.onload=function(){try{resolve(JSON.parse(xhr.responseText));}catch(e){resolve({s:false,e:'响应异常'});}};xhr.onerror=function(){resolve({s:false,e:'网络错误'});};xhr.open('POST',url||'');xhr.send(fd);});} | |
| async function uploadFileWithConflict(dir,file,rel,url){var name=rel||file.name||'file';var chk=await uploadCheckExists(dir,rel,file.name,url);if(!chk.s)return chk;var mode='';if(chk.exists){mode=await uploadConflictAsk(chk.n||name);if(mode==='cancel')return{s:false,e:'已取消',skip:true};}var d=await uploadSendFile(dir,file,rel,mode,url);if(d.conflict&&!mode){mode=await uploadConflictAsk(d.n||name);if(mode==='cancel')return{s:false,e:'已取消',skip:true};return uploadSendFile(dir,file,rel,mode,url);}return d;} | |
| function seTreeUploadOne(dir,file,rel){return uploadFileWithConflict(dir,file,rel,location.pathname||'');} | |
| async function seTreeUploadFiles(dir,files,isDir){var n=files.length;if(!n)return;uploadConflictReset();var ok=0,fail=0,skip=0,lastErr='';seTip('上传 0/'+n+'…',2);for(var i=0;i<n;i++){var file=files[i],rel=isDir&&(file.webkitRelativePath||file.relativePath)?(file.webkitRelativePath||file.relativePath):file.name;var d=await seTreeUploadOne(dir,file,rel);if(d.s)ok++;else if(d.skip)skip++;else{fail++;lastErr=d.e||'失败';}}seTreeLoad(dir);if(fail)seTip('上传完成:'+ok+' 成功,'+fail+' 失败'+(skip?','+skip+' 已跳过':'')+(lastErr?'('+lastErr+')':''),1);else if(skip)seTip('上传完成:'+ok+' 成功,'+skip+' 已跳过',2);else seTip('已上传 '+ok+' 个文件');} | |
| function seTreeOnUpFileChange(e){var inp=e.target,files=inp&&inp.files;if(!files||!files.length)return;var dir=inp._seUploadDir||seTreeDir;seTreeUploadFiles(dir,files,false);} | |
| function seTreeOnUpDirChange(e){var inp=e.target,files=inp&&inp.files;if(!files||!files.length)return;var dir=inp._seUploadDir||seTreeDir;seTreeUploadFiles(dir,files,true);} | |
| function seCtxOnClick(e){e.stopPropagation();var btn=e.target.closest('[data-act]');if(!btn)return;var act=btn.getAttribute('data-act');if(act==='cf')seTreeNewFile();else if(act==='cd')seTreeNewDir();else if(act==='upf')seTreeUploadPick(false);else if(act==='upd')seTreeUploadPick(true);else if(act==='dup')seTreeDuplicate();else if(act==='mv')seTreeMove();else if(act==='chmod')seChmodOpen();else if(act==='rn')seTreeRename();else if(act==='del')seTreeDelete();} | |
| function seTreeInit(){var list=document.getElementById('seTreeList'),side=document.getElementById('seSidebar'),ctx=document.getElementById('seCtxMenu'),head=side?side.querySelector('.se-tree-head'):null,mvList=document.getElementById('seMvList'),sin=document.getElementById('seTreeSearchIn'),upF=document.getElementById('seUpFile'),upD=document.getElementById('seUpDir');if(list&&!list._seBound){list._seBound=1;list.addEventListener('click',seTreeOnClick);list.addEventListener('contextmenu',seTreeOnCtx);}if(head&&!head._seCtxBound){head._seCtxBound=1;head.addEventListener('contextmenu',seTreeOnCtx);}if(sin&&!sin._seBound){sin._seBound=1;sin.addEventListener('input',seTreeFilter);}if(upF&&!upF._seBound){upF._seBound=1;upF.addEventListener('change',seTreeOnUpFileChange);}if(upD&&!upD._seBound){upD._seBound=1;upD.addEventListener('change',seTreeOnUpDirChange);}if(ctx&&!ctx._seBound){ctx._seBound=1;ctx.addEventListener('click',seCtxOnClick);}if(mvList&&!mvList._seBound){mvList._seBound=1;mvList.addEventListener('click',function(e){var el=e.target.closest('.se-mv-item');if(!el||el.classList.contains('disabled'))return;var p=el.getAttribute('data-path');if(p)seMvLoad(p);});}if(!document._seCtxDocBound){document._seCtxDocBound=1;document.addEventListener('click',seCtxHide);document.addEventListener('keydown',function(e){if(e.key==='Escape'){var sb=document.getElementById('seTreeSearchBox');if(sb&&sb.classList.contains('open')){sb.classList.remove('open');var b=document.getElementById('seTreeSearchBtn');if(b)b.classList.remove('on');var i=document.getElementById('seTreeSearchIn');if(i){i.value='';seTreeFilter();}}else if(document.getElementById('upConflictOverlay')&&document.getElementById('upConflictOverlay').classList.contains('open'))uploadConflictPick('cancel');else if(document.getElementById('seChmodOverlay')&&document.getElementById('seChmodOverlay').classList.contains('open'))seChmodClose();else if(document.getElementById('seMvOverlay')&&document.getElementById('seMvOverlay').classList.contains('open'))seMvClose();else if(seSidebarOpen())seSetSidebar(false);else seCtxHide();}});window.addEventListener('scroll',seCtxHide,true);}seTreeLoad(EDIT_DIR);seSetSidebar(!seTreeDrawerMode());if(!window._seDrawerResize){window._seDrawerResize=1;window.addEventListener('resize',function(){var s=document.getElementById('seSidebar'),bd=document.getElementById('seDrawerBackdrop'),btn=document.getElementById('seDrawerBtn');if(seTreeDrawerMode()){if(s)s.classList.remove('collapsed');if(!seSidebarOpen()){if(bd)bd.classList.remove('show');document.body.classList.remove('se-drawer-open');if(btn)btn.classList.remove('on');}}else{if(s)s.classList.remove('drawer-open');if(bd)bd.classList.remove('show');document.body.classList.remove('se-drawer-open');if(btn)btn.classList.toggle('on',seSidebarOpen());}});}} | |
| function seStoreTabs(){try{var paths=seTabs.map(function(t){return t.path;});sessionStorage.setItem(SE_TABS_KEY,JSON.stringify({active:seActiveId,paths:paths}));}catch(x){}} | |
| function seSwitchTab(id){if(id===seActiveId)return;seFlushTab();seLoadTab(id);seRenderTabs();seStoreTabs();} | |
| async function seOpenFile(path,force,noAct){path=seNormPath(path);if(!path||path==='.')return;var ex=seFindTabByPath(path);if(ex&&!force){if(noAct!==true)seSwitchTab(ex.id);return;}if(ex&&force)seCloseTab(ex.id,null,1);if(seTabs.length>=SE_MAX_TABS){seTip('最多打开 '+SE_MAX_TABS+' 个标签',1);return;}if(noAct!==true)seFlushTab();try{var d=await seApi({a:'gf',p:path});if(!d.s){seTip(d.msg||'无法打开',1);return;}var fp=seNormPath(d.p);ex=seFindTabByPath(fp);if(ex&&!force){if(noAct!==true)seSwitchTab(ex.id);return;}var id=seTabId(fp),ct=seNormText(d.ct);var tab={id:id,path:fp,name:d.n,dir:seDirFromPath(fp),content:ct,saved:ct,dirty:false};seTabs.push(tab);if(noAct===true){seRenderTabs();seStoreTabs();}else seSwitchTab(id);}catch(x){seTip('加载失败',1);}} | |
| function seCloseTab(id,ev,force){if(ev){ev.stopPropagation();ev.preventDefault();}var t=seGetTab(id);if(!t)return;if(id===seActiveId)seFlushTab();else t.dirty=!seTextEq(t.content,t.saved);if(!force&&t.dirty&&!confirm('「'+t.name+'」未保存,确定关闭?'))return;var i=seTabs.findIndex(function(x){return x.id===id;});if(i<0)return;seTabs.splice(i,1);if(seActiveId===id){if(seTabs.length){var ni=Math.min(i,seTabs.length-1);seLoadTab(seTabs[ni].id);}else{seActiveId=null;setEditorValue('');seUpdateHeader();}}seRenderTabs();seStoreTabs();if(!seTabs.length)seTip('已无打开的文件',1);} | |
| function seInitTabs(){var ep=seNormPath(EDIT_FILE_PATH),ct=seNormText(EDIT_FILE_CONTENT);var tab={id:seTabId(ep),path:ep,name:EDIT_FILE_NAME,dir:EDIT_DIR,content:ct,saved:ct,dirty:false};seTabs=[tab];seActiveId=tab.id;seRenderTabs();seLoadTab(seActiveId);try{var raw=sessionStorage.getItem(SE_TABS_KEY);if(raw){var st=JSON.parse(raw);(st.paths||[]).filter(function(p){return p&&!sePathEq(p,ep);}).forEach(function(p){seOpenFile(p,0,1);});}}catch(x){}} | |
| function seResolvePath(p){p=String(p).replace(/\\/g,'/').trim();if(!p)return'';if(p.indexOf('/')>=0||p.indexOf('..')===0)return seNormPath(p);var tab=seGetActiveTab();var base=tab?tab.dir:EDIT_DIR;if(!base||base==='.')return seNormPath(p);return seNormPath(base+'/'+p);} | |
| function seOpenFilePrompt(){var tab=seGetActiveTab(),hint=tab&&tab.dir&&tab.dir!=='.'?'例如: '+tab.dir+'/ftp.php':'例如: ftp.php 或 ../其他.php';var p=prompt('输入要打开的文件路径\n'+hint,'');if(!p)return;seOpenFile(seResolvePath(p));} | |
| function seGoFileList(){var tab=seGetActiveTab();location.href='?dir='+encodeURIComponent(tab?tab.dir:EDIT_DIR);} | |
| var originalContent=EDIT_FILE_CONTENT; | |
| function seEsc(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');} | |
| function seHlTok(tokens,cls,text){var i=tokens.length;tokens.push('<span class="'+cls+'">'+text+'</span>');return '\x01H'+i+'H\x01';} | |
| function seHlApply(s,rules){var tokens=[];for(var i=0;i<rules.length;i++){var re=rules[i][0],cls=rules[i][1];s=s.replace(re,function(m){return seHlTok(tokens,cls,m);});}return s.replace(/\x01H(\d+)H\x01/g,function(_,n){return tokens[+n];});} | |
| function seHlRules(lang){if(lang==='php')return[[/('(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*")/g,'str'],[/(\/\*[\s\S]*?\*\/)/g,'cm'],[/(?<!:)\/\/[^\n]*/g,'cm'],[/(^|\s)(#[^\n]*)/gm,'cm'],[/\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/g,'fn'],[/\b(function|class|if|else|elseif|return|new|public|private|protected|static|echo|print|var|const|foreach|while|for|switch|case|break|continue|namespace|use|require|include|extends|implements|true|false|null|array|as|try|catch|finally|throw|declare|interface|trait|abstract|final|global|isset|empty|unset|die|exit)\b/g,'kw']];if(lang==='js'||lang==='ts'||lang==='jsx')return[[/(\/\*[\s\S]*?\*\/)/g,'cm'],[/(\/\/[^\n]*)/g,'cm'],[/('(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*")/g,'str'],[/(`(?:\\.|[^`\\])*`)/g,'str'],[/\b(function|class|if|else|return|new|const|let|var|for|while|do|switch|case|break|continue|import|export|from|default|async|await|try|catch|finally|throw|typeof|instanceof|in|of|delete|void|yield|extends|static|get|set|super|this|true|false|null|undefined)\b/g,'kw'],[/\b(\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?\b/g,'num'],[/\b(console|document|window|JSON|Math|Array|Object|String|Number|Boolean|parseInt|parseFloat|setTimeout|setInterval|clearTimeout|fetch|Promise|Map|Set|RegExp|Error|alert|location|navigator|localStorage|sessionStorage|FormData|XMLHttpRequest|requestAnimationFrame)\b/g,'fn']];if(lang==='css'||lang==='scss')return[[/(\/\*[\s\S]*?\*\/)/g,'cm'],[/("[^"\\]*(?:\\.[^"\\]*)*"|'[^'\\]*(?:\\.[^'\\]*)*')/g,'str'],[/@[\w-]+/g,'kw'],[/#([0-9a-fA-F]{3,8})\b/g,'num'],[/\b(\d+\.?\d*)(?:px|em|rem|vh|vw|vmin|vmax|%|s|ms|deg|fr|ch|ex)?\b/g,'num'],[/([.#][\w-]+)/g,'fn'],[/([\w-]+)(\s*:)/g,'attr'],[/!important\b/g,'kw']];if(lang==='html'||lang==='htm'||lang==='xml')return[[/(<!--[\s\S]*?-->)/g,'cm'],[/(<!\[CDATA\[[\s\S]*?\]\]>)/g,'cm'],[/(<!DOCTYPE[\s\S]*?>)/gi,'tag'],[/(<![\w-][\s\S]*?>)/g,'tag'],[/(<\?[\s\S]*?\?>)/g,'tag'],[/('(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*")/g,'str'],[/(<\/?[\w:-]+)/g,'tag'],[/(\s)([\w:-]+)(=)/g,'attr']];if(lang==='json')return[[/("[^"\\]*(?:\\.[^"\\]*)*")/g,'str'],[/\b(true|false|null)\b/g,'kw'],[/\b(-?\d+(?:\.\d+)?)\b/g,'num']];if(lang==='py')return[[/('(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*")/g,'str'],[/(#.*)/g,'cm'],[/\b(def|class|if|elif|else|return|import|from|as|for|while|try|except|finally|raise|with|lambda|pass|break|continue|True|False|None|and|or|not|in|is|global|yield)\b/g,'kw']];return[];} | |
| function seHlPart(text,type){if(!text)return'';if((type==='js'||type==='css')&&/<\?/.test(text))return text.split(/(<\?[\s\S]*?\?>)/g).map(function(p){if(/^<\?/.test(p))return seHlApply(seEsc(p),seHlRules('php'));return seHlPart(p,type);}).join('');var t=type;if(t==='htm')t='html';if(t==='jsx')t='js';if(t==='scss')t='css';return seHlApply(seEsc(text),seHlRules(t));} | |
| function seHlMixed(src,lang){return seSplitMixed(src,lang).map(function(p){return seHlPart(p.text,p.type);}).join('');} | |
| function seHlCode(s,lang){var out=lang==='html'||lang==='htm'||lang==='php'||lang==='xml'?seHlMixed(s,lang):seHlApply(seEsc(s),seHlRules(lang));return out+(s.slice(-1)==='\n'?'\n':'');} | |
| function getEditorValue(){return seTa.value;} | |
| function setEditorValue(v){seTa.value=v;seTa.scrollTop=0;seTa.scrollLeft=0;seRefresh();seSyncLayout();} | |
| function seSyncLayout(){if(!seHl||!seTa)return;seHl.style.transform='translate('+(-seTa.scrollLeft)+'px,'+(-seTa.scrollTop)+'px)';seGutter.scrollTop=seTa.scrollTop;} | |
| function updateStats(){var v=getEditorValue();document.getElementById('cc').textContent='字符: '+v.length;document.getElementById('lc').textContent='行数: '+(v?v.split('\n').length:1);} | |
| function seCurLine(){return getEditorValue().substring(0,seTa.selectionStart).split('\n').length-1;} | |
| function seRefreshGutter(){var n=getEditorValue().split('\n').length,cur=seCurLine(),h='';for(var i=0;i<n;i++)h+='<div class="'+(i===cur?'cur':'')+'">'+(i+1)+'</div>';seGutter.innerHTML=h;seGutter.scrollTop=seTa.scrollTop;} | |
| function seRefreshHl(){var v=getEditorValue(),mx=0;v.split('\n').forEach(function(l){if(l.length>mx)mx=l.length;});if(v.length>500000||mx>50000){seHl.innerHTML=seEsc(v);}else{seHl.innerHTML=seHlCode(v,seLang);}requestAnimationFrame(seSyncLayout);} | |
| function seRefresh(){seRefreshGutter();seRefreshHl();updateStats();if(document.getElementById('mc'))document.getElementById('mc').textContent='Sublime · Monokai · '+seLang;} | |
| function seInsert(text){var s=seTa.selectionStart,e=seTa.selectionEnd,v=seTa.value;seTa.value=v.slice(0,s)+text+v.slice(e);seTa.selectionStart=seTa.selectionEnd=s+text.length;seRefresh();} | |
| var seFmtLangs={json:1,js:1,jsx:1,css:1,scss:1,php:1,html:1,htm:1},seFindIdx=-1,seFindPos=[]; | |
| function seFmtKind(){var l=seLang;if(l==='jsx')return'js';if(l==='scss')return'css';return seFmtLangs[l]?l:null;} | |
| function seToolbarUpdate(){var k=seFmtKind(),bf=document.getElementById('btnFormat'),bm=document.getElementById('btnMinify');if(bf)bf.disabled=!k;if(bm)bm.disabled=!k;} | |
| function seTip(msg,err){var bg=err===1||err===true?'#ef4444':err===2?'#52525b':'#10b981';var t=document.createElement('div');t.textContent=msg;t.style.cssText='position:fixed;top:20px;right:20px;background:'+bg+';color:#fff;padding:12px 16px;border-radius:8px;z-index:10001;font-size:14px';document.body.appendChild(t);setTimeout(function(){t.remove();},2000);} | |
| function seFindTagOpen(html,i,n){var si=-1,ci=-1,j=i;while(j<n){if(html.substr(j,4)==='<!--'){var ec=html.indexOf('-->',j+4);j=ec<0?n:ec+3;continue;}var r=html.slice(j),m1=/^<script\b/i.exec(r),m2=/^<style\b/i.exec(r);if(m1&&(si<0||j<si))si=j;if(m2&&(ci<0||j<ci))ci=j;if(m1||m2){if(si>=0&&ci>=0)break;j++;continue;}j++;}if(si<0&&ci<0)return null;var type=si<0?'style':ci<0?'script':si<=ci?'script':'style';return{type:type,pos:type==='script'?si:ci};} | |
| function seJsRegexStart(html,i){if(i<=0)return 1;var j=i-1;while(j>=0&&/\s/.test(html[j]))j--;if(j<0)return 1;var p=html[j];return!/[\w$)\]"'0-9.]/.test(p);} | |
| function seSkipJsRegex(html,i,n){if(html[i]!=='/')return i+1;var c2=html[i+1];if(c2==='/'||c2==='*')return i+1;if(!seJsRegexStart(html,i))return i+1;var j=i+1,esc=false;while(j<n){var c=html[j];if(esc){esc=false;j++;continue;}if(c==='\\'){esc=true;j++;continue;}if(c==='/')return j+1;j++;}return j<n?j+1:n;} | |
| function seTagGt(html,pos,n){var inS=null,q,i=pos;while(i<n){var c=html[i];if(inS){if(c===inS)inS=null;i++;continue;}if(c==='"'||c==="'"){inS=c;i++;continue;}if(c==='>')return i;i++;}return -1;} | |
| function seTagSelfClose(openTag){return/\/\s*>$/.test(openTag);} | |
| function seTagInnerLang(openTag,tag){var m=openTag.match(/\btype\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i),t=(m?m[1]||m[2]||m[3]:'').toLowerCase().trim();if(tag==='style')return'css';if(/^text\/(css|scss)$/.test(t))return'css';if(/^text\/(javascript|ecmascript|jscript|js)$/.test(t)||t==='module')return'js';if(/^text\/(html|plain|template|x-handlebars)/.test(t))return'html';if(/\bsrc\s*=/i.test(openTag)&&!/\btype\s*=/i.test(openTag))return'js';return tag==='script'?'js':'css';} | |
| function seFindTagCloseStart(html,i,n,tag){var inS=null,esc=false,close='</'+tag,isStyle=tag==='style';while(i<n){var c=html[i],c2=html[i+1];if(inS){if(esc)esc=false;else if(c==='\\')esc=true;else if(c===inS)inS=null;i++;continue;}if(c==='/'&&c2==='*'){var bc=html.indexOf('*/',i+2);i=bc<0?n:bc+2;continue;}if(!isStyle&&c==='/'&&c2==='/'){var lc=html.indexOf('\n',i+2);i=lc<0?n:lc+1;continue;}if(!isStyle&&c==='/'&&c2!=='/'&&c2!=='*'){i=seSkipJsRegex(html,i,n);continue;}if(c==='"'||c==="'"||c==='`'){inS=c;i++;continue;}if(c==='<'&&html.substr(i,close.length).toLowerCase()===close){var m=html.substr(i).match(new RegExp('^</'+tag+'\\s*>','i'));if(m)return i;}i++;}return n;} | |
| function seSplitHtmlSub(html){var parts=[],last=0,i=0,n=html.length,op,gt,bodyStart,cs,ce,m,openTag,innerLang;while(i<n){op=seFindTagOpen(html,i,n);if(!op)break;if(op.pos>last){parts.push({type:'html',text:html.slice(last,op.pos)});last=op.pos;}gt=seTagGt(html,op.pos,n);if(gt<0)break;openTag=html.slice(op.pos,gt+1);bodyStart=gt+1;innerLang=seTagInnerLang(openTag,op.type);parts.push({type:'html',text:openTag});if(seTagSelfClose(openTag)){last=gt+1;i=gt+1;continue;}cs=seFindTagCloseStart(html,bodyStart,n,op.type);m=html.substr(cs).match(new RegExp('^</'+op.type+'\\s*>','i'));ce=cs+(m?m[0].length:0);parts.push({type:innerLang,text:html.slice(bodyStart,cs)});parts.push({type:'html',text:html.slice(cs,ce)});last=ce;i=ce;}if(last<n)parts.push({type:'html',text:html.slice(last)});return parts.length?parts:[{type:'html',text:html}];} | |
| var _PT={o:'<'+'?php',e:'<'+'?=',s:'<'+'?',c:'?'+'>' ,x:'<'+'?xml'}; | |
| function seInScriptStyle(src,a,pos){var i=a,op,gt,bodyStart,cs,ce,m;while(i<pos){op=seFindTagOpen(src,i,pos);if(!op||op.pos>=pos)break;gt=seTagGt(src,op.pos,pos);if(gt<0||gt>=pos)break;if(seTagSelfClose(src.slice(op.pos,gt+1))){i=gt+1;continue;}bodyStart=gt+1;if(bodyStart>=pos)return 1;cs=seFindTagCloseStart(src,bodyStart,pos,op.type);if(cs>=pos)return 1;m=src.substr(cs).match(new RegExp('^</'+op.type+'\\s*>','i'));ce=cs+(m?m[0].length:0);i=ce;}return 0;} | |
| function seScanPhpHtml(src){var parts=[],i=0,n=src.length,P=_PT;while(i<n){if(src.substr(i,P.x.length)===P.x){var gt=src.indexOf('>',i);if(gt<0)gt=n;parts=parts.concat(seSplitHtmlSub(src.slice(i,gt+1)));i=gt+1;continue;}if(src.substr(i,P.s.length)!==P.s){var nx=src.indexOf(P.s,i);while(nx<n&&seInScriptStyle(src,i,nx))nx=src.indexOf(P.s,nx+2);if(nx<0)nx=n;var h2=src.slice(i,nx);if(h2)parts=parts.concat(seSplitHtmlSub(h2));i=nx;continue;}var open=P.s.length;if(src.substr(i,P.o.length)===P.o)open=P.o.length;else if(src.substr(i,P.e.length)===P.e)open=P.e.length;var j=i+open,inS=null,esc=false,heredoc=null;while(j<n){var c=src[j],c2=src[j+1];if(heredoc){var le=src.indexOf('\n',j);if(le<0)le=n;if(src.slice(j,le).trim()===heredoc){j=le+1;heredoc=null;}else j=le+1;continue;}if(inS){if(esc)esc=false;else if(c==='\\')esc=true;else if(c===inS)inS=null;j++;continue;}if(c==='"'||c==="'"||c==='`'){inS=c;j++;continue;}if(c==='<'&&c2==='<'&&src.substr(j,3)==='<<<'){var he=src.indexOf('\n',j);heredoc=src.slice(j+3,he).replace(/^[\s-]+/,'').trim();j=he+1;continue;}if(c==='?'&&c2==='>'){j+=2;break;}j++;}parts.push({type:'php',text:src.slice(i,j)});i=j;}return parts;} | |
| function seSplitMixed(src,lang){if(lang==='php')return seScanPhpHtml(src);if(lang==='html'||lang==='htm')return seSplitHtmlSub(src);if(lang==='css')return[{type:'css',text:src}];return[{type:'js',text:src}];} | |
| function seFmtPhpBlock(block){var P=_PT,open,inner,close='';if(block.indexOf(P.o)===0){open=P.o;inner=block.slice(P.o.length);}else if(block.indexOf(P.e)===0){open=P.e;inner=block.slice(P.e.length);}else if(block.indexOf(P.s)===0){open=P.s;inner=block.slice(P.s.length);}else return block;if(inner.slice(-P.c.length)===P.c){close=P.c;inner=inner.slice(0,-P.c.length);}inner=inner.trim();var body=inner?seFmtCodeInner(inner,true):'';return open+(body?' '+body:'')+close;} | |
| function seFmtCodeInner(src,isPhp){src=src.replace(/\r\n/g,'\n');var out='',ind=0,tab=' ',i=0,n=src.length,inS=null,esc=false,heredoc=null;function ws(){while(i<n&&/\s/.test(src[i]))i++;}function line(t){out+='\n'+tab.repeat(ind)+t;}function scanEnd(j){var s2=null,e2=false,jj=j;while(jj<n){var cc=src[jj],cc2=src[jj+1];if(s2){if(e2)e2=false;else if(cc==='\\')e2=true;else if(cc===s2)s2=null;jj++;continue;}if(cc==='"'||cc==="'"||cc==='`'){s2=cc;jj++;continue;}if(isPhp&&cc==='/'&&cc2==='*'&&!(jj>0&&src[jj-1]==='\\')){jj+=2;while(jj<n-1&&!(src[jj]==='*'&&src[jj+1]==='/'))jj++;jj+=2;continue;}if(isPhp&&cc==='/'&&cc2==='/'&&src[jj-1]!==':'){while(jj<n&&src[jj]!=='\n')jj++;continue;}if(isPhp&&cc==='#'&&(!jj||/[\s;{}()]/.test(src[jj-1]))&&src[jj-1]!==':'){while(jj<n&&src[jj]!=='\n')jj++;continue;}if('{};'.indexOf(cc)>=0)break;jj++;}return jj;}while(i<n){if(heredoc){var le=src.indexOf('\n',i);if(le<0)le=n;var ln=src.slice(i,le+1);if(src.slice(i,le).trim()===heredoc){line(ln.trimEnd());heredoc=null;}else{if(!out||out.slice(-1)==='\n')out+=tab.repeat(ind);out+=ln;}i=le+1;continue;}ws();if(i>=n)break;var c=src[i];if(inS){if(!out||out.slice(-1)==='\n')out+=tab.repeat(ind);out+=c;if(esc)esc=false;else if(c==='\\')esc=true;else if(c===inS)inS=null;i++;continue;}if(c==='"'||c==="'"||c==='`'){if(!out||out.slice(-1)==='\n')out+=tab.repeat(ind);inS=c;out+=c;i++;continue;}if(isPhp&&c==='<'&&src[i+1]==='<'&&src.substr(i,3)==='<<<'){var he=src.indexOf('\n',i);line(src.slice(i,he+1));heredoc=src.slice(i+3,he).replace(/^[\s-]+/,'').trim();i=he+1;continue;}if(isPhp&&c==='/'&&src[i+1]==='*'&&!(i>0&&src[i-1]==='\\')){var j=i+2;while(j<n-1&&!(src[j]==='*'&&src[j+1]==='/'))j++;line(src.slice(i,j+2));i=j+2;continue;}if(isPhp&&c==='/'&&src[i+1]==='/'&&src[i-1]!==':'){var k=i;while(k<n&&src[k]!=='\n')k++;line(src.slice(i,k));i=k;continue;}if(isPhp&&c==='#'&&(!out||/[\s;{}()]$/.test(out.slice(-1))||out.slice(-1)==='\n')){var k2=i;while(k2<n&&src[k2]!=='\n')k2++;line(src.slice(i,k2));i=k2;continue;}if(c==='}'){ind=Math.max(0,ind-1);line('}');i++;continue;}if(c==='{'){out+=' {';line('');ind++;i++;continue;}if(c===';'){out+=';';i++;ws();if(i<n&&src[i]!=='}')line('');continue;}var j3=scanEnd(i);var chunk=src.slice(i,j3).trim();if(chunk){if(!out||out.slice(-1)==='\n')out+=tab.repeat(ind);else if(!/\s$/.test(out)&&!/^[\]\),]/.test(chunk))out+=' ';out+=chunk;}i=j3;}return out.replace(/^\n/,'').replace(/\n{3,}/g,'\n\n');} | |
| function seFmtJsPrev(){var j=this.length-1;while(j>=0&&/\s/.test(this[j]))j--;return j<0?'':this[j];} | |
| function seFmtJs(src){if(/<\?/.test(src))return src.replace(/(<\?[\s\S]*?\?>)/g,function(m){return '\n'+m+'\n';}).split(/(<\?[\s\S]*?\?>)/).map(function(p){return/^<\?/.test(p)?p:seFmtJsPure(p);}).join('');return seFmtJsPure(src);} | |
| function seFmtJsPure(src){src=src.replace(/\r\n/g,'\n');var out='',ind=0,tab=' ',i=0,n=src.length,inS=null,esc=false,depth=0;function nl(){out+='\n'+tab.repeat(ind);}function prev(){return seFmtJsPrev.call(out);}function peek(){var j=i;while(j<n&&/\s/.test(src[j]))j++;return src.slice(j,j+20);}while(i<n){var c=src[i],c2=src[i+1];if(inS){out+=c;if(esc)esc=false;else if(c==='\\')esc=true;else if(c===inS)inS=null;i++;continue;}if(c==='/'&&c2==='*'){var j=i+2;while(j<n-1&&!(src[j]==='*'&&src[j+1]==='/'))j++;out+=src.slice(i,j+2);i=j+2;continue;}if(c==='/'&&c2==='/'&&/[\s;{})\]]/.test(src[i-1]||'\n')){var k=i;while(k<n&&src[k]!=='\n')k++;out+=src.slice(i,k);i=k;continue;}if(c==='/'&&seJsRegexStart(src,i)){var re=seSkipJsRegex(src,i,n);out+=src.slice(i,re);i=re;continue;}if(c==='"'||c==="'"||c==='`'){inS=c;out+=c;i++;continue;}if(c==='('||c==='['){depth++;out+=c;i++;continue;}if(c===')'||c===']'){depth=Math.max(0,depth-1);out+=c;i++;continue;}if(c==='{'){var p=prev(),inline=depth>0||/[,:[]$/.test(p)||(/[a-zA-Z0-9_\]$]/.test(p)&&p!==')');if(inline){depth++;out+=(p&&!/\s$/.test(out)&&p!=='{'?' ':'')+'{';i++;continue;}if(out&&out.slice(-1)!=='\n')nl();out+='{';nl();depth++;ind++;i++;continue;}if(c==='}'){if(depth<=0){out+='}';i++;continue;}depth--;if(depth===0){ind=Math.max(0,ind-1);if(out.slice(-1)!=='\n')nl();out+='}';var j=i+1;while(j<n&&/\s/.test(src[j]))j++;if(/^(async\s+function|function)\b/.test(src.slice(j)))out+=';';nl();}else out+='}';i++;continue;}if(c===';'&&depth===0){out+=';';var j2=i+1;while(j2<n&&/\s/.test(src[j2]))j2++;if(j2<n)nl();i++;continue;}if(c===';'){out+=';';i++;continue;}if(/\s/.test(c)){var j=i;while(j<n&&/\s/.test(src[j]))j++;if(j<n&&out&&/[a-zA-Z0-9_$]$/.test(out)&&/[a-zA-Z0-9_$]/.test(src[j]))out+=' ';i=j;continue;}if(depth===0&&out&&out.slice(-1)!=='\n'){var pk=peek();if(/^async\s+function\b/.test(pk))nl();else if(/^function\b/.test(pk)&&/\basync\s*$/.test(out.replace(/\s+$/,''))){}else if(/^(function|var|let|const|if|else|for|while|switch|try|catch|finally|return|class|export|import)\b/.test(pk))nl();}out+=c;i++;}return out.replace(/^\n/,'').replace(/\n{3,}/g,'\n\n').trim();} | |
| function seFmtHtml(src){if(!src||!src.trim())return src;src=src.replace(/\r\n/g,'\n');var tab=' ',ind=0,out='',voidTags=/^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/i,noIndent=/^(script|style|pre|textarea|code)$/i,re=/<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<\/?[\w-]+[^>]*\/?>/g,last=0,m,n=src.length;while((m=re.exec(src))){if(m.index>last){var tx=src.slice(last,m.index).replace(/[ \t]+\n/g,'\n').replace(/\n[ \t]+/g,'\n').replace(/[ \t]{2,}/g,' ');if(tx.trim())out+=tx;}var tag=m[0],isClose=/^<\//.test(tag),isComment=/^<!--/.test(tag),name=(tag.match(/<\/?([\w-]+)/i)||['',''])[1].toLowerCase(),isSelf=isComment||/\/>\s*$/.test(tag)||voidTags.test(name);if(isComment){out+='\n'+tab.repeat(ind)+tag+'\n';}else if(isClose){ind=Math.max(0,ind-1);out+='\n'+tab.repeat(ind)+tag;if(!noIndent.test(name))out+='\n';}else{out+='\n'+tab.repeat(ind)+tag;if(!isSelf&&!noIndent.test(name)){ind++;out+='\n';}}last=m.index+tag.length;}if(last<n){var tx2=src.slice(last).replace(/[ \t]+\n/g,'\n').replace(/\n[ \t]+/g,'\n');if(tx2.trim())out+=tx2;}return out.replace(/^\n+/,'').replace(/\n{3,}/g,'\n\n');} | |
| function seFmtCss(src){src=src.replace(/\r\n/g,'\n');var out='',ind=0,tab=' ',i=0,n=src.length,inS=null,esc=false;function ws(){while(i<n&&/\s/.test(src[i]))i++;}while(i<n){ws();if(i>=n)break;var c=src[i];if(inS){out+=c;if(esc)esc=false;else if(c==='\\')esc=true;else if(c===inS)inS=null;i++;continue;}if(c==='"'||c==="'"){inS=c;out+=c;i++;continue;}if(c==='/'&&src[i+1]==='*'&&!(i>0&&src[i-1]==='\\')){var j=i+2;while(j<n-1&&!(src[j]==='*'&&src[j+1]==='/'))j++;out+='\n'+tab.repeat(ind)+src.slice(i,j+2);i=j+2;continue;}if(c==='}'){ind=Math.max(0,ind-1);out+='\n'+tab.repeat(ind)+'}';i++;continue;}if(c==='{'){out+=' {\n'+tab.repeat(ind+1);ind++;i++;continue;}if(c===';'){out+=';\n'+tab.repeat(ind);i++;continue;}var k=i;while(k<n&&src[k]!==';'&&src[k]!=='{'&&src[k]!=='}')k++;out+=src.slice(i,k).trim();i=k;}return out.replace(/\n{3,}/g,'\n\n').trim();} | |
| function seFormatMixed(src,lang){return seSplitMixed(src,lang).map(function(p){if(!p.text)return'';if(p.type==='php')return seFmtPhpBlock(p.text);if(p.type==='js')return seFmtJs(p.text);if(p.type==='css')return seFmtCss(p.text);if(p.type==='html')return seFmtHtml(p.text);return p.text;}).join('');} | |
| function seMinPhpInner(src){return seMinCode(src,true);} | |
| function seMinPhpBlock(block){var P=_PT,open,inner,close='';if(block.indexOf(P.o)===0){open=P.o;inner=block.slice(P.o.length);}else if(block.indexOf(P.e)===0){open=P.e;inner=block.slice(P.e.length);}else if(block.indexOf(P.s)===0){open=P.s;inner=block.slice(P.s.length);}else return block;if(inner.slice(-P.c.length)===P.c){close=P.c;inner=inner.slice(0,-P.c.length);}inner=inner.trim();var body=inner?seMinPhpInner(inner):'';return open+(body?' '+body:'')+close;} | |
| function seMinHtml(src){if(!src||!src.trim())return src;return seSplitHtmlSub(src).map(function(p){if(!p.text)return'';if(p.type==='js')return seMinJsCss(p.text,false);if(p.type==='css')return seMinJsCss(p.text,true);var ph=[],h=p.text;h=h.replace(/<\?[\s\S]*?\?>/g,function(m){var i=ph.length;ph.push(m);return '\x01P'+i+'P\x01';});h=h.replace(/<!--(?!\[if)[\s\S]*?-->/g,'');h=h.replace(/>\s+</g,function(m,off,s){var b=s.slice(Math.max(0,off-12),off).replace(/\s+$/,''),a=s.slice(off+m.length,off+m.length+12).replace(/^\s+/,'');if(/(?:script|style|pre|textarea)$/i.test(b)||/^(?:script|style|pre|textarea)/i.test(a))return m;return '><';});h=h.replace(/[\t ]*\r?\n[\t ]*/g,'');return h.replace(/\x01P(\d+)P\x01/g,function(_,n){return ph[+n];});}).join('');} | |
| function seMinCssInner(src){var o=seMinCode(src,false);o=o.replace(/\/\*[\s\S]*?\*\//g,'');return o.replace(/\s*([{}:;,>+~])\s*/g,'$1').replace(/\s+/g,' ').trim();} | |
| function seMinJsCss(src,isCss){if(isCss)return seMinCssInner(src);return seMinWs(src);} | |
| function seMinWs(src){var out='',i=0,n=src.length,mode='code',q='',esc=false,needSp=false;function sp(){if(needSp&&out&&!/[\s;,{(\[]$/.test(out))out+=' ';needSp=false;}while(i<n){var c=src[i],c2=src[i+1];if(mode==='code'){if(c==='/'&&c2==='/'&&/[\s;{})\]]/.test(src[i-1]||'\n')){while(i<n&&src[i]!=='\n')i++;if(i<n){sp();out+='\n';i++;}continue;}if(c==='"'||c==="'"||c==='`'){sp();mode='str';q=c;out+=c;i++;continue;}if(/\s/.test(c)){needSp=true;i++;continue;}sp();out+=c;needSp=false;i++;continue;}out+=c;if(esc)esc=false;else if(c==='\\')esc=true;else if(c===q)mode='code';i++;}return out.replace(/[ \t]+\n/g,'\n').replace(/\n{2,}/g,'\n').trim();} | |
| function seMinCode(src,php){var out='',i=0,n=src.length,mode='code',q='',esc=false,needSp=false,heredoc=null;function sp(){if(needSp&&out&&!/[\s;,{(\[]$/.test(out))out+=' ';needSp=false;}while(i<n){if(heredoc){var le=src.indexOf('\n',i);if(le<0)le=n;out+=src.slice(i,le+1);if(src.slice(i,le).trim()===heredoc)heredoc=null;i=le+1;continue;}var c=src[i],c2=src[i+1];if(mode==='code'){if(php&&c==='/'&&c2==='*'&&!(i>0&&src[i-1]==='\\')){mode='bc';i+=2;continue;}if(c==='/'&&c2==='/'&&src[i-1]!==':'){while(i<n&&src[i]!=='\n')i++;if(i<n){sp();out+='\n';i++;}continue;}if(php&&c==='#'&&(!out||/[\s;{}()]$/.test(out)||out.slice(-1)==='\n')){while(i<n&&src[i]!=='\n')i++;if(i<n){sp();out+='\n';i++;}continue;}if(php&&c==='<'&&c2==='<'&&src.substr(i,3)==='<<<'){var he=src.indexOf('\n',i);out+=src.slice(i,he+1);heredoc=src.slice(i+3,he).replace(/^[\s-]+/,'').trim();i=he+1;continue;}if(c==='"'||c==="'"||c==='`'){sp();mode='str';q=c;out+=c;i++;continue;}if(/\s/.test(c)){needSp=true;i++;continue;}sp();out+=c;needSp=false;i++;continue;}if(mode==='bc'){if(c==='*'&&c2==='/'){mode='code';i+=2;}else i++;continue;}if(mode==='str'){out+=c;if(esc)esc=false;else if(c==='\\')esc=true;else if(c===q)mode='code';i++;}}return out.replace(/[ \t]+\n/g,'\n').replace(/\n{2,}/g,'\n').trim();} | |
| function seMinMixed(src,lang){return seSplitMixed(src,lang).map(function(p){if(!p.text)return'';if(p.type==='php')return seMinPhpBlock(p.text);if(p.type==='js')return seMinJsCss(p.text,false);if(p.type==='css')return seMinJsCss(p.text,true);if(p.type==='html')return seMinHtml(p.text);return p.text;}).join('');} | |
| function seFormat(){var k=seFmtKind();if(!k)return seTip('当前类型不支持格式化',1);try{var v=getEditorValue(),o;if(k==='json')o=JSON.stringify(JSON.parse(v),null,4);else o=seFormatMixed(v,k);setEditorValue(o);seTip('格式化完成');}catch(e){seTip('格式化失败: '+e.message,1);}} | |
| function seMinify(){var k=seFmtKind();if(!k)return seTip('当前类型不支持压缩',1);try{var v=getEditorValue(),o;if(k==='json')o=JSON.stringify(JSON.parse(v));else o=seMinMixed(v,k);setEditorValue(o);seTip('压缩完成');}catch(e){seTip('压缩失败: '+e.message,1);}} | |
| function seFindOpen(rep){var bar=document.getElementById('seFindbar');if(!bar)return;bar.classList.add('open');bar.classList.toggle('rep',!!rep);seFindIdx=-1;var fi=document.getElementById('seFind');if(fi){if(seTa.selectionStart!==seTa.selectionEnd)fi.value=getEditorValue().slice(seTa.selectionStart,seTa.selectionEnd);fi.focus();fi.select();}seFindRebuild();seFindStat();} | |
| function seFindClose(){var bar=document.getElementById('seFindbar');if(bar){bar.classList.remove('open','rep');}} | |
| function seFindQuery(){return (document.getElementById('seFind')||{}).value||'';} | |
| function seFindRebuild(){var q=seFindQuery(),v=getEditorValue();seFindPos=[];if(!q)return;var pos=0;while((pos=v.indexOf(q,pos))!==-1){seFindPos.push(pos);pos+=q.length;}} | |
| function seFindStat(){var st=document.getElementById('seFindStat');if(!st)return;var q=seFindQuery();if(!q){st.textContent='';return;}if(!seFindPos.length){st.textContent='无匹配';return;}if(seFindIdx>=0&&seFindIdx<seFindPos.length)st.textContent=(seFindIdx+1)+'/'+seFindPos.length;else st.textContent=seFindPos.length+' 处';} | |
| function seFindIdxByCursor(next){var q=seFindQuery(),s=seTa.selectionStart,e=seTa.selectionEnd,i;if(!seFindPos.length)return -1;for(i=0;i<seFindPos.length;i++){if(seFindPos[i]===s&&seFindPos[i]+q.length===e)break;}if(i<seFindPos.length)return next?(i+1)%seFindPos.length:(i-1+seFindPos.length)%seFindPos.length;if(next){for(i=0;i<seFindPos.length;i++){if(seFindPos[i]>s||(seFindPos[i]===s&&q.length))return i;}return 0;}for(i=seFindPos.length-1;i>=0;i--){if(seFindPos[i]<s)return i;}return seFindPos.length-1;} | |
| function seFindSelect(idx,focusTa){if(!seFindPos.length||idx<0||idx>=seFindPos.length)return;seFindIdx=idx;var q=seFindQuery(),p=seFindPos[idx],len=q.length;if(focusTa!==false)seTa.focus();requestAnimationFrame(function(){seTa.setSelectionRange(p,p+len);var lh=parseFloat(getComputedStyle(seTa).lineHeight)||21,before=getEditorValue().slice(0,p),ln=before.split('\n').length;seTa.scrollTop=Math.max(0,(ln-1)*lh-seTa.clientHeight/2+lh);seGutter.scrollTop=seTa.scrollTop;seRefreshGutter();seFindStat();});} | |
| function seFindNext(){seFindRebuild();if(!seFindPos.length){seFindStat();return;}var q=seFindQuery(),s=seTa.selectionStart,e=seTa.selectionEnd,idx;if(seFindIdx>=0&&seFindIdx<seFindPos.length&&seFindPos[seFindIdx]===s&&seFindPos[seFindIdx]+q.length===e)idx=(seFindIdx+1)%seFindPos.length;else if(seFindIdx<0)idx=0;else idx=seFindIdxByCursor(true);seFindSelect(idx);} | |
| function seFindPrev(){seFindRebuild();if(!seFindPos.length){seFindStat();return;}var q=seFindQuery(),s=seTa.selectionStart,e=seTa.selectionEnd,idx;if(seFindIdx>=0&&seFindIdx<seFindPos.length&&seFindPos[seFindIdx]===s&&seFindPos[seFindIdx]+q.length===e)idx=(seFindIdx-1+seFindPos.length)%seFindPos.length;else if(seFindIdx<0)idx=seFindPos.length-1;else idx=seFindIdxByCursor(false);seFindSelect(idx);} | |
| function seReplaceOne(){var q=seFindQuery(),r=(document.getElementById('seReplace')||{}).value||'';if(!q)return;var s=seTa.selectionStart,e=seTa.selectionEnd,v=getEditorValue();if(v.slice(s,e)===q){seTa.value=v.slice(0,s)+r+v.slice(e);seRefresh();seFindRebuild();if(!seFindPos.length){seFindStat();seTip('已全部替换');return;}var idx=0;for(var i=0;i<seFindPos.length;i++){if(seFindPos[i]>=s){idx=i;break;}}seFindSelect(idx);}else seFindNext();} | |
| function seReplaceAll(){var q=seFindQuery(),r=(document.getElementById('seReplace')||{}).value||'';if(!q)return;if(!confirm('确定将全部「'+q+'」替换为「'+r+'」?'))return;var n=getEditorValue().split(q).length-1;setEditorValue(getEditorValue().split(q).join(r));seFindRebuild();seFindStat();seTip('已替换 '+n+' 处');} | |
| seTa.addEventListener('input',function(){clearTimeout(seHlTimer);seRefreshGutter();updateStats();if(!seSilent){var t=seGetActiveTab();if(t){seSyncTabDirty(t);seRenderTabs();seUpdateHeader();}}seHlTimer=setTimeout(seRefreshHl,120);if(document.getElementById('seFindbar')&&document.getElementById('seFindbar').classList.contains('open')){seFindRebuild();seFindStat();}}); | |
| seTa.addEventListener('scroll',seSyncLayout); | |
| seTa.addEventListener('click',seRefreshGutter);seTa.addEventListener('keyup',seRefreshGutter); | |
| seTa.addEventListener('keydown',function(e){if(e.key==='Tab'&&!e.ctrlKey&&!e.metaKey&&!e.altKey){e.preventDefault();seInsert(' ');}else if((e.ctrlKey||e.metaKey)&&e.key==='s'){e.preventDefault();saveContent();}else if((e.ctrlKey||e.metaKey)&&e.key==='w'){e.preventDefault();if(seActiveId)seCloseTab(seActiveId);}else if((e.ctrlKey||e.metaKey)&&e.key==='Tab'){e.preventDefault();if(!seTabs.length)return;var ci=seTabs.findIndex(function(t){return t.id===seActiveId;});var ni=e.shiftKey?(ci-1+seTabs.length)%seTabs.length:(ci+1)%seTabs.length;seSwitchTab(seTabs[ni].id);}else if((e.ctrlKey||e.metaKey)&&e.altKey&&(e.key==='f'||e.key==='F')){e.preventDefault();seFormat();}else if((e.ctrlKey||e.metaKey)&&e.altKey&&(e.key==='m'||e.key==='M')){e.preventDefault();seMinify();}else if((e.ctrlKey||e.metaKey)&&e.key==='f'){e.preventDefault();seFindOpen(false);}else if((e.ctrlKey||e.metaKey)&&e.key==='h'){e.preventDefault();seFindOpen(true);}else if(e.key==='Escape'&&document.getElementById('seFindbar')&&document.getElementById('seFindbar').classList.contains('open')){e.preventDefault();seFindClose();}else if(e.key==='Enter'&&document.activeElement===document.getElementById('seFind')){e.preventDefault();seFindNext();}else if(e.key==='Enter'&&document.activeElement===document.getElementById('seReplace')){e.preventDefault();seReplaceOne();}}); | |
| var seFindEl=document.getElementById('seFind');if(seFindEl)seFindEl.addEventListener('input',function(){seFindIdx=-1;seFindRebuild();seFindStat();}); | |
| window.addEventListener('beforeunload',function(e){if(seHasUnsaved()){e.preventDefault();e.returnValue='';}}); | |
| seInitTabs(); | |
| seTreeInit(); | |
| seToolbarUpdate(); | |
| </script> | |
| <script> | |
| function resetContent(){var tab=seGetActiveTab();if(!tab)return;if(tab.dirty&&!confirm('放弃「'+tab.name+'」的修改?'))return;tab.content=tab.saved;tab.dirty=false;seSilent++;setEditorValue(tab.saved);seSilent=0;seRenderTabs();seUpdateHeader();} | |
| async function saveContent(){seFlushTab();var tab=seGetActiveTab();if(!tab)return;if(!tab.dirty||seTextEq(tab.content,tab.saved)){seTip('还没有改动',2);return;}var tb=document.getElementById('seToolbarSave'),ct=tab.content;if(tb){tb.disabled=true;tb.textContent='保存中...';}try{var d=await seApi({a:'sf',dir:tab.dir,fl:tab.name,ct:ct});if(d.s){tab.saved=ct;tab.content=ct;tab.dirty=false;originalContent=ct;seRenderTabs();seUpdateHeader();seTip('已保存 '+tab.name);}else alert('保存失败');}catch(x){alert('网络错误');}if(tb){tb.disabled=false;tb.textContent='保存';}} | |
| function qnEsc(s){return String(s).replace(/[&<>"']/g,function(m){return{'&':'&','<':'<','>':'>','"':'"',"'":'''}[m];});} | |
| function qnTip(msg,err){var t=document.createElement('div');t.textContent=msg;t.style.cssText='position:fixed;top:20px;right:20px;background:'+(err?'#ef4444':'#10b981')+';color:#fff;padding:12px 16px;border-radius:8px;z-index:10001;font-size:14px';document.body.appendChild(t);setTimeout(function(){t.remove();},2000);} | |
| function qnApplyItems(d){if(d&&d.s&&Array.isArray(d.items)){quickNav=d.items;return true;}return false;} | |
| var qnLoadSeq=0; | |
| function qnLoad(){var seq=++qnLoadSeq;return seApi({a:'qnls',_t:Date.now()}).then(function(d){if(seq!==qnLoadSeq)return quickNav;if(qnApplyItems(d))return quickNav;return quickNav;}).catch(function(){return quickNav;});} | |
| function qnShowMenuLoading(){var m=document.getElementById('qnMenu');if(m)m.innerHTML='<div class="qn-empty">加载中...</div>';} | |
| var qnMenuHome=null; | |
| function seQnClose(){var w=document.getElementById('qnWrap'),m=document.getElementById('qnMenu');if(w)w.classList.remove('open');if(m){m.style.cssText='';if(qnMenuHome&&m.parentNode===document.body)qnMenuHome.appendChild(m);qnMenuHome=null;}} | |
| function qnPositionMenu(){var m=document.getElementById('qnMenu'),w=document.getElementById('qnWrap');if(!m||!w||!w.classList.contains('open'))return;var b=w.querySelector('.qn-btn');if(!b)return;if(window.innerWidth<=768){if(!qnMenuHome&&m.parentNode!==document.body){qnMenuHome=w;document.body.appendChild(m);}}else if(qnMenuHome&&m.parentNode===document.body){qnMenuHome.appendChild(m);qnMenuHome=null;}m.style.display='block';m.style.position='fixed';m.style.zIndex='10070';var r=b.getBoundingClientRect(),vh=window.innerHeight,mob=window.innerWidth<=768;if(mob){var top=r.bottom+6,maxH=vh-top-10;if(maxH<100&&r.top>vh*0.35){var est=Math.min(m.scrollHeight||280,vh*0.55);top=Math.max(8,r.top-est-8);maxH=Math.min(est,r.top-16);}m.style.top=Math.max(8,top)+'px';m.style.left='10px';m.style.right='10px';m.style.bottom='auto';m.style.width='auto';m.style.maxHeight=Math.max(120,maxH)+'px';}else{m.style.top=(r.bottom+6)+'px';m.style.right='auto';m.style.bottom='auto';m.style.maxHeight='min(70vh,'+(vh-r.bottom-16)+'px)';var mw=Math.min(360,Math.max(260,m.offsetWidth||260));var left=r.right-mw;if(left<8)left=8;if(left+mw>window.innerWidth-8)left=window.innerWidth-mw-8;m.style.left=left+'px';m.style.width=mw+'px';}} | |
| function toggleQn(e){e.stopPropagation();var w=document.getElementById('qnWrap');if(!w)return;if(w.classList.contains('open')){seQnClose();return;}w.classList.add('open');qnShowMenuLoading();qnLoad().then(function(){renderQnMenu();requestAnimationFrame(function(){qnPositionMenu();requestAnimationFrame(qnPositionMenu);});});} | |
| if(!window._qnPosBound){window._qnPosBound=1;window.addEventListener('resize',function(){if(document.getElementById('qnWrap')&&document.getElementById('qnWrap').classList.contains('open'))qnPositionMenu();});window.addEventListener('scroll',function(){if(document.getElementById('qnWrap')&&document.getElementById('qnWrap').classList.contains('open'))qnPositionMenu();},true);} | |
| function qnEditUrl(p){var d=String(p).replace(/\\/g,'/');var i=d.lastIndexOf('/');return'?edit='+encodeURIComponent(p)+'&dir='+encodeURIComponent(i>0?d.substring(0,i):'.');} | |
| var qnMenuBound=false; | |
| function qnBindMenu(){if(qnMenuBound)return;var m=document.getElementById('qnMenu');if(!m)return;qnMenuBound=true;m.addEventListener('click',function(e){var rm=e.target.closest('.qn-rm');if(rm){e.preventDefault();e.stopPropagation();qnRemove(rm.getAttribute('data-idx'),e,rm.getAttribute('data-path'));return;}var item=e.target.closest('.qn-item[data-path]');if(item){e.preventDefault();e.stopPropagation();seQnClose();seOpenFile(item.getAttribute('data-path'));return;}if(e.target.closest('[data-qn-act="open"]')){e.preventDefault();e.stopPropagation();seQnClose();seOpenFilePrompt();return;}if(e.target.closest('[data-qn-act="list"]')){e.preventDefault();e.stopPropagation();seQnClose();seGoFileList();return;}if(e.target.closest('[data-qn-act="addcur"]')){qnAddCurrent(e);return;}if(e.target.closest('[data-qn-act="addmanual"]')){qnAddManual(e);return;}});} | |
| function qnMarkMenu(){var m=document.getElementById('qnMenu');if(!m||!m.querySelectorAll)return;var tab=typeof seGetActiveTab==='function'?seGetActiveTab():null;m.querySelectorAll('.qn-item[data-path]').forEach(function(el){var p=el.getAttribute('data-path');el.classList.toggle('cur',!!(tab&&typeof sePathEq==='function'&&sePathEq(p,tab.path)));});} | |
| window.qnMarkMenu=qnMarkMenu; | |
| function renderQnMenu(){var m=document.getElementById('qnMenu');if(!m)return;qnBindMenu();var tab=typeof seGetActiveTab==='function'?seGetActiveTab():null,h='';if(!quickNav.length)h+='<div class="qn-empty">暂无快捷项</div>';else quickNav.forEach(function(it,i){var p=String(it.p).replace(/\\/g,'/');var cur=tab&&typeof sePathEq==='function'&&sePathEq(p,tab.path);h+='<a class="qn-item'+(cur?' cur':'')+'" href="#" data-path="'+seTreeAttr(p)+'" data-idx="'+i+'" title="'+seTreeAttr(p)+'"><span>'+qnEsc(it.l)+'</span><button type="button" class="qn-rm" data-idx="'+i+'" data-path="'+seTreeAttr(p)+'" title="移除">×</button></a>';});h+='<div class="qn-sep"></div><button type="button" class="qn-add-cur" data-qn-act="open">+ 打开其他文件</button><button type="button" class="qn-add-cur" data-qn-act="list">📁 文件列表</button><button type="button" class="qn-add-cur" data-qn-act="addcur">+ 添加当前文件</button><div class="qn-form"><input id="qnLabel" placeholder="显示名称"><input id="qnPath" placeholder="路径,如 ftp.php"><button type="button" data-qn-act="addmanual">添加并打开</button></div>';m.innerHTML=h;if(document.getElementById('qnWrap')&&document.getElementById('qnWrap').classList.contains('open'))requestAnimationFrame(qnPositionMenu);} | |
| function qnSync(){qnLoad().then(function(){var w=document.getElementById('qnWrap');if(w&&w.classList.contains('open')){renderQnMenu();requestAnimationFrame(qnPositionMenu);}});} | |
| function qnRemove(i,e,path){e.preventDefault();e.stopPropagation();var data={a:'qndel'};if(path)data.p=path;else if(i!==null&&i!==''&&!isNaN(parseInt(i,10)))data.i=String(i);else return qnTip('无法移除',1);seApi(data).then(function(d){if(d.s){if(!qnApplyItems(d))return qnSync();renderQnMenu();requestAnimationFrame(qnPositionMenu);qnTip('已移除');}else qnTip(d.msg||'移除失败',1);}).catch(function(){qnTip('网络错误',1);});} | |
| function qnAddCurrent(e){e.stopPropagation();var tab=seGetActiveTab();if(!tab)return qnTip('无当前文件',1);seApi({a:'qnadd',l:tab.name,p:tab.path}).then(function(d){if(d.s){if(!qnApplyItems(d))return qnSync();renderQnMenu();requestAnimationFrame(qnPositionMenu);qnTip('已添加');}else qnTip(d.msg||'添加失败',1);}).catch(function(){qnTip('网络错误',1);});} | |
| function qnAddManual(e){e.stopPropagation();var l=document.getElementById('qnLabel'),p=document.getElementById('qnPath');if(!l||!p||!l.value.trim()||!p.value.trim())return qnTip('请填写名称和路径',1);var path=seResolvePath(p.value.trim());seApi({a:'qnadd',l:l.value.trim(),p:path}).then(function(d){if(d.s){if(!qnApplyItems(d))return qnSync();l.value='';p.value='';var openPath=(d.items&&d.items.length)?d.items[d.items.length-1].p:path;renderQnMenu();seQnClose();seOpenFile(openPath);qnTip('已打开');}else qnTip(d.msg||'添加失败',1);}).catch(function(){qnTip('网络错误',1);});} | |
| document.addEventListener('click',function(e){var w=document.getElementById('qnWrap'),m=document.getElementById('qnMenu');if(w&&w.classList.contains('open')&&!w.contains(e.target)&&!(m&&m.contains(e.target)))seQnClose();}); | |
| document.addEventListener('visibilitychange',function(){if(document.visibilityState==='visible')qnLoad();}); | |
| qnLoad(); | |
| </script> | |
| </body> | |
| </html> | |
| <?php exit;}?> | |
| <!DOCTYPE html> | |
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1.0"> | |
| <title><?php echo $n?></title> | |
| <script>var currentDir=<?php echo json_encode($cd); ?>;var quickNav=<?php echo json_encode(getQn($qn), JSON_UNESCAPED_UNICODE); ?>;</script> | |
| <style> | |
| *,*::before,*::after{margin:0;padding:0;box-sizing:border-box} | |
| :root{ | |
| --primary:#8b5cf6; | |
| --primary-hover:#7c3aed; | |
| --primary-light:rgba(139,92,246,0.08); | |
| --success:#10b981; | |
| --success-light:rgba(16,185,129,0.08); | |
| --warning:#f59e0b; | |
| --warning-light:rgba(245,158,11,0.08); | |
| --danger:#ef4444; | |
| --danger-light:rgba(239,68,68,0.08); | |
| --bg:#fafafa; | |
| --bg-white:#ffffff; | |
| --bg-gray:#f9fafb; | |
| --text:#1f2937; | |
| --text-secondary:#6b7280; | |
| --text-muted:#9ca3af; | |
| --border:#e5e7eb; | |
| --shadow:0 1px 3px rgba(0,0,0,0.04),0 1px 2px -1px rgba(0,0,0,0.04); | |
| --shadow-md:0 4px 6px -1px rgba(139,92,246,0.08),0 2px 4px -2px rgba(139,92,246,0.06); | |
| --shadow-lg:0 10px 15px -3px rgba(139,92,246,0.06),0 4px 6px -4px rgba(139,92,246,0.04); | |
| --radius:12px; | |
| --radius-sm:8px; | |
| --radius-xs:6px; | |
| } | |
| html{font-size:16px;-webkit-text-size-adjust:100%} | |
| @supports (-webkit-touch-callout:none){ | |
| input:not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=file]):not([type=button]):not([type=submit]):not([type=reset]), | |
| select,textarea{font-size:16px!important} | |
| } | |
| body{ | |
| font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif; | |
| background:var(--bg); | |
| color:var(--text); | |
| line-height:1.5; | |
| min-height:100vh; | |
| -webkit-font-smoothing:antialiased; | |
| } | |
| h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25} | |
| a{color:inherit;text-decoration:none} | |
| .svg-sprite{position:absolute;width:0;height:0;overflow:hidden} | |
| .icon{width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0} | |
| .icon svg{width:100%;height:100%;fill:currentColor} | |
| .file-icon-folder{color:#f59e0b} | |
| .file-icon-image{color:#10b981} | |
| .file-icon-video{color:#8b5cf6} | |
| .file-icon-audio{color:#ec4899} | |
| .file-icon-archive{color:#ef4444} | |
| .file-icon-document{color:#3b82f6} | |
| .file-icon-other{color:#6b7280} | |
| /* Header */ | |
| .header{ | |
| background:var(--bg-white); | |
| border-bottom:1px solid var(--border); | |
| padding:14px 24px; | |
| display:flex; | |
| align-items:center; | |
| justify-content:space-between; | |
| position:sticky; | |
| top:0; | |
| z-index:100; | |
| box-shadow:var(--shadow); | |
| } | |
| .header-left{ | |
| display:flex; | |
| align-items:center; | |
| gap:16px; | |
| } | |
| .logo-icon{ | |
| width:40px; | |
| height:40px; | |
| background:linear-gradient(135deg,var(--primary),var(--primary-hover)); | |
| border-radius:var(--radius-sm); | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| box-shadow:0 2px 8px rgba(139,92,246,0.25); | |
| } | |
| .logo-icon svg{width:22px;height:22px;fill:#ffffff} | |
| .breadcrumbs{ | |
| display:flex; | |
| align-items:center; | |
| gap:4px; | |
| font-size:14px; | |
| color:var(--text-secondary); | |
| } | |
| .breadcrumbs a{ | |
| padding:4px 10px; | |
| border-radius:var(--radius-xs); | |
| transition:all 0.2s ease; | |
| } | |
| .breadcrumbs a:hover{ | |
| background:var(--bg-gray); | |
| color:var(--primary); | |
| } | |
| .breadcrumbs span{ | |
| font-weight:500; | |
| color:var(--primary); | |
| background:var(--primary-light); | |
| padding:4px 10px; | |
| border-radius:var(--radius-xs); | |
| } | |
| .header-right{ | |
| display:flex; | |
| align-items:center; | |
| gap:12px; | |
| } | |
| .login-status{ | |
| font-size:13px; | |
| color:var(--text-muted); | |
| padding-right:8px; | |
| } | |
| .btn{ | |
| display:inline-flex; | |
| align-items:center; | |
| justify-content:center; | |
| gap:6px; | |
| padding:8px 16px; | |
| font-size:14px; | |
| font-weight:500; | |
| border-radius:var(--radius-sm); | |
| border:none; | |
| cursor:pointer; | |
| transition:all 0.2s ease; | |
| white-space:nowrap; | |
| } | |
| .btn:hover{transform:translateY(-1px)} | |
| .btn:active{transform:translateY(0)} | |
| .btn:disabled{opacity:0.5;cursor:not-allowed;transform:none} | |
| .btn-primary{ | |
| background:linear-gradient(135deg,var(--primary),var(--primary-hover)); | |
| color:#ffffff; | |
| box-shadow:0 2px 4px rgba(139,92,246,0.2); | |
| } | |
| .btn-primary:hover{ | |
| box-shadow:0 4px 8px rgba(139,92,246,0.25); | |
| } | |
| .btn-secondary{ | |
| background:var(--bg-white); | |
| color:var(--text); | |
| border:1px solid var(--border); | |
| } | |
| .btn-secondary:hover{ | |
| background:var(--bg-gray); | |
| border-color:#d1d5db; | |
| } | |
| .btn-success{ | |
| background:linear-gradient(135deg,var(--success),#059669); | |
| color:#ffffff; | |
| box-shadow:0 2px 4px rgba(16,185,129,0.2); | |
| } | |
| .btn-success:hover{box-shadow:0 4px 8px rgba(16,185,129,0.25)} | |
| .btn-danger{ | |
| background:linear-gradient(135deg,var(--danger),#dc2626); | |
| color:#ffffff; | |
| box-shadow:0 2px 4px rgba(239,68,68,0.2); | |
| } | |
| .btn-danger:hover{box-shadow:0 4px 8px rgba(239,68,68,0.25)} | |
| /* Main Content */ | |
| .main-container{ | |
| max-width:1400px; | |
| margin:0 auto; | |
| padding:0 24px 24px; | |
| } | |
| .message-area{ | |
| margin-bottom:16px; | |
| } | |
| .message{ | |
| display:flex; | |
| align-items:center; | |
| gap:10px; | |
| padding:14px 18px; | |
| border-radius:var(--radius-sm); | |
| font-size:14px; | |
| font-weight:500; | |
| animation:slideIn 0.3s ease; | |
| } | |
| @keyframes slideIn{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}} | |
| .message-success{background:var(--success-light);color:#059666;border:1px solid rgba(16,185,129,0.2)} | |
| .message-error{background:var(--danger-light);color:#dc2626;border:1px solid rgba(239,68,68,0.2)} | |
| .message-warning{background:var(--warning-light);color:#d97706;border:1px solid rgba(245,158,11,0.2)} | |
| /* Toolbar */ | |
| .toolbar{ | |
| display:flex; | |
| flex-wrap:wrap; | |
| align-items:center; | |
| gap:10px; | |
| margin-bottom:20px; | |
| padding:16px; | |
| background:var(--bg-white); | |
| border-radius:var(--radius); | |
| box-shadow:var(--shadow); | |
| } | |
| .toolbar .btn{font-size:13px;padding:8px 14px} | |
| /* Status Bar */ | |
| .status-bar{ | |
| display:flex; | |
| align-items:center; | |
| gap:20px; | |
| margin-bottom:16px; | |
| font-size:13px; | |
| color:var(--text-muted); | |
| } | |
| .status-bar strong{font-weight:500;color:var(--text-secondary)} | |
| .status-bar span{display:flex;align-items:center;gap:6px} | |
| /* File Table */ | |
| .file-table-container{ | |
| background:var(--bg-white); | |
| border-radius:var(--radius); | |
| box-shadow:var(--shadow); | |
| overflow:hidden; | |
| border:1px solid var(--border); | |
| } | |
| .file-table{ | |
| width:100%; | |
| border-collapse:collapse; | |
| } | |
| .file-table th{ | |
| text-align:left; | |
| padding:14px 16px; | |
| background:var(--bg-gray); | |
| border-bottom:1px solid var(--border); | |
| color:var(--text-secondary); | |
| font-weight:600; | |
| font-size:12px; | |
| text-transform:uppercase; | |
| letter-spacing:0.5px; | |
| } | |
| .file-table td{ | |
| padding:14px 16px; | |
| border-bottom:1px solid var(--border); | |
| color:var(--text); | |
| font-size:14px; | |
| vertical-align:middle; | |
| } | |
| .file-table tbody tr{ | |
| transition:background-color 0.2s ease; | |
| } | |
| .file-table tbody tr:hover{ | |
| background:var(--bg-gray); | |
| } | |
| .file-table tbody tr:last-child td{border-bottom:none} | |
| .file-table tbody tr:has(input:checked){ | |
| background:var(--primary-light); | |
| } | |
| .file-table th:first-child, | |
| .file-table td:first-child{ | |
| width:48px; | |
| text-align:center; | |
| padding:14px; | |
| } | |
| .file-table th:nth-child(2), | |
| .file-table td:nth-child(2){width:35%} | |
| .file-table th:nth-child(3), | |
| .file-table td:nth-child(3){width:12%} | |
| .file-table th:nth-child(4), | |
| .file-table td:nth-child(4){width:18%} | |
| .checkbox{ | |
| width:18px; | |
| height:18px; | |
| cursor:pointer; | |
| accent-color:var(--primary); | |
| border-radius:4px; | |
| } | |
| .file-name{ | |
| display:flex; | |
| align-items:center; | |
| gap:12px; | |
| font-weight:500; | |
| } | |
| .file-name a{ | |
| color:var(--text); | |
| transition:color 0.2s; | |
| } | |
| .file-name a:hover{color:var(--primary)} | |
| /* Action Buttons */ | |
| .action-buttons{ | |
| display:flex; | |
| gap:6px; | |
| align-items:center; | |
| } | |
| .action-btn{ | |
| padding:6px 12px; | |
| font-size:12px; | |
| font-weight:500; | |
| color:var(--text-secondary); | |
| background:var(--bg); | |
| border:1px solid var(--border); | |
| border-radius:var(--radius-xs); | |
| cursor:pointer; | |
| transition:all 0.2s ease; | |
| display:inline-flex; | |
| align-items:center; | |
| gap:4px; | |
| white-space:nowrap; | |
| } | |
| .action-btn:hover{ | |
| background:var(--bg-white); | |
| color:var(--text); | |
| border-color:#d1d5db; | |
| } | |
| .action-btn.download{color:var(--success);background:var(--success-light);border-color:rgba(16,185,129,0.2)} | |
| .action-btn.download:hover{background:rgba(16,185,129,0.12);color:#059666;border-color:rgba(16,185,129,0.3)} | |
| .action-btn.zip{color:var(--warning);background:var(--warning-light);border-color:rgba(245,158,11,0.2)} | |
| .action-btn.zip:hover{background:rgba(245,158,11,0.12);color:#d97706;border-color:rgba(245,158,11,0.3)} | |
| .action-btn.edit{color:var(--primary);background:var(--primary-light);border-color:rgba(139,92,246,0.2)} | |
| .action-btn.edit:hover{background:rgba(139,92,246,0.12);color:var(--primary-hover);border-color:rgba(139,92,246,0.3)} | |
| .action-btn.extract{color:#ec4899;background:rgba(236,72,153,0.08);border-color:rgba(236,72,153,0.2)} | |
| .action-btn.extract:hover{background:rgba(236,72,153,0.12);color:#db2777;border-color:rgba(236,72,153,0.3)} | |
| .action-btn.delete{color:var(--danger);background:var(--danger-light);border-color:rgba(239,68,68,0.2)} | |
| .action-btn.delete:hover{background:rgba(239,68,68,0.12);color:#dc2626;border-color:rgba(239,68,68,0.3)} | |
| .qn-wrap{position:relative;display:inline-flex} | |
| .qn-btn{min-width:108px} | |
| .qn-menu{position:absolute;top:calc(100% + 6px);right:0;min-width:280px;max-width:360px;background:var(--bg-white);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-lg);padding:6px 0;display:none;z-index:200;max-height:70vh;overflow:auto} | |
| .qn-wrap.open .qn-menu{display:block} | |
| .qn-item{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;color:var(--text);font-size:14px;text-decoration:none;transition:background .15s} | |
| .qn-item:hover{background:var(--primary-light);color:var(--primary)} | |
| .qn-item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1} | |
| .qn-rm{width:22px;height:22px;border:none;background:transparent;color:var(--text-muted);cursor:pointer;border-radius:4px;font-size:16px;line-height:1;flex-shrink:0} | |
| .qn-rm:hover{background:var(--danger-light);color:#dc2626} | |
| .qn-empty{padding:14px;font-size:13px;color:var(--text-muted);text-align:center} | |
| .qn-sep{height:1px;background:var(--border);margin:6px 0} | |
| .qn-form{padding:10px 14px 12px;display:flex;flex-direction:column;gap:8px} | |
| .qn-form input{padding:8px 10px;font-size:13px;border:1px solid var(--border);border-radius:6px;background:var(--bg)} | |
| .qn-form input:focus{outline:none;border-color:var(--primary)} | |
| .qn-form button{padding:8px;font-size:13px;border:none;border-radius:6px;background:var(--primary);color:#fff;cursor:pointer} | |
| .qn-form button:hover{background:var(--primary-hover)} | |
| /* Empty State */ | |
| .empty-state{ | |
| text-align:center; | |
| padding:60px 20px; | |
| color:var(--text-muted); | |
| } | |
| .empty-state-icon{ | |
| font-size:48px; | |
| margin-bottom:16px; | |
| opacity:0.5; | |
| } | |
| .empty-state p{font-size:14px} | |
| /* Chmod */ | |
| .chmod-hint{font-size:13px;color:var(--text-secondary);margin-bottom:12px;line-height:1.5} | |
| .chmod-list{max-height:220px;overflow:auto;border:1px solid var(--border);border-radius:var(--radius-sm);margin-bottom:16px;background:var(--bg)} | |
| .chmod-row{display:grid;grid-template-columns:1fr auto auto;gap:10px;align-items:center;padding:10px 12px;font-size:13px;border-bottom:1px solid var(--border)} | |
| .chmod-row:last-child{border-bottom:none} | |
| .chmod-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500} | |
| .chmod-name .tag{font-size:11px;color:var(--text-muted);font-weight:400;margin-left:6px} | |
| .chmod-val{font-family:'Consolas','Monaco','Courier New',monospace;font-size:12px;color:var(--text-secondary);white-space:nowrap} | |
| .chmod-presets{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px} | |
| .chmod-presets button{padding:6px 12px;font-size:12px;border:1px solid var(--border);border-radius:6px;background:var(--bg-white);color:var(--text-secondary);cursor:pointer} | |
| .chmod-presets button:hover{border-color:var(--primary);color:var(--primary);background:var(--primary-light)} | |
| .chmod-rec{margin-top:12px;font-size:13px;color:var(--text-secondary);display:flex;align-items:center;gap:8px} | |
| .chmod-rec input{width:16px;height:16px} | |
| .modal.chmod-wide{max-width:520px} | |
| /* 复制/移动目标选择 */ | |
| .fm-dest-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:1100;display:none;align-items:center;justify-content:center;padding:16px} | |
| .zip-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:1050;display:none;align-items:center;justify-content:center;padding:16px} | |
| .zip-overlay.open{display:flex} | |
| .zip-box{width:100%;max-width:380px;background:var(--bg-white);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-lg);overflow:hidden} | |
| .zip-head{padding:14px 16px;font-size:16px;font-weight:600;border-bottom:1px solid var(--border)} | |
| .zip-body{padding:14px 16px} | |
| .zip-hint{font-size:13px;color:var(--text-secondary);margin-bottom:12px} | |
| .zip-body .form-group{margin-bottom:0} | |
| .zip-foot{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border)} | |
| .up-conflict-all{display:flex;align-items:center;gap:8px;margin-top:12px;font-size:13px;color:var(--text-secondary);cursor:pointer} | |
| .up-conflict-all input{width:16px;height:16px;margin:0} | |
| .up-conflict-btns{display:flex;flex-direction:column;gap:8px;width:100%} | |
| .up-conflict-btns .btn{width:100%;justify-content:center} | |
| .fm-dest-overlay.open{display:flex} | |
| .fm-dest-box{width:100%;max-width:420px;max-height:min(560px,88vh);background:var(--bg-white);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-lg);display:flex;flex-direction:column;overflow:hidden} | |
| .fm-dest-head{padding:14px 16px;font-size:16px;font-weight:600;color:var(--text);border-bottom:1px solid var(--border)} | |
| .fm-dest-info{padding:10px 16px;font-size:13px;color:var(--text-secondary);line-height:1.5} | |
| .fm-dest-info em{font-style:normal;color:var(--primary);font-weight:500} | |
| .fm-dest-target{padding:0 16px 10px;font-size:12px;color:var(--text-muted);word-break:break-all;font-family:'Consolas','Monaco','Courier New',monospace} | |
| .fm-dest-target strong{color:var(--success);font-weight:600} | |
| .fm-dest-target .err{color:var(--danger);display:block;margin-top:4px} | |
| .fm-dest-nav{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--border);flex-shrink:0} | |
| .fm-dest-up,.fm-dest-root{padding:4px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text-secondary);cursor:pointer;font-size:12px;flex-shrink:0} | |
| .fm-dest-up:hover:not(:disabled),.fm-dest-root:hover:not(:disabled){border-color:var(--primary);color:var(--primary)} | |
| .fm-dest-up:disabled,.fm-dest-root:disabled{opacity:0.35;cursor:not-allowed} | |
| .fm-dest-cur{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:var(--text-muted);font-family:'Consolas','Monaco','Courier New',monospace} | |
| .fm-dest-list{flex:1;overflow:auto;padding:4px 0;min-height:140px;max-height:300px;-webkit-overflow-scrolling:touch} | |
| .fm-dest-item{display:flex;align-items:center;gap:8px;padding:9px 16px;font-size:13px;color:var(--text);cursor:pointer;user-select:none} | |
| .fm-dest-item:hover{background:var(--primary-light);color:var(--primary)} | |
| .fm-dest-item.disabled{opacity:0.35;cursor:not-allowed;pointer-events:none} | |
| .fm-dest-empty{padding:20px;text-align:center;font-size:13px;color:var(--text-muted);line-height:1.6} | |
| .fm-dest-foot{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border);flex-shrink:0} | |
| .fm-dest-foot button{padding:8px 16px;font-size:13px;border-radius:6px;cursor:pointer;border:1px solid var(--border);background:var(--bg-white);color:var(--text-secondary)} | |
| .fm-dest-foot button:hover{background:var(--bg);color:var(--text)} | |
| .fm-dest-foot .fm-dest-ok{border-color:var(--primary);background:var(--primary);color:#fff;font-weight:600} | |
| .fm-dest-foot .fm-dest-ok:hover{background:var(--primary-hover);border-color:var(--primary-hover);color:#fff} | |
| .fm-dest-foot .fm-dest-ok:disabled{opacity:0.45;cursor:not-allowed} | |
| /* Modal */ | |
| .modal-overlay{ | |
| position:fixed; | |
| inset:0; | |
| background:rgba(0,0,0,0.4); | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| z-index:1000; | |
| opacity:0; | |
| visibility:hidden; | |
| transition:opacity 0.15s ease; | |
| } | |
| .modal-overlay.active{opacity:1;visibility:visible} | |
| .modal{ | |
| background:var(--bg-white); | |
| border-radius:var(--radius); | |
| width:100%; | |
| max-width:420px; | |
| max-height:85vh; | |
| overflow:hidden; | |
| box-shadow:var(--shadow-lg); | |
| } | |
| .modal-header{ | |
| padding:18px 20px; | |
| border-bottom:1px solid var(--border); | |
| font-size:17px; | |
| font-weight:600; | |
| display:flex; | |
| align-items:center; | |
| gap:10px; | |
| } | |
| .modal-header::before{ | |
| content:''; | |
| width:4px; | |
| height:18px; | |
| background:var(--primary); | |
| border-radius:2px; | |
| } | |
| .modal-body{ | |
| padding:20px; | |
| } | |
| .modal-footer{ | |
| padding:14px 20px; | |
| background:var(--bg-gray); | |
| border-top:1px solid var(--border); | |
| display:flex; | |
| justify-content:flex-end; | |
| gap:10px; | |
| } | |
| .form-group{margin-bottom:18px} | |
| .form-group label{ | |
| display:block; | |
| margin-bottom:8px; | |
| font-size:14px; | |
| font-weight:500; | |
| color:var(--text-secondary); | |
| } | |
| .form-group input{ | |
| width:100%; | |
| padding:12px 14px; | |
| font-size:16px; | |
| border:2px solid var(--border); | |
| border-radius:var(--radius-sm); | |
| background:var(--bg); | |
| transition:all 0.2s ease; | |
| } | |
| .form-group input:hover{border-color:#d1d5db} | |
| .form-group input:focus{ | |
| outline:none; | |
| border-color:var(--primary); | |
| background:var(--bg-white); | |
| box-shadow:0 0 0 4px var(--primary-light); | |
| } | |
| .form-group input::placeholder{color:var(--text-muted)} | |
| /* Upload Area */ | |
| .upload-area{ | |
| text-align:center; | |
| padding:36px 20px; | |
| border:2px dashed var(--border); | |
| border-radius:var(--radius-sm); | |
| cursor:pointer; | |
| color:var(--text-muted); | |
| transition:all 0.2s ease; | |
| } | |
| .upload-area:hover{ | |
| border-color:var(--primary); | |
| background:var(--primary-light); | |
| color:var(--primary); | |
| } | |
| .upload-area-icon{ | |
| font-size:32px; | |
| margin-bottom:10px; | |
| opacity:0.7; | |
| } | |
| .upload-btns{display:flex;gap:10px;justify-content:center;margin-top:16px;flex-wrap:wrap} | |
| .upload-btns .btn{min-width:118px} | |
| .upload-area-hint{font-size:12px;margin-top:8px;opacity:0.85;line-height:1.5} | |
| /* Preview Modal */ | |
| .preview-overlay{ | |
| position:fixed; | |
| inset:0; | |
| background:rgba(0,0,0,0.85); | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| z-index:2000; | |
| opacity:0; | |
| visibility:hidden; | |
| transition:opacity 0.2s ease; | |
| } | |
| .preview-overlay.active{opacity:1;visibility:visible} | |
| .preview-content{ | |
| position:relative; | |
| max-width:90vw; | |
| max-height:90vh; | |
| } | |
| .preview-content img, | |
| .preview-content video{ | |
| max-width:100%; | |
| max-height:85vh; | |
| display:block; | |
| border-radius:var(--radius-sm); | |
| box-shadow:0 25px 50px -12px rgba(0,0,0,0.5); | |
| } | |
| .preview-close{ | |
| position:absolute; | |
| top:-48px; | |
| right:0; | |
| width:40px; | |
| height:40px; | |
| font-size:24px; | |
| color:#ffffff; | |
| background:rgba(255,255,255,0.1); | |
| border:none; | |
| border-radius:50%; | |
| cursor:pointer; | |
| transition:background 0.2s ease; | |
| } | |
| .preview-close:hover{background:rgba(255,255,255,0.2)} | |
| /* Toast */ | |
| .toast{ | |
| position:fixed; | |
| top:20px; | |
| right:20px; | |
| padding:14px 20px; | |
| background:var(--bg-white); | |
| color:var(--text); | |
| border-radius:var(--radius-sm); | |
| box-shadow:var(--shadow-lg); | |
| font-size:14px; | |
| font-weight:500; | |
| z-index:10000; | |
| display:flex; | |
| flex-direction:column; | |
| gap:10px; | |
| animation:slideInRight 0.3s ease; | |
| border:1px solid var(--border); | |
| } | |
| @keyframes slideInRight{from{opacity:0;transform:translateX(20px)}to{opacity:1;transform:translateX(0)}} | |
| .toast progress{ | |
| width:180px; | |
| height:5px; | |
| border-radius:3px; | |
| overflow:hidden; | |
| background:var(--border); | |
| } | |
| .toast progress::-webkit-progress-value{ | |
| background:var(--primary); | |
| border-radius:3px; | |
| transition:width 0.3s ease; | |
| } | |
| /* Responsive */ | |
| @media(max-width:768px){ | |
| .header{padding:12px 16px;flex-wrap:wrap;gap:12px} | |
| .header-left{flex-wrap:wrap;order:1} | |
| .header-right{order:2;flex-wrap:wrap} | |
| .main-container{padding:16px} | |
| .toolbar{padding:12px} | |
| .toolbar .btn{flex:1;min-width:calc(50% - 5px)} | |
| .file-table th:nth-child(3), | |
| .file-table td:nth-child(3), | |
| .file-table th:nth-child(4), | |
| .file-table td:nth-child(4), | |
| .file-table th:nth-child(5), | |
| .file-table td:nth-child(5){display:none} | |
| .file-table td:nth-child(2){max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} | |
| .file-table td:last-child,.file-table th:last-child{width:auto;min-width:148px} | |
| .action-buttons{flex-wrap:wrap;gap:4px;justify-content:flex-start} | |
| .action-btn{padding:4px 6px;font-size:10px;flex-shrink:0} | |
| .breadcrumbs{font-size:13px} | |
| .login-status{display:none} | |
| .header-right{width:100%;gap:8px} | |
| .header-right .btn,.header-right .qn-wrap{flex:1 1 auto;min-width:0} | |
| .qn-wrap.open .qn-menu{position:fixed;left:12px;right:12px;min-width:0;max-width:none;z-index:1000;max-height:min(75vh,calc(100vh - 72px));-webkit-overflow-scrolling:touch} | |
| .qn-btn{min-width:0;font-size:13px;padding:8px 10px} | |
| .qn-form input,.qn-form button{width:100%;box-sizing:border-box} | |
| .qn-form button{padding:10px;white-space:nowrap} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <svg class="svg-sprite"> | |
| <symbol id="icon-folder" viewBox="0 0 24 24"><path d="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2z"/></symbol> | |
| <symbol id="icon-folder-open" viewBox="0 0 24 24"><path d="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10z"/></symbol> | |
| <symbol id="icon-file" viewBox="0 0 24 24"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></symbol> | |
| <symbol id="icon-image" viewBox="0 0 24 24"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></symbol> | |
| <symbol id="icon-video" viewBox="0 0 24 24"><path d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4h-4z"/></symbol> | |
| <symbol id="icon-audio" viewBox="0 0 24 24"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></symbol> | |
| <symbol id="icon-archive" viewBox="0 0 24 24"><path d="M20 6h-4V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-6 0h-4V4h4v2z"/></symbol> | |
| <symbol id="icon-file-text" viewBox="0 0 24 24"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></symbol> | |
| <symbol id="icon-refresh" viewBox="0 0 24 24"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></symbol> | |
| <symbol id="icon-save" viewBox="0 0 24 24"><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/></symbol> | |
| <symbol id="icon-copy" viewBox="0 0 24 24"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></symbol> | |
| <symbol id="icon-cut" viewBox="0 0 24 24"><path d="M9.64 7.64c.23-.5.36-1.05.36-1.64 0-2.21-1.79-4-4-4S2 3.79 2 6s1.79 4 4 4c.59 0 1.14-.13 1.64-.36L10 12l-2.36 2.36C7.14 14.13 6.59 14 6 14c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4c0-.59-.13-1.14-.36-1.64L12 14l7 7h3v-1L9.64 7.64zM6 8c-1.1 0-2-.89-2-2s.9-2 2-2 2 .89 2 2-.9 2-2 2zm0 12c-1.1 0-2-.89-2-2s.9-2 2-2 2 .89 2 2-.9 2-2 2zm6-7.5c-.28 0-.5-.22-.5-.5s.22-.5.5-.5.5.22.5.5-.22.5-.5.5zM19 3l-6 6 2 2 7-7V3z"/></symbol> | |
| <symbol id="icon-delete" viewBox="0 0 24 24"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></symbol> | |
| <symbol id="icon-download" viewBox="0 0 24 24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></symbol> | |
| <symbol id="icon-upload" viewBox="0 0 24 24"><path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"/></symbol> | |
| <symbol id="icon-check" viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></symbol> | |
| <symbol id="icon-close" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></symbol> | |
| <symbol id="icon-warning" viewBox="0 0 24 24"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></symbol> | |
| <symbol id="icon-password" viewBox="0 0 24 24"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/></symbol> | |
| <symbol id="icon-lock" viewBox="0 0 24 24"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/></symbol> | |
| </svg> | |
| <div class="header"> | |
| <div class="header-left"> | |
| <div class="logo-icon"><svg><use href="#icon-folder"></use></svg></div> | |
| <div class="breadcrumbs" id="breadcrumbs"></div> | |
| </div> | |
| <div class="header-right"> | |
| <div class="qn-wrap" id="qnWrap"> | |
| <button type="button" class="btn btn-secondary qn-btn" onclick="toggleQn(event)">快速编辑 ▾</button> | |
| <div class="qn-menu" id="qnMenu"></div> | |
| </div> | |
| <span class="login-status">已登录</span> | |
| <button class="btn btn-secondary" onclick="om('cpw')"> | |
| <span class="icon"><svg><use href="#icon-password"></use></svg></span> | |
| 修改密码 | |
| </button> | |
| <button class="btn btn-secondary" onclick="logout()">退出</button> | |
| </div> | |
| </div> | |
| <div class="main-container"> | |
| <div class="message-area" id="msgArea"></div> | |
| <div class="toolbar"> | |
| <button class="btn btn-primary" onclick="om('cf')"> | |
| <span class="icon"><svg><use href="#icon-file"></use></svg></span> | |
| 新建文件 | |
| </button> | |
| <button class="btn btn-primary" onclick="om('cd')"> | |
| <span class="icon"><svg><use href="#icon-folder"></use></svg></span> | |
| 新建文件夹 | |
| </button> | |
| <button class="btn btn-primary" onclick="ocp_sel()"> | |
| <span class="icon"><svg><use href="#icon-copy"></use></svg></span> | |
| 复制 | |
| </button> | |
| <button class="btn btn-primary" onclick="omv_sel()"> | |
| <span class="icon"><svg><use href="#icon-cut"></use></svg></span> | |
| 移动 | |
| </button> | |
| <button class="btn btn-secondary" onclick="ozip_sel()"> | |
| <span class="icon"><svg><use href="#icon-archive"></use></svg></span> | |
| 压缩 | |
| </button> | |
| <button class="btn btn-danger" onclick="odel_sel()"> | |
| <span class="icon"><svg><use href="#icon-delete"></use></svg></span> | |
| 删除 | |
| </button> | |
| <button class="btn btn-success" onclick="om('dlurl')"> | |
| <span class="icon"><svg><use href="#icon-download"></use></svg></span> | |
| 离线下载 | |
| </button> | |
| <button class="btn btn-secondary" onclick="om('up')"> | |
| <span class="icon"><svg><use href="#icon-upload"></use></svg></span> | |
| 上传文件 | |
| </button> | |
| <button class="btn btn-secondary" onclick="ochmod_sel()"> | |
| <span class="icon"><svg><use href="#icon-lock"></use></svg></span> | |
| 权限 | |
| </button> | |
| </div> | |
| <div class="file-table-container"> | |
| <table class="file-table"> | |
| <thead> | |
| <tr> | |
| <th style="width:40px;text-align:center"><input type="checkbox" class="checkbox" onchange="toggleAll(this)"></th> | |
| <th>名称</th> | |
| <th>大小</th> | |
| <th>修改时间</th> | |
| <th style="width:88px">权限</th> | |
| <th>操作</th> | |
| </tr> | |
| </thead> | |
| <tbody id="fileList"></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div class="modal-overlay" id="mo" onclick="if(event.target===this)this.classList.remove('active')"> | |
| <div class="modal" onclick="event.stopPropagation()"> | |
| <div class="modal-header" id="mt">操作</div> | |
| <div class="modal-body" id="mb"></div> | |
| <div class="modal-footer"> | |
| <button class="btn btn-secondary" onclick="document.getElementById('mo').classList.remove('active')">取消</button> | |
| <button class="btn btn-primary" id="ms">确定</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="preview-overlay" id="pvmo" onclick="if(event.target===this)this.classList.remove('active')"> | |
| <div class="preview-content" onclick="event.stopPropagation()"> | |
| <div class="pvc" id="pvcc"></div> | |
| <button class="preview-close" onclick="document.getElementById('pvmo').classList.remove('active')">×</button> | |
| </div> | |
| </div> | |
| <div class="modal-overlay" id="cpwmo" onclick="if(event.target===this)this.classList.remove('active')"> | |
| <div class="modal" onclick="event.stopPropagation()" style="max-width:380px"> | |
| <div class="modal-header">修改密码</div> | |
| <div class="modal-body" style="padding-top:12px"> | |
| <div class="form-group"><label>原密码</label><input type="password" id="cpwOld" placeholder="输入原密码"></div> | |
| <div class="form-group"><label>新密码</label><input type="password" id="cpwNew" placeholder="输入新密码"></div> | |
| <div class="form-group"><label>确认密码</label><input type="password" id="cpwConfirm" placeholder="再次输入新密码"></div> | |
| </div> | |
| <div class="modal-footer"> | |
| <button class="btn btn-secondary" onclick="document.getElementById('cpwmo').classList.remove('active')">取消</button> | |
| <button class="btn btn-primary" onclick="cpw()">确认修改</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="zip-overlay" id="zipOverlay" onclick="if(event.target===this)zipClose()"> | |
| <div class="zip-box" onclick="event.stopPropagation()"> | |
| <div class="zip-head">压缩为 ZIP</div> | |
| <div class="zip-body"> | |
| <p class="zip-hint">已选 <span id="zipCount">0</span> 项</p> | |
| <div class="form-group"><label for="zipName">压缩包名称</label><input type="text" id="zipName" placeholder="如 backup.zip" autocomplete="off"></div> | |
| </div> | |
| <div class="zip-foot"> | |
| <button type="button" class="btn btn-secondary" onclick="zipClose()">取消</button> | |
| <button type="button" class="btn btn-primary" onclick="zipDo()">开始压缩</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="zip-overlay" id="upConflictOverlay" onclick="if(event.target===this)uploadConflictPick('cancel')"> | |
| <div class="zip-box" onclick="event.stopPropagation()"> | |
| <div class="zip-head">文件已存在</div> | |
| <div class="zip-body"> | |
| <p class="zip-hint"><strong id="upConflictName" style="word-break:break-all"></strong> 已存在,请选择处理方式:</p> | |
| <label class="up-conflict-all"><input type="checkbox" id="upConflictAll"> 全部应用此选择(本次上传)</label> | |
| </div> | |
| <div class="zip-foot up-conflict-btns"> | |
| <button type="button" class="btn btn-primary" onclick="uploadConflictPick('replace')">替换</button> | |
| <button type="button" class="btn btn-secondary" onclick="uploadConflictPick('copy')">保留为副本</button> | |
| <button type="button" class="btn btn-secondary" onclick="uploadConflictPick('cancel')">取消</button> | |
| </div> | |
| </div> | |
| </div> | |
| <input type="file" id="upfile" multiple style="display:none" onchange="uploadFilePick(this,false)"> | |
| <input type="file" id="updir" multiple webkitdirectory directory style="display:none" onchange="uploadFilePick(this,true)"> | |
| <div class="fm-dest-overlay" id="fmDestOverlay" onclick="if(event.target===this)fmDestClose()"> | |
| <div class="fm-dest-box" onclick="event.stopPropagation()"> | |
| <div class="fm-dest-head" id="fmDestHead">选择目标文件夹</div> | |
| <div class="fm-dest-info" id="fmDestInfo"></div> | |
| <div class="fm-dest-target" id="fmDestTarget"></div> | |
| <div class="fm-dest-nav"> | |
| <button type="button" class="fm-dest-up" id="fmDestUp" onclick="fmDestUp()">↑ 上级</button> | |
| <button type="button" class="fm-dest-root" id="fmDestRoot" onclick="fmDestGoRoot()">根目录</button> | |
| <span class="fm-dest-cur" id="fmDestCur" title="">.</span> | |
| </div> | |
| <div class="fm-dest-list" id="fmDestList"></div> | |
| <div class="fm-dest-foot"> | |
| <button type="button" onclick="fmDestClose()">取消</button> | |
| <button type="button" class="fm-dest-ok" id="fmDestOk" onclick="fmDestConfirm()">确认</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| var currentItems=[]; | |
| var currentAction=''; | |
| var currentNames=[]; | |
| function escapeHtml(text){return text.replace(/[&<>"']/g,function(m){return{'&':'&','<':'<','>':'>','"':'"','\'':'''}[m]})} | |
| function renderBreadcrumbs(dir){var parts=dir.split('/').filter(function(p){return p});var h='<a onclick="navigateTo(\'.\')">根目录</a>';var p='';parts.forEach(function(x,i){p+=(p?'/':'')+x;h+=i===parts.length-1?'<span> / '+escapeHtml(x)+'</span>':'<span> / </span><a onclick="navigateTo(\''+p+'\')">'+escapeHtml(x)+'</a>';});if(parts.length===0)h+='<span> / 根目录</span>';document.getElementById('breadcrumbs').innerHTML=h;} | |
| function renderFileList(items){currentItems=items;zipItemDirMap=null;var tbody=document.getElementById('fileList');if(items.length===0){tbody.innerHTML='<tr><td colspan="6" class="empty-state"><div class="empty-state-icon"><span class="icon" style="width:48px;height:48px;opacity:0.5"><svg><use href="#icon-folder-open"></use></svg></span></div><p>此目录为空</p></td></tr>';return;} | |
| var imgs=['jpg','jpeg','png','gif','bmp','webp','svg','ico'];var videos=['mp4','webm','ogg','mov','avi','mkv','flv','wmv','m4v'];var audios=['mp3','wav','oga','flac','aac','m4a','wma'];var zips=['zip','7z','rar','tar','gz','bz2','xz'];var docs=['txt','md','json','xml','html','htm','css','js','php','py','java','c','cpp','h','go','rs','yaml','yml','ini','conf'];var ej=function(s){return String(s).replace(/[\\"']/g,function(c){return'\\'+c;})};var gi=function(ext,d){if(d)return'folder';if(imgs.includes(ext))return'image';if(videos.includes(ext))return'video';if(audios.includes(ext))return'audio';if(zips.includes(ext))return'archive';if(docs.includes(ext))return'file-text';return'file'};var gc=function(t){var m={folder:'folder',image:'image',video:'video',audio:'audio',archive:'archive','file-text':'document','file':'other'};return'file-icon-'+(m[t]||'other')}; | |
| var html=items.map(function(i){var e=i.n.split('.').pop().toLowerCase();var ic=gi(e,i.d);var cc=gc(ic);var nh=i.d?'<span class="icon '+cc+'" style="width:20px;height:20px"><svg><use href="#icon-'+ic+'"></use></svg></span><a href="javascript:void(0)" onclick="navigateTo(\''+ej(i.p)+'\')">'+escapeHtml(i.n)+'</a>':'<span class="icon '+cc+'" style="width:20px;height:20px"><svg><use href="#icon-'+ic+'"></use></svg></span>'+(imgs.includes(e)||videos.includes(e)?'<a href="javascript:void(0)" onclick="pv(\''+ej(i.p)+'\',\''+e+'\')">'+escapeHtml(i.n)+'</a>':'<span>'+escapeHtml(i.n)+'</span>');var ac='<button class="action-btn download" onclick="downloadItem(\''+ej(i.n)+'\')">下载</button>';if(i.d)ac+='<button class="action-btn zip" onclick="zipItem(\''+ej(i.n)+'\')">压缩</button>';else{ac+='<a class="action-btn edit" href="javascript:void(0)" onclick="editItem(\''+ej(i.n)+'\')">编辑</a>';if(zips.includes(e))ac+='<button class="action-btn extract" onclick="extractItem(\''+ej(i.n)+'\')">解压</button>';}ac+='<button class="action-btn" onclick="orn(\''+ej(i.n)+'\')">重命名</button><button class="action-btn delete" onclick="deleteItem(\''+ej(i.n)+'\')">删除</button>';var pm=i.pm?'<code style="font-size:12px" title="'+escapeHtml(i.ps||'')+'">'+escapeHtml(i.pm)+'</code>':'-';return'<tr><td style="text-align:center"><input type="checkbox" class="checkbox" name="sn" value="'+escapeHtml(i.n)+'" onchange="updateCheckState()"></td><td><div class="file-name">'+nh+'</div></td><td>'+i.szf+'</td><td>'+i.mt+'</td><td>'+pm+'</td><td><div class="action-buttons">'+ac+'</div></td></tr>';}).join('');tbody.innerHTML=html;} | |
| function updateCheckState(){var a=document.querySelectorAll('input[name="sn"]');var c=document.querySelectorAll('input[name="sn"]:checked');if(a.length>0){var h=document.querySelector('.file-table thead input[type="checkbox"]');if(h)h.checked=c.length===a.length;}}function toggleAll(cb){document.querySelectorAll('input[name="sn"]').forEach(function(e){e.checked=cb.checked;});} | |
| async function loadDirectory(dir){var fd=new FormData();fd.append('a','ls');fd.append('dir',dir);var r=await fetch('',{method:'POST',body:fd});var d=await r.json();if(d.s){currentDir=d.dir;renderBreadcrumbs(d.dir);renderFileList(d.items);history.pushState({dir:dir},'','?dir='+encodeURIComponent(dir));}}function navigateTo(dir){loadDirectory(dir);} | |
| async function logout(){var fd=new FormData();fd.append('a','lo');await fetch('',{method:'POST',body:fd});location.reload();} | |
| function showMsg(msg,type){var a=document.getElementById('msgArea');var c=type==='s'?'message-success':type==='e'?'message-error':'message-warning';var ic=type==='s'?'check':type==='e'?'close':'warning';a.innerHTML='<div class="message '+c+'"><span class="icon" style="margin-right:8px"><svg><use href="#icon-'+ic+'"></use></svg></span> '+msg+'</div>';setTimeout(function(){a.innerHTML='';},5000);}function fs(b){var u=['B','KB','MB','GB'];var i=0;while(b>=1024&&i<3){b/=1024;i++;}return Math.round(b*100)/100+' '+u[i];}function showToast(msg,pct){var t=document.createElement('div');t.className='toast';t.innerHTML=msg+(pct!==undefined?'<br><progress value="'+pct+'" max="100"></progress>':'');document.body.appendChild(t);return t;}function fmtElapsed(sec){sec=Math.max(0,+sec||0);if(sec<60)return Math.round(sec*100)/100+' 秒';var m=Math.floor(sec/60),s=Math.round((sec-m*60)*10)/10;if(m<60)return m+' 分 '+s+' 秒';var h=Math.floor(m/60);m=m%60;return h+' 小时 '+m+' 分 '+s+' 秒';}function longTaskFetch(label,fd,done){var t0=Date.now(),t=showToast(label+'... 0 秒'),iv=setInterval(function(){t.innerHTML=label+'... '+fmtElapsed((Date.now()-t0)/1000);},200);fetch('',{method:'POST',body:fd,credentials:'same-origin',cache:'no-store'}).then(function(r){return r.text().then(function(text){var d;try{d=JSON.parse(text);}catch(e){var hint=text&&text.trim()?text.trim().replace(/\s+/g,' ').slice(0,160):('HTTP '+r.status);throw new Error(hint);}return d;});}).then(function(d){clearInterval(iv);t.remove();var tm=d.timef||fmtElapsed(d.time!=null?d.time:(Date.now()-t0)/1000);done(d,tm);}).catch(function(err){clearInterval(iv);t.remove();showMsg(label+'失败: '+(err&&err.message?err.message:'网络错误或请求中断'),'e');});} | |
| function om(t){currentAction=t;var mo=document.getElementById('mo'),mt=document.getElementById('mt'),mb=document.getElementById('mb'),ms=document.getElementById('ms'),modal=mo?mo.querySelector('.modal'):null;if(modal)modal.classList.remove('chmod-wide');if(t==='cpw'){document.getElementById('cpwOld').value='';document.getElementById('cpwNew').value='';document.getElementById('cpwConfirm').value='';document.getElementById('cpwmo').classList.add('active');setTimeout(function(){document.getElementById('cpwOld').focus();},100);return;} | |
| mo.classList.add('active');if(t==='cf'){mt.textContent='新建文件';mb.innerHTML='<div class="form-group"><label>文件名</label><input type="text" id="nin" placeholder="输入文件名"></div>';ms.onclick=cf;ms.style.display='inline-flex';ms.textContent='确定';} | |
| else if(t==='cd'){mt.textContent='新建文件夹';mb.innerHTML='<div class="form-group"><label>文件夹名</label><input type="text" id="nin" placeholder="输入文件夹名"></div>';ms.onclick=cd;ms.style.display='inline-flex';ms.textContent='确定';} | |
| else if(t==='up'){mt.textContent='上传文件 / 文件夹';mb.innerHTML='<div id="uploadDrop" class="upload-area"><div class="upload-area-icon"><span class="icon" style="width:48px;height:48px"><svg><use href="#icon-upload"></use></svg></span></div><p>拖拽文件或文件夹到此处上传</p><p class="upload-area-hint">支持多选文件;选择文件夹将保留目录结构</p><div class="upload-btns"><button type="button" class="btn btn-primary" onclick="event.stopPropagation();document.getElementById(\'upfile\').click()">选择文件</button><button type="button" class="btn btn-secondary" onclick="event.stopPropagation();document.getElementById(\'updir\').click()">选择文件夹</button></div></div>';ms.style.display='none';setTimeout(initUploadDrop,50);} | |
| else if(t==='dlurl'){mt.textContent='离线下载';mb.innerHTML='<div class="form-group"><label>下载链接</label><input type="text" id="dlurl" placeholder="输入文件URL地址"></div>';ms.onclick=dlurl;ms.style.display='inline-flex';ms.textContent='开始下载';setTimeout(function(){document.getElementById('dlurl').focus();},100);}} | |
| function cf(){var n=document.getElementById('nin').value;if(!n)return;var fd=new FormData();fd.append('a','cf');fd.append('dir',currentDir);fd.append('n',n);fetch('',{method:'POST',body:fd}).then(function(r){return r.json();}).then(function(d){if(d.s){showMsg('文件创建成功: '+d.n,'s');loadDirectory(currentDir);}else{showMsg('创建失败: '+d.e,'e');}document.getElementById('mo').classList.remove('active');});} | |
| function cd(){var n=document.getElementById('nin').value;if(!n)return;var fd=new FormData();fd.append('a','cd');fd.append('dir',currentDir);fd.append('n',n);fetch('',{method:'POST',body:fd}).then(function(r){return r.json();}).then(function(d){if(d.s){showMsg('文件夹创建成功: '+d.n,'s');loadDirectory(currentDir);}else{showMsg('创建失败: '+d.e,'e');}document.getElementById('mo').classList.remove('active');});} | |
| function uploadHasDirPaths(files){for(var i=0;i<files.length;i++){var r=files[i].webkitRelativePath||files[i].relativePath||'';if(r&&r.indexOf('/')>=0)return true;}return false;} | |
| function uploadFilePick(input,isDir){var files=input&&input.files;if(!files||!files.length)return;uploadFilesBatch(files,isDir);input.value='';} | |
| var upConflictAll=null,upConflictPending=null; | |
| function uploadConflictReset(){upConflictAll=null;var el=document.getElementById('upConflictAll');if(el)el.checked=false;} | |
| function uploadConflictClose(){var o=document.getElementById('upConflictOverlay');if(o)o.classList.remove('open');upConflictPending=null;} | |
| function uploadConflictPick(mode){var p=upConflictPending;if(!p)return;var allEl=document.getElementById('upConflictAll');if(allEl&&allEl.checked&&mode!=='cancel')upConflictAll=mode;uploadConflictClose();if(p.resolve)p.resolve(mode);} | |
| function uploadConflictAsk(name){return new Promise(function(resolve){if(upConflictAll){resolve(upConflictAll);return;}upConflictPending={resolve:resolve};var nm=document.getElementById('upConflictName');if(nm)nm.textContent=name;var o=document.getElementById('upConflictOverlay');if(o)o.classList.add('open');else resolve('replace');});} | |
| function uploadCheckExists(dir,rel,name,url){var fd=new FormData();fd.append('a','upchk');fd.append('dir',dir);if(rel)fd.append('rel',rel);else fd.append('n',name||'');return fetch(url||'',{method:'POST',body:fd,credentials:'same-origin',cache:'no-store'}).then(function(r){return r.json();});} | |
| function uploadSendFile(dir,file,rel,mode,url){return new Promise(function(resolve){var xhr=new XMLHttpRequest();var fd=new FormData();fd.append('a','up');fd.append('dir',dir);fd.append('fl',file,file.name||'file');if(rel)fd.append('rel',rel);if(mode)fd.append('mode',mode);xhr.onload=function(){try{resolve(JSON.parse(xhr.responseText));}catch(e){resolve({s:false,e:'响应异常'});}};xhr.onerror=function(){resolve({s:false,e:'网络错误'});};xhr.open('POST',url||'');xhr.send(fd);});} | |
| async function uploadFileWithConflict(dir,file,rel,url){var name=rel||file.name||'file';var chk=await uploadCheckExists(dir,rel,file.name,url);if(!chk.s)return chk;var mode='';if(chk.exists){mode=await uploadConflictAsk(chk.n||name);if(mode==='cancel')return{s:false,e:'已取消',skip:true};}var d=await uploadSendFile(dir,file,rel,mode,url);if(d.conflict&&!mode){mode=await uploadConflictAsk(d.n||name);if(mode==='cancel')return{s:false,e:'已取消',skip:true};return uploadSendFile(dir,file,rel,mode,url);}return d;} | |
| function uploadOneFile(file,rel,cb){uploadFileWithConflict(currentDir,file,rel,'').then(cb);} | |
| function uploadFilesBatch(files,isDir){var n=files.length;if(!n)return;uploadConflictReset();var t=showToast('上传 0/'+n+' …'),ok=0,fail=0,skip=0,lastErr='',i=0;(function next(){if(i>=n){t.remove();loadDirectory(currentDir);if(fail)showMsg('上传完成:'+ok+' 成功,'+fail+' 失败'+(skip?','+skip+' 已跳过':'')+(lastErr?'('+lastErr+')':''),'e');else if(skip)showMsg('上传完成:'+ok+' 成功,'+skip+' 已跳过','w');else{showMsg('已上传 '+ok+' 个文件','s');document.getElementById('mo').classList.remove('active');}return;}var file=files[i],rel=isDir&&(file.webkitRelativePath||file.relativePath)?(file.webkitRelativePath||file.relativePath):'';t.innerHTML='上传 '+(i+1)+'/'+n+':'+(rel||file.name);uploadOneFile(file,rel,function(d){if(d.s)ok++;else if(d.skip)skip++;else{fail++;lastErr=d.e||'失败';}i++;next();});})();} | |
| function initUploadDrop(){var dz=document.getElementById('uploadDrop');if(!dz||dz._upBound)return;dz._upBound=1;['dragenter','dragover'].forEach(function(ev){dz.addEventListener(ev,function(e){e.preventDefault();e.stopPropagation();dz.style.borderColor='var(--primary)';dz.style.background='var(--primary-light)';});});['dragleave','drop'].forEach(function(ev){dz.addEventListener(ev,function(e){e.preventDefault();e.stopPropagation();dz.style.borderColor='';dz.style.background='';});});dz.addEventListener('drop',function(e){var fl=e.dataTransfer&&e.dataTransfer.files;if(!fl||!fl.length)return;uploadFilesBatch(fl,uploadHasDirPaths(fl));});} | |
| function downloadItem(n){window.location.href='?dw='+encodeURIComponent(n)+'&dir='+encodeURIComponent(currentDir);} | |
| var zipItemDirMap=null; | |
| function zipBuildDirMap(){var m=Object.create(null),i;for(i=0;i<currentItems.length;i++)m[currentItems[i].n]=!!currentItems[i].d;zipItemDirMap=m;} | |
| function zipDefaultName(ns){if(!ns||!ns.length)return'archive.zip';if(ns.length===1){var n=ns[0],isDir=zipItemDirMap?!!zipItemDirMap[n]:false;if(isDir)return n+'.zip';var dot=n.lastIndexOf('.');return(dot>0?n.slice(0,dot):n)+'.zip';}var d=new Date(),p=function(x){return String(x).padStart(2,'0');};return'selected_'+d.getFullYear()+p(d.getMonth()+1)+p(d.getDate())+'_'+p(d.getHours())+p(d.getMinutes())+p(d.getSeconds())+'.zip';} | |
| function zipClose(){var o=document.getElementById('zipOverlay');if(o)o.classList.remove('open');} | |
| function zipOpen(){var o=document.getElementById('zipOverlay'),inp=document.getElementById('zipName'),cnt=document.getElementById('zipCount');if(!o||!inp)return;zipBuildDirMap();if(cnt)cnt.textContent=String(currentNames.length);inp.value=zipDefaultName(currentNames);o.classList.add('open');requestAnimationFrame(function(){inp.focus();inp.select();});} | |
| function zipDo(){var inp=document.getElementById('zipName'),name=inp?inp.value.trim():'';if(!name)return showMsg('请输入压缩包名称','w');if(!currentNames||!currentNames.length)return showMsg('没有选中项目','w');var fd=new FormData();fd.append('a','zp');fd.append('dir',currentDir);fd.append('zfn',name);currentNames.forEach(function(n){fd.append('n[]',n);});zipClose();longTaskFetch('压缩',fd,function(d,tm){if(d.s){showMsg(d.msg||('压缩成功,耗时 '+tm),'s');loadDirectory(currentDir);}else showMsg(d.msg||('压缩失败,耗时 '+tm),'e');});} | |
| function zipItem(n){currentNames=[n];zipOpen();} | |
| function ozip_sel(){var ns=getSelNames();if(!ns.length)return showMsg('请先选择要压缩的项目','w');currentNames=ns;zipOpen();} | |
| function extractItem(n){if(!confirm('确定要解压 '+n+' 吗?'))return;var fd=new FormData();fd.append('a','ex');fd.append('dir',currentDir);fd.append('n',n);longTaskFetch('解压 '+n,fd,function(d,tm){if(d.s){showMsg(d.msg||('解压成功,耗时 '+tm),'s');loadDirectory(currentDir);}else showMsg(d.msg||('解压失败,耗时 '+tm),'e');});} | |
| function editItem(n){window.location.href='?edit='+encodeURIComponent(currentDir+'/'+n)+'&dir='+encodeURIComponent(currentDir);} | |
| function orn(on){om('rn');document.getElementById('mt').textContent='重命名';document.getElementById('mb').innerHTML='<div class="form-group"><label>新名称</label><input type="text" id="nn" value="'+on+'" placeholder="输入新名称"></div>';document.getElementById('ms').onclick=function(){var nn=document.getElementById('nn').value;if(!nn||nn===on)return;var fd=new FormData();fd.append('a','rn');fd.append('dir',currentDir);fd.append('on',on);fd.append('nn',nn);fetch('',{method:'POST',body:fd}).then(function(r){return r.json();}).then(function(d){if(d.s){showMsg('重命名成功','s');loadDirectory(currentDir);}else{showMsg('重命名失败','e');}document.getElementById('mo').classList.remove('active');});};} | |
| function deleteItem(n){if(!confirm('确定要删除 "'+n+'" 吗?此操作不可恢复!'))return;var fd=new FormData();fd.append('a','del');fd.append('dir',currentDir);fd.append('n[]',n);fetch('',{method:'POST',body:fd}).then(function(r){return r.json();}).then(function(d){showMsg(d.msg,d.s?'s':'e');loadDirectory(currentDir);});} | |
| function odel_sel(){var ns=Array.from(document.querySelectorAll('input[name="sn"]:checked')).map(function(e){return e.value;});if(ns.length===0)return showMsg('请先选择要删除的项目','w');if(!confirm('确定要删除 '+ns.length+' 个项目吗?此操作不可恢复!'))return;var fd=new FormData();fd.append('a','del');fd.append('dir',currentDir);ns.forEach(function(n){fd.append('n[]',n);});fetch('',{method:'POST',body:fd}).then(function(r){return r.json();}).then(function(d){showMsg(d.msg,d.s?'s':'e');loadDirectory(currentDir);});} | |
| var fmDestAction='',fmDestPickDir='.',fmDestParent=null; | |
| function fmNormPath(p){p=String(p).replace(/\\/g,'/').replace(/\/+/g,'/');if(!p||p==='.')return '.';var parts=p.split('/'),out=[],i,s;for(i=0;i<parts.length;i++){s=parts[i];if(!s||s==='.')continue;if(s==='..'){if(out.length&&out[out.length-1]!=='..')out.pop();else out.push('..');}else out.push(s);}return out.length?out.join('/'):'.';} | |
| function fmPathEq(a,b){return fmNormPath(a)===fmNormPath(b);} | |
| function fmFmtDir(d){d=fmNormPath(d||'.');return d==='.'?'/ (根目录)':'/'+d;} | |
| function fmAttr(s){return String(s).replace(/&/g,'&').replace(/"/g,'"');} | |
| function fmItemIsDir(n){for(var i=0;i<currentItems.length;i++){if(currentItems[i].n===n)return!!currentItems[i].d;}return false;} | |
| function fmSelSrcPath(n){return fmNormPath(currentDir==='.'?n:currentDir+'/'+n);} | |
| function fmDestInvalidMsg(destDir){destDir=fmNormPath(destDir);var srcDir=fmNormPath(currentDir);if(fmDestAction==='cp'&&fmPathEq(destDir,srcDir))return'目标目录与来源目录相同';if(fmDestAction==='mv'&&fmPathEq(destDir,srcDir))return'目标目录与来源目录相同,请选其他目录';var i,n,src,destPath,op;for(i=0;i<currentNames.length;i++){n=currentNames[i];src=fmSelSrcPath(n);destPath=fmNormPath(destDir==='.'?n:destDir+'/'+n);if(fmDestAction==='mv'&&fmPathEq(src,destPath))return'「'+n+'」目标与当前位置相同';if(fmItemIsDir(n)){op=src+'/';if(fmPathEq(destDir,src)||(destDir!=='.'&&destDir.indexOf(op)===0))return'不能将文件夹「'+n+'」'+(fmDestAction==='mv'?'移动到':'复制到')+'自身或其子目录';}}return'';} | |
| function fmDestRowInvalid(folderPath){var i;for(i=0;i<currentNames.length;i++){if(!fmItemIsDir(currentNames[i]))continue;var src=fmSelSrcPath(currentNames[i]),p=fmNormPath(folderPath),op=src+'/';if(fmPathEq(p,src)||(p!=='.'&&p.indexOf(op)===0))return true;}return false;} | |
| function fmDestUpdate(){var el=document.getElementById('fmDestTarget'),ok=document.getElementById('fmDestOk');if(!el)return;var dest=fmNormPath(fmDestPickDir),msg=fmDestInvalidMsg(dest),label=fmDestAction==='mv'?'移动到':'复制到';el.innerHTML=label+':<strong>'+escapeHtml(fmFmtDir(dest))+'</strong>'+(msg?'<span class="err">'+escapeHtml(msg)+'</span>':'');if(ok)ok.disabled=!!msg;} | |
| function fmDestClose(){var o=document.getElementById('fmDestOverlay');if(o)o.classList.remove('open');fmDestAction='';} | |
| function fmDestLoad(dir){dir=dir||'.';fmDestPickDir=dir;var cur=document.getElementById('fmDestCur'),up=document.getElementById('fmDestUp'),rootBtn=document.getElementById('fmDestRoot'),list=document.getElementById('fmDestList');if(cur){cur.textContent=fmFmtDir(dir);cur.title=dir;}fmDestUpdate();if(list)list.innerHTML='<div class="fm-dest-empty">加载中...</div>';var fd=new FormData();fd.append('a','ls');fd.append('dir',dir);fetch('',{method:'POST',body:fd,credentials:'same-origin',cache:'no-store'}).then(function(r){return r.json();}).then(function(d){if(!d.s){if(list)list.innerHTML='<div class="fm-dest-empty">加载失败</div>';return;}fmDestParent=d.parent!=null?d.parent:(dir!=='.'?'.':null);if(up)up.disabled=dir==='.';if(rootBtn)rootBtn.disabled=dir==='.';var dirs=(d.items||[]).filter(function(it){return it.d;});if(!list)return;if(!dirs.length){var hint=fmDestAction==='mv'?'确认移动':'确认复制';list.innerHTML='<div class="fm-dest-empty">当前目录下没有子文件夹<br><span style="font-size:12px">可点击「'+hint+'」到当前目录</span></div>';return;}var h='';dirs.forEach(function(it){var p=String(it.p).replace(/\\/g,'/'),bad=fmDestRowInvalid(p);h+='<div class="fm-dest-item'+(bad?' disabled':'')+'" data-path="'+fmAttr(p)+'"><span>📁</span><span>'+escapeHtml(it.n)+'</span></div>';});list.innerHTML=h;}).catch(function(){if(list)list.innerHTML='<div class="fm-dest-empty">加载失败</div>';});} | |
| function fmDestUp(){if(fmDestPickDir==='.')return;fmDestLoad(fmDestParent||'.');} | |
| function fmDestGoRoot(){if(fmDestPickDir==='.')return;fmDestLoad('.');} | |
| function fmDestOpen(action){var ns=getSelNames();if(!ns.length)return showMsg('请先选择要'+(action==='mv'?'移动':'复制')+'的项目','w');currentNames=ns;fmDestAction=action;fmDestPickDir=fmNormPath(currentDir);var head=document.getElementById('fmDestHead'),info=document.getElementById('fmDestInfo'),ok=document.getElementById('fmDestOk'),o=document.getElementById('fmDestOverlay');if(head)head.textContent=action==='mv'?'选择移动目标':'选择复制目标';if(info)info.innerHTML='已选 <em>'+ns.length+'</em> 项,点击进入子文件夹,选好后点击「'+(action==='mv'?'确认移动':'确认复制')+'」';if(ok)ok.textContent=action==='mv'?'确认移动':'确认复制';if(o)o.classList.add('open');fmDestLoad(fmDestPickDir);} | |
| function fmDestConfirm(){if(!fmDestAction||!currentNames.length)return;var destDir=fmNormPath(fmDestPickDir),msg=fmDestInvalidMsg(destDir);if(msg)return showMsg(msg,'w');var fd=new FormData();fd.append('a',fmDestAction);fd.append('dir',currentDir);fd.append('d',destDir);currentNames.forEach(function(n){fd.append('n[]',n);});var btn=document.getElementById('fmDestOk');if(btn){btn.disabled=true;btn.textContent='处理中…';}fetch('',{method:'POST',body:fd,credentials:'same-origin',cache:'no-store'}).then(function(r){return r.json();}).then(function(d){if(btn){btn.disabled=false;btn.textContent=fmDestAction==='mv'?'确认移动':'确认复制';}showMsg(d.msg||'完成',d.s?'s':'e');if(d.s){fmDestClose();loadDirectory(fmDestAction==='mv'?destDir:currentDir);}}).catch(function(){if(btn){btn.disabled=false;btn.textContent=fmDestAction==='mv'?'确认移动':'确认复制';}showMsg((fmDestAction==='mv'?'移动':'复制')+'失败','e');});} | |
| function ocp_sel(){fmDestOpen('cp');} | |
| function omv_sel(){fmDestOpen('mv');} | |
| function getSelNames(){return Array.from(document.querySelectorAll('input[name="sn"]:checked')).map(function(e){return e.value;});} | |
| function ochmod_sel(){var ns=getSelNames();if(!ns.length)return showMsg('请先选择要修改权限的文件或文件夹','w');currentNames=ns;ochmodOpen();} | |
| function ochmodOpen(){var mo=document.getElementById('mo'),mt=document.getElementById('mt'),mb=document.getElementById('mb'),ms=document.getElementById('ms'),modal=mo?mo.querySelector('.modal'):null;if(!mo||!mb)return;currentAction='chmod';if(modal)modal.classList.add('chmod-wide');mt.textContent='修改权限';mb.innerHTML='<div class="chmod-hint">正在读取当前权限…</div>';ms.style.display='inline-flex';ms.textContent='应用';ms.onclick=chmodApply;mo.classList.add('active');chmodLoadStat();} | |
| function chmodPreset(v){var inp=document.getElementById('chmodMode');if(inp)inp.value=v;} | |
| function chmodRender(items){var mb=document.getElementById('mb');if(!mb)return;var h='<p class="chmod-hint">已选 '+items.length+' 项。下方为当前权限,修改后点击「应用」。</p><div class="chmod-list">';var octs={},hasDir=false;items.forEach(function(it){if(it.d)hasDir=true;var o=it.oct||'-';octs[o]=(octs[o]||0)+1;h+='<div class="chmod-row"><div class="chmod-name">'+escapeHtml(it.n)+(it.d?' <span class="tag">文件夹</span>':' <span class="tag">文件</span>')+'</div><span class="chmod-val">'+escapeHtml(o)+'</span><span class="chmod-val">'+escapeHtml(it.sym||'')+'</span></div>';});h+='</div><div class="form-group"><label>新权限</label><input type="text" id="chmodMode" placeholder="如 755、0644 或 rwxr-xr-x" autocomplete="off"></div><div class="chmod-presets"><button type="button" onclick="chmodPreset(\'755\')">755</button><button type="button" onclick="chmodPreset(\'644\')">644</button><button type="button" onclick="chmodPreset(\'775\')">775</button><button type="button" onclick="chmodPreset(\'600\')">600</button><button type="button" onclick="chmodPreset(\'rwxr-xr-x\')">rwxr-xr-x</button></div>';if(hasDir)h+='<label class="chmod-rec"><input type="checkbox" id="chmodRecursive"> 递归修改文件夹内所有内容</label>';mb.innerHTML=h;var keys=Object.keys(octs);if(keys.length===1){var inp=document.getElementById('chmodMode');if(inp)inp.value=keys[0];}} | |
| function chmodLoadStat(){var fd=new FormData();fd.append('a','chmod');fd.append('dir',currentDir);currentNames.forEach(function(n){fd.append('n[]',n);});fetch('',{method:'POST',body:fd,credentials:'same-origin',cache:'no-store'}).then(function(r){return r.json();}).then(function(d){if(!d.s||!d.items||!d.items.length){showMsg(d.msg||'读取权限失败','e');document.getElementById('mo').classList.remove('active');return;}chmodRender(d.items);}).catch(function(){showMsg('读取权限失败','e');document.getElementById('mo').classList.remove('active');});} | |
| function chmodApply(){var modeEl=document.getElementById('chmodMode'),mode=modeEl?modeEl.value.trim():'';if(!mode)return showMsg('请输入新权限','w');var recEl=document.getElementById('chmodRecursive'),fd=new FormData();fd.append('a','chmod');fd.append('dir',currentDir);fd.append('mode',mode);if(recEl&&recEl.checked)fd.append('recursive','1');currentNames.forEach(function(n){fd.append('n[]',n);});var btn=document.getElementById('ms');if(btn){btn.disabled=true;btn.textContent='应用中…';}fetch('',{method:'POST',body:fd,credentials:'same-origin',cache:'no-store'}).then(function(r){return r.json();}).then(function(d){if(btn){btn.disabled=false;btn.textContent='应用';}showMsg(d.msg||'完成',d.s?'s':'e');if(d.s){document.getElementById('mo').classList.remove('active');loadDirectory(currentDir);}}).catch(function(){if(btn){btn.disabled=false;btn.textContent='应用';}showMsg('修改权限失败','e');});} | |
| async function dlurl(){ | |
| var u=document.getElementById('dlurl').value.trim(); | |
| if(u){ | |
| document.getElementById('mo').classList.remove('active'); | |
| var t=showToast('准备下载: '+u,0); | |
| // Progress poller | |
| var pd=false; | |
| var p_iv=setInterval(async function(){ | |
| if(pd) { clearInterval(p_iv); return; } | |
| try { | |
| var pr = await fetch('?a=progress'); | |
| var pi = await pr.json(); | |
| if(pi.s){ | |
| var sz = fs(pi.downloaded) + (pi.total > 0 ? ' / ' + fs(pi.total) : ''); | |
| var sp = fs(pi.speed) + '/s'; | |
| t.innerHTML = '下载中: ' + pi.pct + '%<br><span style="font-size:12px">'+sz+'</span> <span style="font-size:12px;opacity:0.8">('+sp+')</span><br><progress value="'+pi.pct+'" max="100"></progress>'; | |
| } | |
| } catch(e){} | |
| }, 1000); | |
| var fd=new FormData(); | |
| fd.append('a','dlurl'); | |
| fd.append('dir',currentDir); | |
| fd.append('u',u); | |
| try{ | |
| var r=await fetch('',{method:'POST',body:fd}); | |
| var d=await r.json(); | |
| pd=true; | |
| clearInterval(p_iv); | |
| t.innerHTML=d.msg+'<br><progress value="100" max="100"></progress>'; | |
| setTimeout(function(){t.remove();},1500); | |
| if(d.s)loadDirectory(currentDir); | |
| }catch(x){ | |
| pd=true; | |
| clearInterval(p_iv); | |
| showMsg('下载请求失败','e'); | |
| t.remove(); | |
| } | |
| } | |
| } | |
| function cpw(){var oldp=document.getElementById('cpwOld').value;var newp=document.getElementById('cpwNew').value;var confirmp=document.getElementById('cpwConfirm').value;if(newp!==confirmp)return alert('两次输入的密码不一致');if(newp.length<1)return alert('新密码不能为空');var fd=new FormData();fd.append('a','cpw');fd.append('old',oldp);fd.append('new',newp);fetch('',{method:'POST',body:fd}).then(function(r){return r.json();}).then(function(d){if(d.s){alert('密码修改成功');document.getElementById('cpwmo').classList.remove('active');}else{alert(d.msg);}});} | |
| function pv(p,e){var pt=p.toLowerCase();if(pt.match(/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/)){document.getElementById('pvcc').innerHTML='<img src="'+p+'" style="max-width:80vw;max-height:80vh">';document.getElementById('pvmo').classList.add('active');}else if(pt.match(/\.(mp4|webm|ogg|mov|avi|mkv|flv|wmv|m4v)$/)){document.getElementById('pvcc').innerHTML='<video src="'+p+'" controls style="max-width:80vw;max-height:80vh"></video>';document.getElementById('pvmo').classList.add('active');}else{}} | |
| function qnApi(data){var fd=new FormData();Object.keys(data).forEach(function(k){fd.append(k,data[k]);});return fetch('',{method:'POST',body:fd,credentials:'same-origin',cache:'no-store'}).then(function(r){return r.json();});} | |
| function qnApplyItems(d){if(d&&d.s&&Array.isArray(d.items)){quickNav=d.items;return true;}return false;} | |
| var qnLoadSeq=0; | |
| function qnLoad(){var seq=++qnLoadSeq;return qnApi({a:'qnls',_t:Date.now()}).then(function(d){if(seq!==qnLoadSeq)return quickNav;if(qnApplyItems(d))return quickNav;return quickNav;}).catch(function(){return quickNav;});} | |
| function qnShowMenuLoading(){var m=document.getElementById('qnMenu');if(m)m.innerHTML='<div class="qn-empty">加载中...</div>';} | |
| function qnAttr(s){return String(s).replace(/&/g,'&').replace(/"/g,'"').replace(/</g,'<');} | |
| function qnResolvePathFl(p){p=String(p).replace(/\\/g,'/').trim();if(!p)return'';if(p.indexOf('/')>=0||p.indexOf('..')===0)return p.replace(/\/+/g,'/');var d=typeof currentDir!=='undefined'?currentDir:'';if(!d||d==='.')return p;return (d+'/'+p).replace(/\/+/g,'/');} | |
| function toggleQn(e){e.stopPropagation();var w=document.getElementById('qnWrap');if(!w)return;if(w.classList.contains('open')){w.classList.remove('open');var m=document.getElementById('qnMenu');if(m)m.style.top='';return;}w.classList.add('open');qnShowMenuLoading();qnLoad().then(function(){renderQnMenu();var m=document.getElementById('qnMenu');if(m&&window.innerWidth<=768){var b=w.querySelector('.qn-btn');if(b){var r=b.getBoundingClientRect();m.style.top=(r.bottom+6)+'px';}}});} | |
| function qnEditUrl(p){var d=String(p).replace(/\\/g,'/');var i=d.lastIndexOf('/');return'?edit='+encodeURIComponent(p)+'&dir='+encodeURIComponent(i>0?d.substring(0,i):'.');} | |
| var qnMenuBoundFl=false; | |
| function qnBindMenuFl(){if(qnMenuBoundFl)return;var m=document.getElementById('qnMenu');if(!m)return;qnMenuBoundFl=true;m.addEventListener('click',function(e){var rm=e.target.closest('.qn-rm');if(rm){e.preventDefault();e.stopPropagation();qnRemove(rm.getAttribute('data-idx'),e,rm.getAttribute('data-path'));return;}if(e.target.closest('[data-qn-act="addmanual"]')){qnAddManual(e);return;}});} | |
| function renderQnMenu(){var m=document.getElementById('qnMenu');if(!m)return;qnBindMenuFl();var hint=currentDir&¤tDir!=='.'?'路径相对当前目录,如 nx.php':'文件路径,如 nx.php';var h='';if(!quickNav.length)h+='<div class="qn-empty">暂无导航,可在下方添加</div>';quickNav.forEach(function(it,i){var p=String(it.p).replace(/\\/g,'/');h+='<a class="qn-item" href="'+qnEditUrl(p)+'"><span>'+escapeHtml(it.l)+'</span><button type="button" class="qn-rm" data-idx="'+i+'" data-path="'+qnAttr(p)+'" title="移除">×</button></a>';});h+='<div class="qn-sep"></div><div class="qn-form"><input id="qnLabel" placeholder="显示名称"><input id="qnPath" placeholder="'+qnAttr(hint)+'"><button type="button" data-qn-act="addmanual">添加导航</button></div>';m.innerHTML=h;} | |
| function qnRemove(i,e,path){e.preventDefault();e.stopPropagation();var data={a:'qndel'};if(path)data.p=path;else if(i!==null&&i!==''&&!isNaN(parseInt(i,10)))data.i=String(i);else return showMsg('无法移除','e');qnApi(data).then(function(d){if(d.s){if(!qnApplyItems(d))return qnLoad().then(function(){var w=document.getElementById('qnWrap');if(w&&w.classList.contains('open'))renderQnMenu();});var w=document.getElementById('qnWrap');if(w&&w.classList.contains('open'))renderQnMenu();showMsg('已移除','s');}else showMsg(d.msg||'移除失败','e');});} | |
| function qnAddManual(e){e.stopPropagation();var l=document.getElementById('qnLabel'),p=document.getElementById('qnPath');if(!l||!p||!l.value.trim()||!p.value.trim())return showMsg('请填写名称和路径','w');var path=qnResolvePathFl(p.value.trim());qnApi({a:'qnadd',l:l.value.trim(),p:path}).then(function(d){if(d.s){if(!qnApplyItems(d))return qnLoad().then(renderQnMenu);renderQnMenu();l.value='';p.value='';showMsg('已添加','s');}else showMsg(d.msg||'添加失败','e');});} | |
| document.addEventListener('click',function(e){var w=document.getElementById('qnWrap'),m=document.getElementById('qnMenu');if(w&&w.classList.contains('open')&&!w.contains(e.target)&&!(m&&m.contains(e.target)))w.classList.remove('open');}); | |
| document.getElementById('qnWrap').addEventListener('click',function(e){e.stopPropagation();}); | |
| document.addEventListener('visibilitychange',function(){if(document.visibilityState==='visible')qnLoad();}); | |
| qnLoad(); | |
| (function(){var list=document.getElementById('fmDestList');if(list&&!list._fmBound){list._fmBound=1;list.addEventListener('click',function(e){var el=e.target.closest('.fm-dest-item');if(!el||el.classList.contains('disabled'))return;var p=el.getAttribute('data-path');if(p)fmDestLoad(p);});}if(!document._fmDestKey){document._fmDestKey=1;document.addEventListener('keydown',function(e){if(e.key==='Escape'){var uo=document.getElementById('upConflictOverlay');if(uo&&uo.classList.contains('open'))uploadConflictPick('cancel');else{var zo=document.getElementById('zipOverlay');if(zo&&zo.classList.contains('open'))zipClose();else{var o=document.getElementById('fmDestOverlay');if(o&&o.classList.contains('open'))fmDestClose();}}}});}})(); | |
| loadDirectory(currentDir); | |
| </script> | |
| </body> | |
| </html> |
Xet Storage Details
- Size:
- 209 kB
- Xet hash:
- c2e6a66e418a174525406abe9f5dcb8b09bc1126897df237bc8b86eec4ced8c5
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.