मुफ़्त APK बनाएँ

JavaScript ब्रिज API

फ़ाइलें: उपयोगकर्ता का दिया हुआ फ़ोल्डर

एक बार चुनी गई डायरेक्टरी, जिसे बाद में पढ़ा और लिखा जा सकता है, और जिसकी अनुमति रीबूट के बाद भी बनी रहती है।

WebToApkFS.clearAccess हेल्पर#

WebToApkFS.clearAccess()

उदाहरण

दिया हुआ फ़ोल्डर भूल जाता है और अनुमति वापस Android को दे देता है।

लौटाता है: कुछ नहीं (तुरंत)। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Native Folder Access (SAF) टिक करें।

फ़ोल्डर के नाम के पास हमेशा डिसकनेक्ट करने का तरीका दें:

document.getElementById('disconnect').addEventListener('click', function () {
  if (!window.WebToApkFS) return;
  window.WebToApkFS.clearAccess();
  console.log('still connected?', window.WebToApkFS.hasAccess());   // false
  document.getElementById('folder-name').textContent = 'No folder chosen';
  document.getElementById('choose-folder').hidden = false;
});

एक बटन जो मौजूदा स्थिति के हिसाब से जोड़ता या डिसकनेक्ट करता है:

var btn = document.getElementById('folder-toggle');
function paint() {
  btn.textContent = window.WebToApkFS.hasAccess() ? 'Disconnect folder' : 'Connect a folder';
}

btn.addEventListener('click', async function () {
  if (!window.WebToApkFS) return;
  if (window.WebToApkFS.hasAccess()) {
    if (!confirm('Stop saving to this folder? Your files stay where they are.')) return;
    window.WebToApkFS.clearAccess();
  } else {
    await window.WebToApkFS.requestAccess();
  }
  paint();
});
if (window.WebToApkFS) paint();

खाते बदलें: फ़ोल्डर को साइन-इन की बाकी स्थिति के साथ भूल जाएँ:

function switchAccount() {
  sessionStorage.clear();
  if (window.WebToApkFS) window.WebToApkFS.clearAccess();   // the next account picks its own folder
  showLoginScreen();
}

ध्यान दें: फ़ाइलें जहाँ थीं वहीं रहती हैं; बस ऐप अब उन तक नहीं पहुँच सकता। फ़ोल्डर फिर इस्तेमाल करने के लिए टैप से WebToApkFS.requestAccess() कॉल करें।

WebToApkFS.delete हेल्पर#

WebToApkFS.delete(relativePath, options)

उदाहरण

दिए गए फ़ोल्डर के अंदर की फ़ाइल या फ़ोल्डर हटाता है।

लौटाता है: तुरंत एक बूलियन: हटाई जाने पर true। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Native Folder Access (SAF) टिक करें, और एक दिया हुआ फ़ोल्डर।

पूछने के बाद एक फ़ाइल हटाएँ:

if (window.WebToApkFS && confirm('Delete notes/old.txt?')) {
  var ok = window.WebToApkFS.delete('notes/old.txt');
  if (!ok) alert('Could not delete the file.');
}

फ़ोल्डर और उसके अंदर का सब कुछ हटाएँ - दें { recursive: true }:

if (window.WebToApkFS && confirm('Delete the whole 2025 folder?')) {
  var gone = window.WebToApkFS.delete('notes/2025', { recursive: true });
  console.log(gone ? 'deleted' : 'not deleted');
}

30 दिन से पुराने एक्सपोर्ट साफ़ करें, एक-एक फ़ाइल करके:

function pruneOldExports() {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return 0;
  var cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
  var removed = 0;
  window.WebToApkFS.list('exports').forEach(function (e) {
    if (e.isDirectory || e.lastModified >= cutoff) return;
    if (window.WebToApkFS.delete('exports/' + e.name)) removed++;
  });
  return removed;
}

if (confirm('Delete exports older than 30 days?')) {
  document.getElementById('status').textContent = pruneOldExports() + ' old exports deleted';
}

ध्यान दें: { recursive: true } के बिना फ़ोल्डर तभी हटता है जब वह खाली हो। दिया हुआ फ़ोल्डर खुद ('') कभी नहीं हटाया जा सकता। हटाना तुरंत होता है और कोई ट्रैश नहीं है, इसलिए हमेशा पहले उपयोगकर्ता से पुष्टि करें।

WebToApkFS.getAccessUri हेल्पर#

WebToApkFS.getAccessUri()

उदाहरण

दिए गए फ़ोल्डर का पता देता है, ताकि आप उपयोगकर्ता को दिखा सकें कि फ़ाइलें कहाँ जाती हैं।

लौटाता है: तुरंत एक स्ट्रिंग: content://com.android.externalstorage.documents/tree/primary%3ADocuments%2FNotes जैसा ट्री URI, या '' जब कोई फ़ोल्डर न दिया गया हो। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Native Folder Access (SAF) टिक करें।

Disconnect बटन के पास पढ़ने लायक फ़ोल्डर नाम दिखाएँ:

function connectedFolderName() {
  if (!window.WebToApkFS) return '';
  var uri = window.WebToApkFS.getAccessUri();
  if (!uri) return '';
  var tail = uri.split('/tree/')[1] || '';
  try { tail = decodeURIComponent(tail); } catch (e) {}
  return tail.split(':').pop() || tail;        // "Documents/Notes"
}

var name = connectedFolderName();
document.getElementById('folder-name').textContent = name ? 'Folder: ' + name : 'No folder chosen';

पता लगाएँ कि उपयोगकर्ता ने पिछली बार के बाद कोई दूसरा फ़ोल्डर चुना है, और पुराने फ़ोल्डर के बारे में जो कैश किया था उसे दोबारा बनाएँ:

function checkFolderChanged() {
  if (!window.WebToApkFS) return;
  var uri = window.WebToApkFS.getAccessUri();
  var last = '';
  try { last = localStorage.getItem('folderUri') || ''; } catch (e) {}
  if (uri && uri !== last) {
    rebuildFileIndex();                         // your app: re-read the new folder
    try { localStorage.setItem('folderUri', uri); } catch (e) {}
  }
}

checkFolderChanged();

Disconnect बटन जो फ़ोल्डर भूल जाता है और पुष्टि करता है कि वह हट गया:

document.getElementById('disconnect').addEventListener('click', function () {
  if (!window.WebToApkFS || !window.WebToApkFS.getAccessUri()) return;
  if (!confirm('Stop saving to this folder? Your files stay where they are.')) return;
  window.WebToApkFS.clearAccess();
  var still = window.WebToApkFS.getAccessUri();
  document.getElementById('folder-name').textContent = still ? 'Could not disconnect' : 'No folder chosen';
});

ध्यान दें: फ़ाइल वाले तरीके यह URI नहीं लेते - वे दिए गए फ़ोल्डर के सापेक्ष पाथ लेते हैं ('notes/today.txt')। URI का फ़ॉर्मैट उस Files प्रोवाइडर का है जिससे उपयोगकर्ता ने चुना, इसलिए इसे सिर्फ़ दिखाने के लिए इस्तेमाल करें।

WebToApkFS.hasAccess हेल्पर#

WebToApkFS.hasAccess()

उदाहरण

बताता है कि उपयोगकर्ता ने ऐप को पहले से कोई फ़ोल्डर दिया है या नहीं।

लौटाता है: तुरंत एक बूलियन। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Native Folder Access (SAF) टिक करें।

जवाब के हिसाब से "फ़ोल्डर चुनें" या फ़ाइल सूची दिखाएँ:

function renderFolderScreen() {
  if (!window.WebToApkFS) {
    document.getElementById('folder-panel').hidden = true;   // not in the app, or not enabled
    return;
  }
  var connected = window.WebToApkFS.hasAccess();
  document.getElementById('choose-folder').hidden = connected;
  document.getElementById('file-list').hidden = !connected;
  if (connected) {
    var entries = window.WebToApkFS.list('');
    document.getElementById('file-list').textContent =
      entries.map(function (e) { return e.name; }).join('\n');
  }
}

ऑटोसेव सिर्फ़ तब करें जब फ़ोल्डर जुड़ा हो, और न जुड़ा हो तो यह बताएँ:

var saveTimer = null;
document.getElementById('editor').addEventListener('input', function () {
  clearTimeout(saveTimer);
  saveTimer = setTimeout(function () {
    var status = document.getElementById('status');
    if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) {
      status.textContent = 'Choose a folder to turn on autosave';
      return;
    }
    var ok = window.WebToApkFS.writeText('notes/draft.txt', document.getElementById('editor').value);
    status.textContent = ok ? 'Saved' : 'Could not save';
  }, 800);
});

स्टार्ट-अप पर, अगर कोई फ़ोल्डर याद है तो आख़िरी नोट दोबारा खोलें, वरना वेलकम स्क्रीन दिखाएँ:

window.addEventListener('load', function () {
  var fs = window.WebToApkFS;
  if (!fs || !fs.hasAccess()) {
    showWelcome();                                 // your app: explains "Choose folder"
    return;
  }
  var last = 'notes/today.txt';
  try { last = localStorage.getItem('lastNote') || last; } catch (e) {}
  document.getElementById('editor').value = fs.readText(last);
});

ध्यान दें: यह सिर्फ़ बताता है कि ऐप को कोई फ़ोल्डर याद है। अगर उपयोगकर्ता ने वह फ़ोल्डर हटाया या हिलाया, तो list('') [] लौटाता है - फिर से "फ़ोल्डर चुनें" का विकल्प दें। window.WebToApkFS पेज का लोड पूरा होने पर आता है; लोड के दौरान कच्चा इस्तेमाल करें window.WebToApk.hasFolderAccess() === 'true'.

WebToApkFS.isAvailable हेल्पर#

WebToApkFS.isAvailable()

उदाहरण

बताता है कि यह ऐप उपयोगकर्ता के दिए फ़ोल्डर का इस्तेमाल कर सकता है या नहीं।

लौटाता है: तुरंत एक बूलियन: true जब बिल्ड में फ़ोल्डर एक्सेस चालू हो। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Native Folder Access (SAF) टिक करें - इसके बिना (और Secure Keys के बिना) window.WebToApkFS पेज में जोड़ा ही नहीं जाता; सिर्फ़ Secure Keys हो तो यह मौजूद रहता है और जवाब देता है false.

window.WebToApkFS आपके पेज की पहली स्क्रिप्ट चलने से पहले जोड़ा जाता है; लगभग 2021 से पुराने WebView पर यह पेज के लोड पूरा होने पर ही आता है। उन्हें भी कवर करने के लिए, तय करने से पहले थोड़ी देर इसका इंतज़ार करें:

function whenFolderHelperReady(done) {
  var tries = 0;
  (function check() {
    if (window.WebToApkFS) return done(window.WebToApkFS);
    if (++tries > 30) return done(null);       // in a browser, or the build did not enable it
    setTimeout(check, 100);
  })();
}

whenFolderHelperReady(function (fs) {
  var usable = !!(fs && fs.isAvailable());
  document.getElementById('folder-panel').hidden = !usable;
});

बटन टैप के अंदर पेज बहुत पहले लोड हो चुका होता है, इसलिए सीधी जाँच काफ़ी है:

if (window.WebToApkFS && window.WebToApkFS.isAvailable()) {
  // show the folder features
}

दोनों जाँचों को एक स्थिति में बदलें जो पूरी फ़ोल्डर स्क्रीन को चलाती है:

function folderState() {
  var fs = window.WebToApkFS;
  if (!fs || !fs.isAvailable()) return 'unsupported';   // this build has no folder access
  return fs.hasAccess() ? 'ready' : 'needs-folder';
}

var state = folderState();
document.getElementById('folder-panel').hidden = state === 'unsupported';
document.getElementById('choose-folder').hidden = state !== 'needs-folder';
document.getElementById('export').disabled = state !== 'ready';

ध्यान दें: यह नहीं बताता कि कोई फ़ोल्डर अभी चुना गया है या नहीं - उसके लिए WebToApkFS.hasAccess() इस्तेमाल करें। पेज लोड होने से पहले, कच्चा window.WebToApk.isFolderAccessEnabled() (स्ट्रिंग "true"/"false") यही सवाल हल करता है।

WebToApkFS.list हेल्पर#

WebToApkFS.list(relativePath)

उदाहरण

दिए गए फ़ोल्डर के किसी फ़ोल्डर के अंदर की फ़ाइलें और सब-फ़ोल्डर की सूची देता है।

लौटाता है: तुरंत एक ऐरे: [{ name, uri, isDirectory, length, lastModified }] (length बाइट में, lastModified 1970 से मिलीसेकंड में)। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Native Folder Access (SAF) टिक करें, और एक दिया हुआ फ़ोल्डर।

एक सादा फ़ाइल ब्राउज़र ('' खुद दिया हुआ फ़ोल्डर है):

function renderFolder(path) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return;
  var ul = document.getElementById('files');
  ul.innerHTML = '';
  window.WebToApkFS.list(path)
    .sort(function (a, b) { return (b.isDirectory - a.isDirectory) || a.name.localeCompare(b.name); })
    .forEach(function (e) {
      var li = document.createElement('li');
      li.textContent = (e.isDirectory ? '[folder] ' : '') + e.name +
        (e.isDirectory ? '' : ' (' + e.length + ' bytes, ' + new Date(e.lastModified).toLocaleDateString() + ')');
      if (e.isDirectory) li.onclick = function () { renderFolder(path ? path + '/' + e.name : e.name); };
      ul.appendChild(li);
    });
}

renderFolder('');

सभी Markdown नोट इकट्ठा करने के लिए हर सब-फ़ोल्डर में जाएँ (पाथ जो आप इसे दे सकते हैं readText):

function findNotes(path, out) {
  out = out || [];
  window.WebToApkFS.list(path).forEach(function (e) {
    var full = path ? path + '/' + e.name : e.name;
    if (e.isDirectory) findNotes(full, out);
    else if (/\.md$/i.test(e.name)) out.push(full);
  });
  return out;
}

if (window.WebToApkFS && window.WebToApkFS.hasAccess()) {
  var notes = findNotes('');
  document.getElementById('count').textContent = notes.length + ' notes';
}

सबसे नया बैकअप ढूँढें और देखें कि बैकअप कितनी जगह लेते हैं:

function backupSummary() {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return null;
  var files = window.WebToApkFS.list('backups').filter(function (e) { return !e.isDirectory; });
  if (!files.length) return null;
  var newest = files.reduce(function (a, b) { return b.lastModified > a.lastModified ? b : a; });
  var bytes = files.reduce(function (sum, e) { return sum + e.length; }, 0);
  return { newest: 'backups/' + newest.name, count: files.length, kb: Math.round(bytes / 1024) };
}

var s = backupSummary();
document.getElementById('backup-info').textContent = s
  ? s.count + ' backups, ' + s.kb + ' KB. Latest: ' + s.newest
  : 'No backups yet';

ध्यान दें: खाली फ़ोल्डर, न मिलने वाला फ़ोल्डर, फ़ाइल वाला पाथ, .. वाला पाथ, और कोई दिया हुआ फ़ोल्डर न होना - सब [] लौटाते हैं। सूची क्रम में नहीं होती। हर uri इसके साथ भी काम करता है WebToApkFS.stat() और WebToApkFS.readBytes().

WebToApkFS.mkdir हेल्पर#

WebToApkFS.mkdir(relativePath)

उदाहरण

दिए गए फ़ोल्डर के अंदर फ़ोल्डर बनाता है, छूटे हुए पैरेंट फ़ोल्डर समेत।

लौटाता है: तुरंत एक बूलियन: बाद में फ़ोल्डर मौजूद हो तो true। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Native Folder Access (SAF) टिक करें, और एक दिया हुआ फ़ोल्डर।

if (window.WebToApkFS && window.WebToApkFS.hasAccess()) {
  var month = new Date().toISOString().slice(0, 7);          // "2026-06"
  if (window.WebToApkFS.mkdir('journal/' + month)) {
    window.WebToApkFS.writeText('journal/' + month + '/readme.txt', 'One file per day.');
  } else {
    alert('Could not create the folder.');
  }
}

पहली बार इस्तेमाल पर प्रोजेक्ट का फ़ोल्डर ढाँचा बनाएँ, और जो फ़ोल्डर न बन सका उसकी रिपोर्ट करें:

function createProjectFolders(project) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return false;
  var failed = ['images', 'exports', 'drafts'].filter(function (sub) {
    return !window.WebToApkFS.mkdir('projects/' + project + '/' + sub);
  });
  if (failed.length) alert('Could not create: ' + failed.join(', '));
  return failed.length === 0;
}

createProjectFolders('holiday-2026');

"नया फ़ोल्डर" बटन जो नाम पहले से लिया होने पर चेतावनी देता है (क्योंकि मौजूदा फ़ोल्डर के लिए mkdir का जवाब true आता है):

document.getElementById('new-folder').addEventListener('click', function () {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return;
  var name = (prompt('Folder name') || '').trim();
  if (!name || /[\\/]/.test(name) || name === '.' || name === '..') return;
  var taken = window.WebToApkFS.list('').some(function (e) { return e.name === name; });
  if (taken) { alert('"' + name + '" already exists.'); return; }
  if (window.WebToApkFS.mkdir(name)) renderFolder('');   // your app: refresh the list
  else alert('Could not create "' + name + '".');
});

ध्यान दें: फ़ोल्डर पहले से हो तो यह true लौटाता है, इसलिए हर सेव से पहले कॉल करना ठीक है। जब उस नाम की कोई FILE रास्ते में हो, पाथ में .. हो, या कोई फ़ोल्डर दिया न गया हो, तब यह false लौटाता है। WebToApkFS.writeText() पहले से ज़रूरी पैरेंट बना देता है, इसलिए mkdir सिर्फ़ खाली फ़ोल्डरों के लिए चाहिए।

WebToApkFS.readText हेल्पर#

WebToApkFS.readText(relativePath)

उदाहरण

दिए गए फ़ोल्डर से टेक्स्ट फ़ाइल UTF-8 में पढ़ता है।

लौटाता है: तुरंत एक स्ट्रिंग: फ़ाइल का टेक्स्ट, या ''। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Native Folder Access (SAF) टिक करें, और एक दिया हुआ फ़ोल्डर।

'' का मतलब है "खाली फ़ाइल" या "ऐसी कोई फ़ाइल नहीं"। जब यह जानना ज़रूरी हो कि कौन-सा है, तो WebToApkFS.stat() इस्तेमाल करें:

function loadSettings() {
  if (!window.WebToApkFS) return null;
  var info = window.WebToApkFS.stat('data/settings.json');
  if (!info.ok) return null;                                // not saved yet
  try {
    return JSON.parse(window.WebToApkFS.readText('data/settings.json'));
  } catch (e) {
    alert('settings.json is damaged: ' + e.message);
    return null;
  }
}

नोट को टेक्स्ट बॉक्स में खोलें:

if (window.WebToApkFS && window.WebToApkFS.hasAccess()) {
  document.getElementById('editor').value = window.WebToApkFS.readText('notes/today.txt');
}

उपयोगकर्ता ने फ़ोल्डर में जो CSV डाली उसे इम्पोर्ट करें:

function importCsv(path) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return [];
  var text = window.WebToApkFS.readText(path);
  if (!text) return [];                                     // missing or empty
  var lines = text.replace(/\r/g, '').split('\n').filter(Boolean);
  var header = lines.shift().split(',');
  return lines.map(function (line) {
    var cells = line.split(',');
    var row = {};
    header.forEach(function (h, i) { row[h.trim()] = (cells[i] || '').trim(); });
    return row;
  });
}

var contacts = importCsv('import/contacts.csv');
console.log(contacts.length + ' rows imported');

ध्यान दें: पूरी फ़ाइल एक साथ लोड होती है - बड़ी या बाइनरी फ़ाइलों के लिए WebToApkFS.readBytes() इस्तेमाल करें। जो बाइट वैध UTF-8 नहीं हैं वे रिप्लेसमेंट कैरेक्टर बनकर आते हैं। फ़ोल्डर, .. वाला पाथ, या कोई दिया हुआ फ़ोल्डर न होना भी यही लौटाता है ''.

WebToApkFS.rename हेल्पर#

WebToApkFS.rename(relativePath, newName)

उदाहरण

दिए गए फ़ोल्डर के अंदर फ़ाइल या फ़ोल्डर का नाम बदलता है।

लौटाता है: तुरंत एक बूलियन। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Native Folder Access (SAF) टिक करें, और एक दिया हुआ फ़ोल्डर।

newName सिर्फ़ नया नाम है, पाथ नहीं। एंट्री उसी फ़ोल्डर में रहती है:

function renameNote(path, newName) {
  if (!window.WebToApkFS) return false;
  var ok = window.WebToApkFS.rename(path, newName);     // 'notes/a.txt', 'b.txt' -> notes/b.txt
  if (!ok) alert('Could not rename. A file called "' + newName + '" may already exist.');
  return ok;
}

renameNote('notes/today.txt', 'journal.txt');
renameNote('notes/2025', 'archive-2025');               // folders work too

टकराव के बिना नाम बदलें: "नाम (2)", "नाम (3)" … तब तक आज़माएँ जब तक खाली नाम न मिल जाए:

function renameUnique(path, wanted) {
  if (!window.WebToApkFS) return '';
  var dot = wanted.lastIndexOf('.');
  var base = dot > 0 ? wanted.slice(0, dot) : wanted;
  var ext = dot > 0 ? wanted.slice(dot) : '';
  for (var n = 1; n <= 20; n++) {
    var name = n === 1 ? wanted : base + ' (' + n + ')' + ext;
    if (window.WebToApkFS.rename(path, name)) return name;
  }
  return '';
}

var finalName = renameUnique('photos/IMG_0042.jpg', 'Beach.jpg');

सुरक्षित तरीके से सेव करें: पहले अस्थायी फ़ाइल लिखें, फिर उसे बदल दें (rename ऐसा नाम नहीं लेता जो पहले से हो, इसलिए पुरानी फ़ाइल ठीक पहले हटाई जाती है):

function saveSafely(folder, name, text) {
  var fs = window.WebToApkFS;
  if (!fs || !fs.hasAccess()) return false;
  var tmp = folder + '/' + name + '.tmp';
  if (!fs.writeText(tmp, text)) return false;           // the original is untouched
  fs.delete(folder + '/' + name);                       // false when it did not exist yet
  return fs.rename(tmp, name);
}

saveSafely('notes', 'today.txt', document.getElementById('editor').value);

ध्यान दें: यह false लौटाता है जब किसी दूसरी एंट्री का नाम पहले से newName हो, जब newName खाली हो, ./.. हो, या उसमें / या \ हो, जब पाथ मौजूद न हो, या जब कोई फ़ोल्डर दिया न गया हो। फ़ाइल को फ़ोल्डरों के बीच ले जाने के लिए उसे पढ़ें, नई जगह लिखें, फिर पुरानी हटा दें।

WebToApkFS.requestAccess हेल्पर#

WebToApkFS.requestAccess()

उदाहरण

सिस्टम का फ़ोल्डर पिकर खोलता है ताकि उपयोगकर्ता ऐप को एक फ़ोल्डर दे सके, जो रीस्टार्ट के बाद भी बना रहता है।

लौटाता है: एक Promise जो हमेशा पूरा होता है (कभी रिजेक्ट नहीं होता) { success, folderUri, requestId } के साथ, और success के false होने पर error भी। success बूलियन है; folderUri दिया गया content://…/tree/… पता है, या ''। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Native Folder Access (SAF) टिक करें।

उपयोगकर्ता जिस बटन को टैप करे उससे माँगें, और दिखाएँ कि कौन-सा फ़ोल्डर जुड़ा है:

document.getElementById('choose-folder').addEventListener('click', async function () {
  if (!window.WebToApkFS) {
    alert('Folder access is not available in this app.');
    return;
  }
  var r = await window.WebToApkFS.requestAccess();
  if (!r.success) {
    document.getElementById('status').textContent = 'No folder chosen.';
    return;
  }
  var name = decodeURIComponent(r.folderUri.split('/tree/')[1] || '').split(':').pop();
  document.getElementById('status').textContent = 'Saving to: ' + name;
  window.WebToApkFS.writeText('hello.txt', 'Connected at ' + new Date().toISOString());
});

तभी पूछें जब अभी कोई फ़ोल्डर दिया न गया हो:

async function ensureFolder() {
  if (!window.WebToApkFS) return false;
  if (window.WebToApkFS.hasAccess()) return true;
  var r = await window.WebToApkFS.requestAccess();
  return r.success;
}

"फ़ोल्डर बदलें" बटन जो अनुरोध के फ़ेल होने का हर तरीका समझाता है:

var folderErrors = {
  cancelled: 'No folder chosen.',
  busy: 'The folder picker is already open.',
  not_persisted: 'Android would not keep access to that folder. Try another one.',
  disabled: 'Folder access is switched off in this app.',
  no_picker: 'This phone has no folder picker.',
  foreign_origin: 'This page is not allowed to use folders.'
};

document.getElementById('change-folder').addEventListener('click', async function () {
  if (!window.WebToApkFS) return;
  var r = await window.WebToApkFS.requestAccess();
  document.getElementById('status').textContent = r.success
    ? 'Folder changed.'
    : (folderErrors[r.error] || 'Could not open the folder picker.');
  if (r.success) renderFolder('');                      // your app: show the new folder
});

ध्यान दें: इसे टैप से कॉल करें, पेज लोड होते समय कभी नहीं - पिकर ऐप को ढक लेता है। success: false के साथ error आता है: 'cancelled' (उपयोगकर्ता पीछे हट गया), 'not_persisted' (Android ने अनुमति नहीं रखी), 'disabled', 'busy' (पहले के अनुरोध के लिए पिकर पहले से खुला है - उसे अपना जवाब फिर भी मिलेगा), 'no_picker' या 'foreign_origin'। दोबारा चुनने पर पहले वाला फ़ोल्डर बदल जाता है।

WebToApkFS.writeText हेल्पर#

WebToApkFS.writeText(relativePath, content)

उदाहरण

दिए गए फ़ोल्डर में UTF-8 टेक्स्ट फ़ाइल लिखता है, उसे बनाकर (और ज़रूरी पैरेंट फ़ोल्डर भी) या बदलकर।

लौटाता है: तुरंत एक बूलियन: फ़ाइल लिखी जाने पर true। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Native Folder Access (SAF) टिक करें, और एक दिया हुआ फ़ोल्डर।

बटन से सेव करें:

document.getElementById('save').addEventListener('click', async function () {
  if (!window.WebToApkFS) { alert('Saving to a folder is not available here.'); return; }
  if (!window.WebToApkFS.hasAccess()) {
    var r = await window.WebToApkFS.requestAccess();
    if (!r.success) return;                              // user cancelled
  }
  var ok = window.WebToApkFS.writeText('notes/today.txt', document.getElementById('editor').value);
  document.getElementById('status').textContent = ok ? 'Saved' : 'Could not save';
});

किसी ऑब्जेक्ट को JSON के रूप में सेव करें:

if (window.WebToApkFS && window.WebToApkFS.hasAccess()) {
  window.WebToApkFS.writeText('data/settings.json', JSON.stringify({ theme: 'dark', size: 16 }, null, 2));
}

रोज़ के लॉग में एक लाइन जोड़ें (जोड़ने का तरीका नहीं है, इसलिए पढ़ें, जोड़ें, और वापस लिखें):

function logLine(message) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return false;
  var path = 'logs/' + new Date().toISOString().slice(0, 10) + '.log';   // one file per day
  var old = window.WebToApkFS.readText(path);
  var line = new Date().toLocaleTimeString() + '  ' + message + '\n';
  return window.WebToApkFS.writeText(path, old + line);
}

logLine('Order #1042 marked as shipped');

ध्यान दें: मौजूदा फ़ाइल पूरी तरह बदल दी जाती है - जोड़ने का तरीका नहीं है। content स्ट्रिंग में बदला जाता है (null का '' बन जाता है)। अगर पाथ किसी मौजूदा फ़ोल्डर का है, उसमें .. है, या कोई फ़ोल्डर दिया नहीं गया, तो यह false लौटाता है। फ़ाइल का प्रकार एक्सटेंशन से लिया जाता है, इसलिए डिस्क पर नाम ठीक वही है जो आपने दिया।

clearFolderAccess ब्रिज#

window.WebToApk.clearFolderAccess()

सहेजी हुई फ़ोल्डर अनुमति छोड़ देता है। दूसरी साइट के पेज के लिए मना (कुछ नहीं करता)।

उदाहरण

दिया हुआ फ़ोल्डर भूल जाता है और अनुमति वापस Android को दे देता है।

लौटाता है: कुछ नहीं (तुरंत)। WebToApkFS.clearAccess() भी यही करता है। ज़रूरत: चरण 4 (Access) में Native Folder Access (SAF) टिक किया हुआ; स्विच बंद हो तो यह कुछ नहीं करता।

"Disconnect" बटन:

document.getElementById('disconnect').addEventListener('click', function () {
  if (window.WebToApkFS) {
    window.WebToApkFS.clearAccess();
  } else if (window.WebToApk && typeof window.WebToApk.clearFolderAccess === 'function') {
    window.WebToApk.clearFolderAccess();
  }
  document.getElementById('folder-name').textContent = 'No folder chosen';
});

साइन-आउट पर फ़ोल्डर भी भूल जाएँ, ताकि इस फ़ोन पर अगला व्यक्ति अपना फ़ोल्डर चुने:

function signOut() {
  localStorage.clear();
  if (window.WebToApk && typeof window.WebToApk.clearFolderAccess === 'function') {
    window.WebToApk.clearFolderAccess();
  }
  location.href = 'login.html';
}

Files ऐप में हिलाया या हटाया गया फ़ोल्डर भूल जाएँ, फिर दोबारा चुनने का विकल्प दें (फ़ोल्डर पहली बार जुड़ने पर ऐप myapp-folder.json लिखता है):

async function reconnectIfMissing() {
  var fs = window.WebToApkFS;
  if (!fs || !fs.hasAccess()) return;
  if (fs.stat('myapp-folder.json').ok) return;          // marker still there: the folder is fine
  if (typeof window.WebToApk.clearFolderAccess === 'function') {
    window.WebToApk.clearFolderAccess();                // drop the grant that points nowhere
  }
  if (confirm('The folder you chose earlier is gone. Choose a folder again?')) {
    var r = await fs.requestAccess();
    if (r.success) fs.writeText('myapp-folder.json', '{}');
  }
}

ध्यान दें: फ़ोल्डर की फ़ाइलें हटाई नहीं जातीं; बस ऐप अब उन तक नहीं पहुँच सकता। इसके बाद hasFolderAccess() का जवाब "false" होता है और हर फ़ाइल वाला तरीका तब तक फ़ेल होता है जब तक उपयोगकर्ता फिर फ़ोल्डर न चुने।

deleteEntry ब्रिज#

window.WebToApk.deleteEntry(relativePath: String, recursive: String): String

ACTION_OPEN_DOCUMENT_TREE और persistable URI अनुमतियों वाला Play-सुरक्षित फ़ोल्डर एक्सेस। JavaScript इस्तेमाल: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." या "" window.WebToApk.clearFolderAccess() // सहेजा हुआ एक्सेस छोड़ देता है। Android → JS कॉलबैक: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (चौथा आर्गुमेंट, सफलता पर "") बताता है कि अनुरोध क्यों फ़ेल हुआ: "disabled", "foreign_origin", "busy" (पहले के अनुरोध के लिए पिकर पहले से खुला है - उसे अपना जवाब फिर भी मिलेगा), "cancelled", "not_persisted", "no_picker"।

अलग से नहीं, बल्कि अपने समूह Native Folder Access (SAF) Bridge के साथ बताया गया है।

उदाहरण

दिए गए फ़ोल्डर के अंदर की फ़ाइल या फ़ोल्डर हटाता है।

लौटाता है: तुरंत STRING "true" या "false"। WebToApkFS.delete(path, { recursive }) असली बूलियन देता है। ज़रूरत: चरण 4 (Access) में Native Folder Access (SAF) टिक किया हुआ और एक दिया हुआ फ़ोल्डर।

हेल्पर के साथ, उपयोगकर्ता से पूछने के बाद:

function removeEntry(path, isFolder) {
  if (!window.WebToApkFS) return false;
  if (!confirm('Delete ' + path + '? This cannot be undone.')) return false;
  return window.WebToApkFS.delete(path, { recursive: isFolder });
}

removeEntry('notes/old.txt', false);
removeEntry('notes/2025', true);      // the folder and everything in it

कच्चा कॉल - दूसरा आर्गुमेंट STRING 'true' या 'false':

if (window.WebToApk && typeof window.WebToApk.deleteEntry === 'function') {
  var gone = window.WebToApk.deleteEntry('cache', 'true') === 'true';
}

सिर्फ़ पाँच सबसे नए बैकअप रखें और बाकी हटा दें:

function pruneBackups(keep) {
  var fs = window.WebToApkFS;
  if (!fs) return 0;
  var files = fs.list('backups').filter(function (e) { return !e.isDirectory; });
  files.sort(function (a, b) { return b.lastModified - a.lastModified; });   // newest first
  var removed = 0;
  files.slice(keep).forEach(function (e) {
    if (fs.delete('backups/' + e.name)) removed++;
  });
  return removed;
}

console.log(pruneBackups(5) + ' old backups deleted');

ध्यान दें: जो फ़ोल्डर खाली नहीं है वह सिर्फ़ recursive 'true' के साथ हटता है। दिया हुआ फ़ोल्डर खुद ('') कभी नहीं हटाया जा सकता। हटाना असली और तुरंत होता है: कोई ट्रैश नहीं है।

getFolderAccessUri ब्रिज#

window.WebToApk.getFolderAccessUri(): String

दिए गए फ़ोल्डर का `content://` ट्री URI, या "" (कोई नहीं, बंद, या दूसरी साइट का पेज)।

उदाहरण

उपयोगकर्ता के दिए फ़ोल्डर का पता देता है, ताकि आप दिखा सकें कि कौन-सा फ़ोल्डर जुड़ा है।

लौटाता है: तुरंत एक स्ट्रिंग: content://com.android.externalstorage.documents/tree/primary%3ADocuments%2FNotes जैसा ट्री URI, या "" जब कोई फ़ोल्डर न दिया गया हो या स्विच बंद हो। WebToApkFS.getAccessUri() वही स्ट्रिंग देता है। ज़रूरत: चरण 4 (Access) में Native Folder Access (SAF) टिक किया हुआ।

URI को ऐसे नाम में बदलें जिसे उपयोगकर्ता पहचाने:

function folderLabel() {
  var uri = '';
  if (window.WebToApkFS) {
    uri = window.WebToApkFS.getAccessUri();
  } else if (window.WebToApk && typeof window.WebToApk.getFolderAccessUri === 'function') {
    uri = window.WebToApk.getFolderAccessUri();
  }
  if (!uri) return '';
  var tail = uri.split('/tree/')[1] || '';
  try { tail = decodeURIComponent(tail); } catch (e) {}
  return tail.split(':').pop() || tail;          // "primary:Documents/Notes" -> "Documents/Notes"
}

var label = folderLabel();
document.getElementById('folder-name').textContent = label ? 'Saving to: ' + label : 'No folder chosen';

फ़ाइल सूची तभी दोबारा लोड करें जब उपयोगकर्ता ने सच में दूसरा फ़ोल्डर चुना हो:

document.getElementById('change-folder').addEventListener('click', async function () {
  if (!window.WebToApkFS) return;
  var before = window.WebToApkFS.getAccessUri();
  var r = await window.WebToApkFS.requestAccess();
  if (!r.success) return;                        // the old folder is still connected
  if (r.folderUri !== before) {
    renderFiles(window.WebToApkFS.list(''));     // a new place: show what is in it
  }
});

बताएँ कि फ़ोल्डर फ़ोन में है, SD कार्ड पर है, या किसी दूसरे स्टोरेज ऐप में:

function folderLocation() {
  var uri = window.WebToApkFS ? window.WebToApkFS.getAccessUri() : '';
  if (!uri) return 'none';
  var id = uri.split('/tree/')[1] || '';
  try { id = decodeURIComponent(id); } catch (e) {}
  if (uri.indexOf('com.android.externalstorage.documents') === -1) return 'other';   // e.g. a cloud drive app
  return id.indexOf('primary:') === 0 ? 'phone' : 'sd-card';                         // "1A2B-3C4D:Music" is a card
}

document.getElementById('folder-where').textContent =
  { none: '', phone: 'Phone storage', 'sd-card': 'SD card', other: 'Another storage app' }[folderLocation()];

ध्यान दें: आप इस URI को फ़ाइल वाले तरीकों को कभी वापस नहीं देते - वे सब इस फ़ोल्डर के सापेक्ष पाथ लेते हैं। नाम को "Disconnect" बटन के पास दिखाएँ ताकि उपयोगकर्ता जाने कि फ़ाइलें कहाँ जाती हैं।

hasFolderAccess ब्रिज#

window.WebToApk.hasFolderAccess(): String

ACTION_OPEN_DOCUMENT_TREE और persistable URI अनुमतियों वाला Play-सुरक्षित फ़ोल्डर एक्सेस। JavaScript इस्तेमाल: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." या "" window.WebToApk.clearFolderAccess() // सहेजा हुआ एक्सेस छोड़ देता है। Android → JS कॉलबैक: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (चौथा आर्गुमेंट, सफलता पर "") बताता है कि अनुरोध क्यों फ़ेल हुआ: "disabled", "foreign_origin", "busy" (पहले के अनुरोध के लिए पिकर पहले से खुला है - उसे अपना जवाब फिर भी मिलेगा), "cancelled", "not_persisted", "no_picker"।

अलग से नहीं, बल्कि अपने समूह Native Folder Access (SAF) Bridge के साथ बताया गया है।

उदाहरण

बताता है कि उपयोगकर्ता ने ऐप को पहले से कोई फ़ोल्डर दिया है या नहीं।

लौटाता है: तुरंत STRING "true" या "false" (बूलियन नहीं)। WebToApkFS.hasAccess() असली बूलियन देता है। ज़रूरत: चरण 4 (Access) में Native Folder Access (SAF) टिक किया हुआ; इसके बिना जवाब हमेशा "false".

कच्चा तरीका पेज की बिल्कुल पहली लाइन से मौजूद रहता है, इसलिए पेज लोड होते समय यही सही जाँच है (WebToApkFS सिर्फ़ लोड पूरा होने पर जोड़ा जाता है):

function folderConnected() {
  if (window.WebToApkFS) return window.WebToApkFS.hasAccess();
  return !!(window.WebToApk && typeof window.WebToApk.hasFolderAccess === 'function' &&
    window.WebToApk.hasFolderAccess() === 'true');
}

document.getElementById('choose-folder').hidden = folderConnected();

सेव से पहले जाँचें और पिकर तभी खोलें जब अभी कोई फ़ोल्डर जुड़ा न हो:

async function saveBackup(json) {
  if (!window.WebToApkFS) return false;
  if (!window.WebToApkFS.hasAccess()) {
    var r = await window.WebToApkFS.requestAccess();   // asked once; the grant is remembered
    if (!r.success) return false;
  }
  return window.WebToApkFS.writeText('backup.json', json);
}

document.getElementById('backup').addEventListener('click', function () {
  saveBackup(JSON.stringify(appState));
});

ऑटोसेव टाइमर जो तभी लिखता है जब फ़ोल्डर जुड़ा हो और खुद कभी पिकर नहीं खोलता:

setInterval(function () {
  if (!window.WebToApk || typeof window.WebToApk.hasFolderAccess !== 'function') return;
  if (window.WebToApk.hasFolderAccess() !== 'true') return;   // not connected: the Save button asks
  window.WebToApk.writeTextFile('drafts/autosave.txt', document.getElementById('editor').value);
}, 30000);

ध्यान दें: === 'true' से तुलना करें; स्ट्रिंग "false" truthy होती है। यह सिर्फ़ इतना बताता है कि ऐप को कोई फ़ोल्डर याद है। अगर वह फ़ोल्डर Files ऐप में हटा या हिला दिया गया, तो listFolderEntries('') का जवाब [] होता है - उपयोगकर्ता से उसे फिर चुनने को कहें।

isFolderAccessEnabled ब्रिज#

window.WebToApk.isFolderAccessEnabled(): String

ACTION_OPEN_DOCUMENT_TREE और persistable URI अनुमतियों वाला Play-सुरक्षित फ़ोल्डर एक्सेस। JavaScript इस्तेमाल: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." या "" window.WebToApk.clearFolderAccess() // सहेजा हुआ एक्सेस छोड़ देता है। Android → JS कॉलबैक: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (चौथा आर्गुमेंट, सफलता पर "") बताता है कि अनुरोध क्यों फ़ेल हुआ: "disabled", "foreign_origin", "busy" (पहले के अनुरोध के लिए पिकर पहले से खुला है - उसे अपना जवाब फिर भी मिलेगा), "cancelled", "not_persisted", "no_picker"।

अलग से नहीं, बल्कि अपने समूह Native Folder Access (SAF) Bridge के साथ बताया गया है।

उदाहरण

बताता है कि यह ऐप फ़ोल्डर एक्सेस चालू करके बना था या नहीं।

लौटाता है: तुरंत STRING "true" या "false"। ज़रूरत: इसे कॉल करने के लिए कुछ नहीं; यह "true" तभी देता है जब चरण 4 (Access) में Native Folder Access (SAF) टिक किया गया हो।

इससे तय करें कि फ़ोल्डर फ़ीचर दिखाना है या नहीं। पेज लोड होने के बाद WebToApkFS.isAvailable() यही सवाल बूलियन के रूप में हल करता है:

function folderFeatureShipped() {
  if (window.WebToApkFS) return window.WebToApkFS.isAvailable();
  return !!(window.WebToApk && typeof window.WebToApk.isFolderAccessEnabled === 'function' &&
    window.WebToApk.isFolderAccessEnabled() === 'true');
}

if (!folderFeatureShipped()) {
  document.getElementById('folder-panel').hidden = true;   // in a browser, or the switch was off
}

सेटिंग्स की एक पंक्ति को उसकी तीनों स्थितियों (इस बिल्ड में नहीं, जुड़ा नहीं, जुड़ा) से चलाएँ:

function folderSettingsState() {
  var fs = window.WebToApkFS;
  if (!fs || !fs.isAvailable()) return 'unavailable';
  return fs.hasAccess() ? 'connected' : 'not-connected';
}

var row = document.getElementById('folder-row');
switch (folderSettingsState()) {
  case 'unavailable':   row.hidden = true; break;
  case 'not-connected': row.querySelector('button').textContent = 'Choose a folder'; break;
  case 'connected':     row.querySelector('button').textContent = 'Disconnect'; break;
}

पेज बनने से पहले उसे चिह्नित करें, ताकि CSS बिना झिलमिलाहट के सिर्फ़-फ़ोल्डर वाले कंट्रोल दिखा सके:

// In a <script> inside <head>: the raw method is already there.
if (window.WebToApk && typeof window.WebToApk.isFolderAccessEnabled === 'function' &&
    window.WebToApk.isFolderAccessEnabled() === 'true') {
  document.documentElement.classList.add('has-folders');   // .has-folders .folder-only { display: block }
}

ध्यान दें: यह नहीं बताता कि कोई फ़ोल्डर अभी चुना गया है या नहीं - उसके लिए hasFolderAccess() इस्तेमाल करें। ब्राउज़र में window.WebToApk होता ही नहीं, इसलिए ऊपर की जाँच बिना एरर के false देती है।

listFolderEntries ब्रिज#

window.WebToApk.listFolderEntries(relativePath: String): String

ACTION_OPEN_DOCUMENT_TREE और persistable URI अनुमतियों वाला Play-सुरक्षित फ़ोल्डर एक्सेस। JavaScript इस्तेमाल: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." या "" window.WebToApk.clearFolderAccess() // सहेजा हुआ एक्सेस छोड़ देता है। Android → JS कॉलबैक: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (चौथा आर्गुमेंट, सफलता पर "") बताता है कि अनुरोध क्यों फ़ेल हुआ: "disabled", "foreign_origin", "busy" (पहले के अनुरोध के लिए पिकर पहले से खुला है - उसे अपना जवाब फिर भी मिलेगा), "cancelled", "not_persisted", "no_picker"।

अलग से नहीं, बल्कि अपने समूह Native Folder Access (SAF) Bridge के साथ बताया गया है।

उदाहरण

दिए गए फ़ोल्डर के किसी फ़ोल्डर के अंदर की फ़ाइलें और सब-फ़ोल्डर की सूची देता है।

लौटाता है: तुरंत एक JSON STRING: { name, uri, isDirectory, length, lastModified } की ऐरे (length बाइट में, lastModified 1970 से मिलीसेकंड में)। WebToApkFS.list(path) इसे आपके लिए पार्स कर देता है। ज़रूरत: चरण 4 (Access) में Native Folder Access (SAF) टिक किया हुआ और एक दिया हुआ फ़ोल्डर।

हेल्पर के साथ ('' खुद दिया हुआ फ़ोल्डर है):

function showFolder(path) {
  if (!window.WebToApkFS) return;
  var entries = window.WebToApkFS.list(path);          // already an array
  entries.forEach(function (e) {
    console.log(e.isDirectory ? '[dir] ' : '      ', e.name, e.length + ' bytes');
  });
}
showFolder('');          // the root
showFolder('notes');     // a sub-folder

कच्चा कॉल, जो ऐसा टेक्स्ट लौटाता है जिसे आपको पार्स करना होगा:

if (window.WebToApk && typeof window.WebToApk.listFolderEntries === 'function') {
  var entries = JSON.parse(window.WebToApk.listFolderEntries('notes'));
  console.log(entries.length + ' entries');
}

सबसे नई बैकअप फ़ाइल ढूँढें और दिखाएँ कि वह कब बनी थी:

function latestBackup() {
  if (!window.WebToApkFS) return null;
  var backups = window.WebToApkFS.list('backups').filter(function (e) {
    return !e.isDirectory && /\.json$/i.test(e.name);
  });
  backups.sort(function (a, b) { return b.lastModified - a.lastModified; });   // newest first
  return backups[0] || null;
}

var last = latestBackup();
document.getElementById('last-backup').textContent =
  last ? 'Last backup: ' + new Date(last.lastModified).toLocaleString() : 'No backups yet';

ध्यान दें: खाली फ़ोल्डर, न मिलने वाला फ़ोल्डर, फ़ाइल वाला पाथ, .. वाला पाथ, कोई दिया हुआ फ़ोल्डर न होना, और दूसरी वेबसाइट का पेज - सब [] देते हैं - "कोई फ़ोल्डर नहीं" को अलग पहचानना हो तो पहले hasFolderAccess() कॉल करें। पाथ सापेक्ष होते हैं, / इस्तेमाल करते हैं, और दिए गए फ़ोल्डर से ऊपर कभी नहीं जाते।

mkdir ब्रिज#

window.WebToApk.mkdir(relativePath: String): String

ACTION_OPEN_DOCUMENT_TREE और persistable URI अनुमतियों वाला Play-सुरक्षित फ़ोल्डर एक्सेस। JavaScript इस्तेमाल: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." या "" window.WebToApk.clearFolderAccess() // सहेजा हुआ एक्सेस छोड़ देता है। Android → JS कॉलबैक: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (चौथा आर्गुमेंट, सफलता पर "") बताता है कि अनुरोध क्यों फ़ेल हुआ: "disabled", "foreign_origin", "busy" (पहले के अनुरोध के लिए पिकर पहले से खुला है - उसे अपना जवाब फिर भी मिलेगा), "cancelled", "not_persisted", "no_picker"।

अलग से नहीं, बल्कि अपने समूह Native Folder Access (SAF) Bridge के साथ बताया गया है।

उदाहरण

दिए गए फ़ोल्डर के अंदर फ़ोल्डर बनाता है, छूटे हुए पैरेंट फ़ोल्डर समेत।

लौटाता है: तुरंत STRING "true" या "false"। WebToApkFS.mkdir(path) असली बूलियन देता है। ज़रूरत: चरण 4 (Access) में Native Folder Access (SAF) टिक किया हुआ और एक दिया हुआ फ़ोल्डर।

हेल्पर के साथ:

if (window.WebToApkFS && window.WebToApkFS.hasAccess()) {
  var ok = window.WebToApkFS.mkdir('photos/2026/june');   // creates photos, 2026 and june
  if (!ok) console.log('Could not create the folder');
}

सीधा कॉल:

if (window.WebToApk && typeof window.WebToApk.mkdir === 'function') {
  var made = window.WebToApk.mkdir('backups') === 'true';
}

एक एक्सपोर्ट के लिए तारीख वाला फ़ोल्डर बनाएँ और हर आइटम उसी में लिखें:

function exportAll(items) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return false;
  var dir = 'exports/' + new Date().toISOString().slice(0, 10);   // e.g. exports/2026-09-25
  if (!window.WebToApkFS.mkdir(dir)) {
    alert('Could not create ' + dir + ' (is a file with that name in the way?)');
    return false;
  }
  items.forEach(function (item) {
    window.WebToApkFS.writeText(dir + '/' + item.id + '.json', JSON.stringify(item));
  });
  return true;
}

ध्यान दें: फ़ोल्डर पहले से मौजूद हो तो यह "true" देता है, इसलिए हर सेव से पहले इसे कॉल करना सुरक्षित है। जब उस नाम की कोई FILE रास्ते में हो, पाथ में .. हो, या कोई फ़ोल्डर दिया न गया हो, तब यह "false" देता है।

readTextFile ब्रिज#

window.WebToApk.readTextFile(relativePath: String): String

ACTION_OPEN_DOCUMENT_TREE और persistable URI अनुमतियों वाला Play-सुरक्षित फ़ोल्डर एक्सेस। JavaScript इस्तेमाल: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." या "" window.WebToApk.clearFolderAccess() // सहेजा हुआ एक्सेस छोड़ देता है। Android → JS कॉलबैक: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (चौथा आर्गुमेंट, सफलता पर "") बताता है कि अनुरोध क्यों फ़ेल हुआ: "disabled", "foreign_origin", "busy" (पहले के अनुरोध के लिए पिकर पहले से खुला है - उसे अपना जवाब फिर भी मिलेगा), "cancelled", "not_persisted", "no_picker"।

अलग से नहीं, बल्कि अपने समूह Native Folder Access (SAF) Bridge के साथ बताया गया है।

उदाहरण

दिए गए फ़ोल्डर से टेक्स्ट फ़ाइल UTF-8 में पढ़ता है।

लौटाता है: तुरंत एक स्ट्रिंग: फ़ाइल का टेक्स्ट, या ""। WebToApkFS.readText(path) भी यही लौटाता है। ज़रूरत: चरण 4 (Access) में Native Folder Access (SAF) टिक किया हुआ और एक दिया हुआ फ़ोल्डर।

"" का मतलब है "खाली फ़ाइल" या "ऐसी कोई फ़ाइल नहीं", इसलिए जब फ़र्क मायने रखे तो WebToApkFS.stat() से जाँचें:

function loadNote(path) {
  if (!window.WebToApkFS) return null;
  var info = window.WebToApkFS.stat(path);    // { ok: true, name, size, ... } or { ok: false, error: 'not-found' }
  if (!info.ok) return null;                  // the file does not exist
  return window.WebToApkFS.readText(path);    // '' here really is an empty file
}

var text = loadNote('notes/today.txt');
document.getElementById('editor').value = text === null ? '' : text;

सीधा कॉल:

if (window.WebToApk && typeof window.WebToApk.readTextFile === 'function') {
  var raw = window.WebToApk.readTextFile('notes/today.txt');
  console.log(raw.length + ' characters');
}

किसी फ़ोल्डर से हर .txt और .md नोट इम्पोर्ट करें:

function importNotes(folder) {
  if (!window.WebToApkFS) return [];
  return window.WebToApkFS.list(folder)
    .filter(function (e) { return !e.isDirectory && /\.(txt|md)$/i.test(e.name); })
    .map(function (e) {
      return {
        title: e.name.replace(/\.(txt|md)$/i, ''),
        body: window.WebToApkFS.readText(folder + '/' + e.name)
      };
    });
}

renderNotes(importNotes('notes'));

ध्यान दें: पूरी फ़ाइल मेमोरी में पढ़ी जाती है - नोट और JSON के लिए ठीक है, बड़ी फ़ाइलों के लिए नहीं; उनके लिए WebToApkFS.readBytes इस्तेमाल करें। जो बाइट वैध UTF-8 नहीं हैं वे रिप्लेसमेंट कैरेक्टर बनकर आते हैं। फ़ोल्डर पाथ, .. वाला पाथ, कोई दिया हुआ फ़ोल्डर न होना, या दूसरी वेबसाइट का पेज - सब यही जवाब देते हैं "".

renameEntry ब्रिज#

window.WebToApk.renameEntry(relativePath: String, newName: String): String

ACTION_OPEN_DOCUMENT_TREE और persistable URI अनुमतियों वाला Play-सुरक्षित फ़ोल्डर एक्सेस। JavaScript इस्तेमाल: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." या "" window.WebToApk.clearFolderAccess() // सहेजा हुआ एक्सेस छोड़ देता है। Android → JS कॉलबैक: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (चौथा आर्गुमेंट, सफलता पर "") बताता है कि अनुरोध क्यों फ़ेल हुआ: "disabled", "foreign_origin", "busy" (पहले के अनुरोध के लिए पिकर पहले से खुला है - उसे अपना जवाब फिर भी मिलेगा), "cancelled", "not_persisted", "no_picker"।

अलग से नहीं, बल्कि अपने समूह Native Folder Access (SAF) Bridge के साथ बताया गया है।

उदाहरण

दिए गए फ़ोल्डर के अंदर फ़ाइल या फ़ोल्डर का नाम बदलता है।

लौटाता है: तुरंत STRING "true" या "false"। WebToApkFS.rename(path, newName) असली बूलियन देता है। ज़रूरत: चरण 4 (Access) में Native Folder Access (SAF) टिक किया हुआ और एक दिया हुआ फ़ोल्डर।

newName सादा नाम है, पाथ नहीं - एंट्री उसी फ़ोल्डर में रहती है:

if (window.WebToApkFS) {
  var ok = window.WebToApkFS.rename('notes/today.txt', 'journal.txt');   // -> notes/journal.txt
  if (!ok) alert('Rename failed. Is there already a file called journal.txt?');
}

सीधा कॉल:

if (window.WebToApk && typeof window.WebToApk.renameEntry === 'function') {
  var renamed = window.WebToApk.renameEntry('old-folder', 'new-folder') === 'true';
}

नया बैकअप लिखने से पहले पिछले बैकअप का नाम बदलकर उसे बचाएँ:

function rotateBackup(json) {
  var fs = window.WebToApkFS;
  if (!fs || !fs.hasAccess()) return false;
  if (fs.stat('backup.json').ok) {
    var stamp = new Date().toISOString().replace(/[:.]/g, '-');   // no characters a file name cannot hold
    fs.rename('backup.json', 'backup-' + stamp + '.json');        // stays in the same folder
  }
  return fs.writeText('backup.json', json);
}

ध्यान दें: यह फ़ेल होता है जब किसी दूसरी एंट्री का नाम पहले से newName हो, जब newName खाली हो या उसमें / या \ हो, जब पाथ मौजूद न हो, या जब कोई फ़ोल्डर दिया न गया हो। फ़ाइल को दूसरे फ़ोल्डर में ले जाने के लिए उसे वहाँ लिखें और पुरानी हटा दें।

requestFolderAccess ब्रिज#

window.WebToApk.requestFolderAccess(requestId: String)

सिस्टम का फ़ोल्डर पिकर खोलता है और चुने हुए फ़ोल्डर का पढ़ने/लिखने का एक्सेस सहेजता है। जवाब `WebToApkOnFolderAccessResult` पर आता है; एक समय में सिर्फ़ एक पिकर खुल सकता है, इसलिए पिकर खुला होने पर किया गया अनुरोध तुरंत `error:"busy"` के साथ जवाब पाता है और खुला पिकर अपना जवाब रखता है।

उदाहरण

सिस्टम का फ़ोल्डर पिकर खोलता है ताकि उपयोगकर्ता ऐप को एक फ़ोल्डर दे सके। पेज आमतौर पर WebToApkFS.requestAccess() इस्तेमाल करते हैं, जो इस कॉल को Promise में लपेट देता है।

लौटाता है: कुछ नहीं। जवाब बाद में एक अंदरूनी कॉलबैक से आता है जिसका मालिक WebToApkFS है; WebToApkFS.requestAccess() { success, folderUri, requestId } के साथ पूरा होता है, और success के false होने पर error भी। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Native Folder Access (SAF) टिक करें।

बटन टैप से, हेल्पर के साथ माँगें:

document.getElementById('choose-folder').addEventListener('click', async function () {
  if (!window.WebToApkFS) {
    alert('Folder access is not available in this app.');
    return;
  }
  var r = await window.WebToApkFS.requestAccess();
  if (r.success) {
    console.log('Folder granted:', r.folderUri);   // content://.../tree/...
  } else {
    console.log('No folder chosen');                 // user cancelled, or Android refused the grant
  }
});

सिर्फ़ पहली बार पूछें, फिर ऐप का अपना सब-फ़ोल्डर बनाएँ:

async function ensureFolder() {
  if (!window.WebToApkFS || !window.WebToApkFS.isAvailable()) return false;
  if (window.WebToApkFS.hasAccess()) return true;          // granted on an earlier run
  var r = await window.WebToApkFS.requestAccess();
  if (!r.success) return false;
  window.WebToApkFS.mkdir('exports');                       // true even if it already exists
  return true;
}

document.getElementById('export').addEventListener('click', async function () {
  if (!(await ensureFolder())) return;
  window.WebToApkFS.writeText('exports/list.csv', buildCsv());
});

असली error कोड इस्तेमाल करके उपयोगकर्ता को ठीक-ठीक बताएँ कि फ़ोल्डर क्यों नहीं जुड़ा:

var folderMessages = {
  cancelled: 'No folder was chosen.',
  not_persisted: 'Android would not keep access to that folder. Please choose another one.',
  busy: 'The folder picker is already open.',
  disabled: 'This app was built without folder access.',
  foreign_origin: 'A folder can only be connected from the app\'s own pages.',
  no_picker: 'This phone has no folder picker.'
};

document.getElementById('choose-folder').addEventListener('click', async function () {
  if (!window.WebToApkFS) return;
  var r = await window.WebToApkFS.requestAccess();
  document.getElementById('status').textContent = r.success
    ? 'Folder connected.'
    : (folderMessages[r.error] || 'Could not connect a folder.');
});

ध्यान दें: कच्चे तरीके को खुद कॉल न करें: उसका एकमात्र जवाब वह कॉलबैक है जिसे हेल्पर सुनता है। success false तब होता है जब उपयोगकर्ता रद्द करे (error:'cancelled'), जब Android अनुमति न रखे ('not_persisted'), और तुरंत तब जब बिल्ड ने स्विच चालू नहीं किया ('disabled') या स्क्रीन पर दिखता पेज ऐप का अपना नहीं है ('foreign_origin')। अनुमति ऐप के रीस्टार्ट के बाद भी रहती है; दोबारा चुनने पर फ़ोल्डर बदल जाता है। एक समय में सिर्फ़ एक पिकर खुल सकता है - पिकर खुला होने पर किया गया अनुरोध तुरंत error:'busy' के साथ जवाब पाता है, और खुला पिकर अपना जवाब रखता है।

writeTextFile ब्रिज#

window.WebToApk.writeTextFile(relativePath: String, content: String): String

ACTION_OPEN_DOCUMENT_TREE और persistable URI अनुमतियों वाला Play-सुरक्षित फ़ोल्डर एक्सेस। JavaScript इस्तेमाल: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." या "" window.WebToApk.clearFolderAccess() // सहेजा हुआ एक्सेस छोड़ देता है। Android → JS कॉलबैक: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (चौथा आर्गुमेंट, सफलता पर "") बताता है कि अनुरोध क्यों फ़ेल हुआ: "disabled", "foreign_origin", "busy" (पहले के अनुरोध के लिए पिकर पहले से खुला है - उसे अपना जवाब फिर भी मिलेगा), "cancelled", "not_persisted", "no_picker"।

अलग से नहीं, बल्कि अपने समूह Native Folder Access (SAF) Bridge के साथ बताया गया है।

उदाहरण

दिए गए फ़ोल्डर में टेक्स्ट फ़ाइल (UTF-8) लिखता है, उसे बनाकर या उसकी सामग्री बदलकर।

लौटाता है: तुरंत STRING "true" या "false"। WebToApkFS.writeText(path, text) असली बूलियन देता है। ज़रूरत: चरण 4 (Access) में Native Folder Access (SAF) टिक किया हुआ और एक दिया हुआ फ़ोल्डर।

हेल्पर के साथ:

function saveNote(path, text) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) {
    alert('Choose a folder first.');
    return false;
  }
  var ok = window.WebToApkFS.writeText(path, text);   // missing parent folders are created
  if (!ok) alert('Could not save ' + path);
  return ok;
}

saveNote('notes/2026/today.txt', document.getElementById('editor').value);

कच्चा कॉल, जिसका जवाब टेक्स्ट है:

if (window.WebToApk && typeof window.WebToApk.writeTextFile === 'function') {
  var saved = window.WebToApk.writeTextFile('data/settings.json', JSON.stringify({ theme: 'dark' })) === 'true';
}

रोज़ के लॉग में एक लाइन जोड़ें - लिखने से फ़ाइल बदल जाती है, इसलिए पहले उसे पढ़ें:

function appendLog(line) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return false;
  var now = new Date().toISOString();
  var path = 'logs/' + now.slice(0, 10) + '.log';          // one file per day
  var old = window.WebToApkFS.readText(path);              // '' the first time
  return window.WebToApkFS.writeText(path, old + now + ' ' + line + '\n');
}

appendLog('Order #1042 marked as shipped');

ध्यान दें: मौजूदा फ़ाइल पूरी तरह बदल दी जाती है। अगर पाथ किसी मौजूदा फ़ोल्डर का है, उसमें .. है, या कोई फ़ोल्डर दिया नहीं गया, तो यह फ़ेल होता है। फ़ाइल का प्रकार एक्सटेंशन (.json, .txt, .md…) से तय होता है, इसलिए आप जो नाम देते हैं वही डिस्क पर नाम होता है।

हर डॉक्स बिल्ड पर ऐप रनटाइम और उसकी उदाहरण फ़ाइलों से बनाया जाता है। Markdown में पढ़ें · सभी फ़ैमिली.

2026-10-03 को शिप किए गए ब्रिज से मिलाकर जाँचा गया।