browse.js 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -3
- package/dist/browse.min.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browse.js",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "A lightweight yet versatile file browser written in vanilla JS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file",
|
|
@@ -21,12 +21,11 @@
|
|
|
21
21
|
"type": "commonjs",
|
|
22
22
|
"main": "dist/browse.js",
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "tsc &&
|
|
24
|
+
"build": "tsc && cp src/browse.js dist/browse.js && lessc src/style.less dist/style.css",
|
|
25
25
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"less": "^4.5.1",
|
|
29
|
-
"terser": "^5.46.0",
|
|
30
29
|
"typescript": "^5.9.3"
|
|
31
30
|
}
|
|
32
31
|
}
|
package/dist/browse.min.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function svgDataUrlFromSvg(svg){return"data:image/svg+xml;utf8,"+encodeURIComponent(svg)}function escapeText(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function svgDocumentIcon(label,bg="#e9eefc",w=240,h=310){return svgDataUrlFromSvg(`<?xml version='1.0' encoding='utf-8'?><svg xmlns='http://www.w3.org/2000/svg' width='${w}' height='${h}' viewBox='0 0 ${w} ${h}'><path d='${`M0 0 H${w-84} L${w} 84 V${h} H0 Z`}' fill='${bg}' stroke='#0b12200a' stroke-linejoin='round'/><path d='${`M${w-84} 0 v84 h84 Z`}' fill='#0000001a' stroke='#0b122005'/><text x='50%' y='58%' dominant-baseline='middle' text-anchor='middle' font-family='Inter,Arial,Helvetica,sans-serif' font-size='36' fill='#0b1220'>${escapeText(label||"")}</text></svg>`)}function svgFolderIcon(label,bg="#ffd7a8",w=420,h=320){return svgDataUrlFromSvg(`<?xml version='1.0' encoding='utf-8'?><svg xmlns='http://www.w3.org/2000/svg' width='${w}' height='${h}' viewBox='0 0 ${w} ${h}'><rect x='6' y='34' rx='12' ry='12' width='${w-12}' height='${h-40}' fill='${bg}' stroke='#0b12200a'/><rect x='18' y='12' rx='8' ry='8' width='120' height='44' fill='${bg}'/><text x='50%' y='62%' dominant-baseline='middle' text-anchor='middle' font-family='Inter,Arial,Helvetica,sans-serif' font-size='36' fill='#0b1220'>${escapeText(label||"")}</text></svg>`)}const DEFAULT_ICON_RULES=[{icon:svgDocumentIcon("IMG","#c7ddff"),typeMatch:t=>t.startsWith("image/"),exts:[".png",".jpg",".jpeg",".gif",".webp",".bmp",".svg",".avif"]},{icon:svgDocumentIcon("PDF","#ffd7d7"),typeMatch:t=>"application/pdf"===t,exts:[".pdf"]},{icon:svgDocumentIcon("AUD","#e6dcff"),typeMatch:t=>t.startsWith("audio/"),exts:[".mp3",".wav",".m4a",".flac"]},{icon:svgDocumentIcon("VID","#dff7ff"),typeMatch:t=>t.startsWith("video/"),exts:[".mp4",".mov",".webm",".mkv"]},{icon:svgDocumentIcon("DOC","#dfe6ff"),typeMatch:t=>["application/msword","application/vnd.openxmlformats-officedocument.wordprocessingml.document"].includes(t),exts:[".doc",".docx",".odt"]},{icon:svgDocumentIcon("XLS","#e4ffd7"),typeMatch:t=>["text/csv","application/vnd.ms-excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"].includes(t),exts:[".xls",".xlsx",".ods",".csv"]},{icon:svgDocumentIcon("ZIP","#fffed7"),typeMatch:t=>t.includes("zip")||"application/x-zip-compressed"===t,exts:[".zip",".tar",".gz"]},{icon:svgDocumentIcon("TXT","#eaeaea"),typeMatch:t=>t.startsWith("text/"),exts:[".txt",".md",".log"]}];export class BrowseJS{constructor(container,files=[],opts={}){if(container instanceof Element)this.container=container;else{const containerElement=document.getElementById(container)||document.querySelector(container);if(!containerElement)throw new Error("Container not found: "+container);this.container=containerElement}this.opts=opts,this.iconRules=opts.icons&&opts.icons.rules?opts.icons.rules:DEFAULT_ICON_RULES,this.folderIcon=opts.icons&&opts.icons.folder?opts.icons.folder:svgFolderIcon(""),this.defaultIcon=opts.icons&&opts.icons.default?opts.icons.default:svgDocumentIcon("?","#e9eefc"),this.multi=Boolean(opts.multiSelect),this.selectedIndices=new Set,this.stack=[{name:opts.rootName||"Root",files:files}],this.breadcrumbEl=document.createElement("nav"),this.breadcrumbEl.className="breadcrumb",this.breadcrumbEl.setAttribute("aria-label","Breadcrumb"),this.crumbsWrap=document.createElement("div"),this.crumbsWrap.className="crumbs-wrap",this.controlsEl=document.createElement("div"),this.controlsEl.className="crumb-controls",this.breadcrumbEl.appendChild(this.crumbsWrap),this.breadcrumbEl.appendChild(this.controlsEl),this.detailsEl=document.createElement("aside"),this.detailsEl.className="details",this.detailsEl.setAttribute("aria-live","polite"),this.detailsEl.textContent=opts.detailsText||"Select a file",this.container.innerHTML="",this.galleryEl=document.createElement("div"),this.galleryEl.className="gallery-grid",this.container.appendChild(this.breadcrumbEl),this.container.appendChild(this.galleryEl),this.container.appendChild(this.detailsEl),this._fileInput=document.createElement("input"),this._fileInput.type="file",this._fileInput.style.display="none",this.container.appendChild(this._fileInput),this.container.classList.add("browsejs"),this.galleryEl.tabIndex=0,document.addEventListener("dragover",e=>{e.preventDefault(),this.container.classList.add("dragover")}),document.addEventListener("dragleave",e=>{this.container.classList.remove("dragover")}),this.container.addEventListener("drop",e=>{e.preventDefault(),this.container.classList.remove("dragover");const files=Array.from(e.dataTransfer&&e.dataTransfer.files||[]);0!==files.length&&this.handleDropFiles(files)}),this.render()}getIconForItem(item){if(item.children)return this.folderIcon;const type=item.meta&&item.meta.type?String(item.meta.type).toLowerCase():"",name=item.name?String(item.name).toLowerCase():"";if(type)for(const r of this.iconRules)if(r.typeMatch&&r.typeMatch(type))return r.icon;for(const r of this.iconRules)if(r.exts)for(const e of r.exts)if(name.endsWith(e))return r.icon;return this.defaultIcon}render(){this.galleryEl.innerHTML="";this.currentFiles().forEach((f,i)=>{const card=document.createElement("button");card.className="card",card.type="button",card.dataset.index=i,card.title=f.name;const img=document.createElement("img");img.alt=f.name||"",img.src=f.thumbnail||this.getIconForItem(f);const meta=document.createElement("div");meta.className="meta",meta.textContent=f.name,card.appendChild(img),card.appendChild(meta),f.children&&Array.isArray(f.children)?(card.addEventListener("click",()=>this.enterFolder(i)),card.addEventListener("keyup",e=>{"Enter"===e.key&&this.enterFolder(i)})):(card.addEventListener("click",()=>this.select(i)),card.addEventListener("keyup",e=>{"Enter"===e.key&&this.select(i)})),this.galleryEl.appendChild(card)}),this.updateSelectionUI(),this.renderBreadcrumb()}updateSelectionUI(){this.galleryEl.querySelectorAll(".card").forEach(c=>{const idx=Number(c.dataset.index);this.selectedIndices.has(idx)?c.classList.add("selected"):c.classList.remove("selected")}),this.renderDetails()}renderDetails(){if(!this.detailsEl)return;const activeList=this.currentFiles();if(0===this.selectedIndices.size)this.detailsEl.innerHTML='<p class="small">No selection</p>';else if(this.selectedIndices.size>1){const indices=Array.from(this.selectedIndices).sort();this.detailsEl.innerHTML="";const header=document.createElement("div");header.innerHTML=`<strong>${indices.length} item${indices.length>1?"s":""} selected</strong>`,this.detailsEl.appendChild(header);const list=document.createElement("div");list.className="small",list.innerHTML=indices.map(i=>`<div>${escapeText(activeList[i]&&activeList[i].name||"Untitled")}</div>`).join(""),this.detailsEl.appendChild(list)}else{const index=Array.from(this.selectedIndices)[0],item=activeList[index];if(!item)return void(this.detailsEl.innerHTML='<p class="small">No selection</p>');this.detailsEl.innerHTML="";const img=document.createElement("img");img.className="preview",img.src=item.thumbnail||this.galleryEl.querySelector(`.card[data-index="${index}"] img`)?.src||this.getIconForItem(item),img.alt=item.name||"preview";const name=document.createElement("div");name.innerHTML=`<strong>${item.name||"Untitled"}</strong>`;const meta=document.createElement("div");meta.className="small",item.meta&&"object"==typeof item.meta?meta.innerHTML=Object.entries(item.meta).map(([k,v])=>`<div><strong>${k}:</strong> ${v}</div>`).join(""):item.meta?meta.textContent=String(item.meta):meta.textContent="",this.detailsEl.appendChild(img),this.detailsEl.appendChild(name),this.detailsEl.appendChild(meta)}}renderBreadcrumb(){if(!this.breadcrumbEl)return;this.crumbsWrap.innerHTML="",this.stack.forEach((s,idx)=>{const btn=document.createElement("button");if(btn.type="button",btn.className="crumb",btn.textContent=s.name,btn.addEventListener("click",()=>this.goToCrumb(idx)),this.crumbsWrap.appendChild(btn),idx<this.stack.length-1){const sep=document.createElement("span");sep.className="sep",sep.textContent="»",this.crumbsWrap.appendChild(sep)}}),this.controlsEl.innerHTML="";const hasCreate="function"==typeof this.opts.onCreateFolder,hasUpload="function"==typeof this.opts.onUpload;if(hasCreate){const createBtn=document.createElement("button");createBtn.type="button",createBtn.className="crumb-action",createBtn.textContent="New Folder",createBtn.addEventListener("click",()=>this.handleCreateFolder()),this.controlsEl.appendChild(createBtn)}if(hasUpload){const uploadBtn=document.createElement("button");uploadBtn.type="button",uploadBtn.className="crumb-action",uploadBtn.textContent="Upload",uploadBtn.addEventListener("click",()=>this.handleUpload()),this.controlsEl.appendChild(uploadBtn)}}currentFolder(){return this.stack.map(s=>s.name+"/").slice(1).join()}currentFiles(){return this.stack[this.stack.length-1].files}addFiles(files){Array.isArray(files)||(files=[files]);this.currentFiles().push(...files),this.render()}getSelectedPaths(){const activeList=this.currentFiles(),basePath=this.currentFolder(),paths=[];return Array.from(this.selectedIndices).forEach(i=>{const item=activeList[i];item&&paths.push(basePath+item.name)}),paths}async handleCreateFolder(){if(this._creatingFolder)return;this._creatingFolder=!0;const current=this.currentFiles(),card=document.createElement("div");card.className="card creating",card.tabIndex=0;const img=document.createElement("img");img.alt="Folder",img.src=this.folderIcon;const meta=document.createElement("div");meta.className="meta";const nameEl=document.createElement("div");nameEl.className="editable-name",nameEl.contentEditable="true",nameEl.spellcheck=!1,nameEl.textContent="",meta.appendChild(nameEl),card.appendChild(img),card.appendChild(meta),this.galleryEl.insertBefore(card,this.galleryEl.firstChild),nameEl.focus();try{const sel=window.getSelection(),range=document.createRange();range.selectNodeContents(nameEl),sel.removeAllRanges(),sel.addRange(range)}catch(e){}const cleanup=()=>{this._creatingFolder=!1,card.parentNode&&card.parentNode.removeChild(card)},commit=async()=>{const name=nameEl.textContent.trim();if(name){if("function"==typeof this.opts.onCreateFolder){const res=await this.opts.onCreateFolder(name);if(!res)return void cleanup();current.splice(0,0,res)}else current.splice(0,0,{name:name,children:[]});this._creatingFolder=!1,this.render()}else cleanup()};nameEl.addEventListener("keydown",e=>{"Enter"===e.key?(e.preventDefault(),nameEl.blur()):"Escape"===e.key&&(e.preventDefault(),cleanup())}),nameEl.addEventListener("blur",()=>{setTimeout(()=>{this._creatingFolder&&commit()},0)},{once:!0})}async handleUpload(){"function"==typeof this.opts.onUpload&&(this._fileInput.value="",this._fileInput.multiple=!0,this._fileInput.onchange=async()=>{const files=Array.from(this._fileInput.files||[]);if(0!==files.length)try{const res=await this.opts.onUpload(files);if(res){const current=this.currentFiles();Array.isArray(res)?current.push(...res):current.push(res),this.render()}}catch(err){console.error("onUpload error",err)}},this._fileInput.click())}async handleDropFiles(files){if(files&&0!==files.length&&"function"==typeof this.opts.onUpload)try{const res=await this.opts.onUpload(files);if(res){const current=this.currentFiles();Array.isArray(res)?current.push(...res):current.push(res),this.render()}}catch(err){console.error("onUpload error",err)}}select(i){const activeList=this.currentFiles();if(this.multi?this.selectedIndices.has(i)?this.selectedIndices.delete(i):this.selectedIndices.add(i):this.selectedIndices.has(i)?this.selectedIndices.clear():(this.selectedIndices.clear(),this.selectedIndices.add(i)),this.updateSelectionUI(),"function"==typeof this.opts.onSelect){const indices=Array.from(this.selectedIndices).sort((a,b)=>a-b),items=indices.map(idx=>activeList[idx]);this.opts.onSelect(items,indices)}}enterFolder(i){const f=this.currentFiles()[i];f&&Array.isArray(f.children)&&(this.stack.push({name:f.name||"Folder",files:f.children}),this.selectedIndices.clear(),this.render())}goToCrumb(idx){this.stack=this.stack.slice(0,idx+1),this.selectedIndices.clear(),this.render()}}
|