Buat APK Gratis

API jembatan JavaScript

File: folder yang diizinkan pengguna

Sebuah direktori yang dipilih sekali, dapat dibaca dan ditulis sesudahnya, dengan izin yang bertahan setelah reboot.

WebToApkFS.clearAccess helper#

WebToApkFS.clearAccess()

Contoh

Melupakan folder yang diizinkan dan mengembalikan izin ke Android.

Mengembalikan: tidak ada (sinkron). Membutuhkan: centang Native Folder Access (SAF) di Langkah 4 (Akses) saat Anda membuat build.

Selalu sediakan cara untuk memutuskan koneksi di samping nama folder:

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;
});

Satu tombol yang menghubungkan atau memutuskan, tergantung keadaan saat ini:

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();

Ganti akun: lupakan folder bersama keadaan masuk lainnya:

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

Catatan: File tetap di tempatnya; aplikasi hanya tidak dapat menjangkaunya lagi. Untuk memakai folder lagi, panggil WebToApkFS.requestAccess() dari sebuah ketukan.

WebToApkFS.delete helper#

WebToApkFS.delete(relativePath, options)

Contoh

Menghapus file atau folder di dalam folder yang diizinkan.

Mengembalikan: boolean, sinkron: true bila berhasil dihapus. Membutuhkan: centang Native Folder Access (SAF) di Langkah 4 (Akses) saat Anda membuat build, dan folder yang diizinkan.

Hapus satu file, setelah bertanya:

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

Hapus folder dan semua isinya - kirim { 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');
}

Bersihkan ekspor yang lebih tua dari 30 hari, file demi file:

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';
}

Catatan: Tanpa { recursive: true } folder hanya dihapus bila kosong. Folder yang diizinkan itu sendiri ('') tidak pernah dapat dihapus. Penghapusan bersifat langsung dan tidak ada tempat sampah, jadi selalu konfirmasi dengan pengguna dulu.

WebToApkFS.getAccessUri helper#

WebToApkFS.getAccessUri()

Contoh

Memberikan alamat folder yang diizinkan, sehingga Anda dapat menunjukkan kepada pengguna ke mana file disimpan.

Mengembalikan: string, sinkron: URI tree seperti content://com.android.externalstorage.documents/tree/primary%3ADocuments%2FNotes, atau '' bila tidak ada folder yang diizinkan. Membutuhkan: centang Native Folder Access (SAF) di Langkah 4 (Akses) saat Anda membuat build.

Tampilkan nama folder yang mudah dibaca di samping tombol Putuskan:

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';

Sadari bahwa pengguna memilih folder lain sejak kunjungan terakhir, dan bangun ulang apa yang Anda simpan dalam cache tentang folder lama:

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();

Tombol Putuskan yang melupakan folder dan memastikan folder itu sudah hilang:

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';
});

Catatan: Metode file tidak menerima URI ini - mereka menerima path relatif terhadap folder yang diizinkan ('notes/today.txt'). Format URI milik penyedia File yang dipilih pengguna, jadi pakai hanya untuk ditampilkan.

WebToApkFS.hasAccess helper#

WebToApkFS.hasAccess()

Contoh

Memberi tahu apakah pengguna sudah memberikan sebuah folder kepada aplikasi.

Mengembalikan: boolean, sinkron. Membutuhkan: centang Native Folder Access (SAF) di Langkah 4 (Akses) saat Anda membuat build.

Tampilkan "Pilih folder" atau daftar file, tergantung jawabannya:

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');
  }
}

Simpan otomatis hanya saat folder terhubung, dan beri tahu bila tidak:

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);
});

Saat mulai, buka kembali catatan terakhir bila ada folder yang diingat, jika tidak tampilkan layar sambutan:

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);
});

Catatan: Ini hanya menyatakan aplikasi mengingat sebuah folder. Bila pengguna menghapus atau memindahkan folder itu, list('') mengembalikan [] - tawarkan "Pilih folder" lagi. window.WebToApkFS muncul saat halaman selesai dimuat; selama pemuatan gunakan yang mentah window.WebToApk.hasFolderAccess() === 'true'.

WebToApkFS.isAvailable helper#

WebToApkFS.isAvailable()

Contoh

Memberi tahu apakah aplikasi ini dapat memakai folder yang diizinkan pengguna sama sekali.

Mengembalikan: boolean, sinkron: true bila build mengaktifkan akses folder. Membutuhkan: centang Native Folder Access (SAF) di Langkah 4 (Akses) saat Anda membuat build - tanpanya (dan tanpa Secure Keys) window.WebToApkFS tidak ditambahkan ke halaman; dengan hanya Secure Keys ia ada dan menjawab false.

window.WebToApkFS ditambahkan sebelum skrip pertama halaman Anda berjalan; pada WebView yang lebih tua dari sekitar 2021 baru tiba saat halaman selesai dimuat. Untuk mencakup itu juga, tunggu sebentar sebelum Anda memutuskan:

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;
});

Di dalam ketukan tombol, halaman sudah lama selesai dimuat, jadi pemeriksaan langsung sudah cukup:

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

Jadikan kedua pemeriksaan menjadi satu keadaan yang menggerakkan seluruh layar folder:

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';

Catatan: Ini tidak menyatakan apakah folder sudah dipilih - gunakan WebToApkFS.hasAccess() untuk itu. Sebelum halaman dimuat, window.WebToApk.isFolderAccessEnabled() mentah (string "true"/"false") menjawab pertanyaan yang sama.

WebToApkFS.list helper#

WebToApkFS.list(relativePath)

Contoh

Menampilkan file dan subfolder di dalam sebuah folder dari folder yang diizinkan.

Mengembalikan: array, sinkron: [{ name, uri, isDirectory, length, lastModified }] (length dalam byte, lastModified dalam ms sejak 1970). Membutuhkan: centang Native Folder Access (SAF) di Langkah 4 (Akses) saat Anda membuat build, dan folder yang diizinkan.

Penjelajah file sederhana ('' adalah folder yang diizinkan itu sendiri):

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('');

Telusuri setiap subfolder untuk mengumpulkan semua catatan Markdown (path yang dapat Anda kirim ke 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';
}

Temukan cadangan terbaru dan berapa banyak ruang yang dipakai cadangan:

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';

Catatan: Folder kosong, folder yang tidak ada, path yang berupa file, path dengan .., dan tidak ada folder yang diizinkan semuanya mengembalikan []. Daftar tidak diurutkan. Setiap uri juga berfungsi dengan WebToApkFS.stat() dan WebToApkFS.readBytes().

WebToApkFS.mkdir helper#

WebToApkFS.mkdir(relativePath)

Contoh

Membuat folder di dalam folder yang diizinkan, termasuk folder induk yang hilang.

Mengembalikan: boolean, sinkron: true bila folder ada setelahnya. Membutuhkan: centang Native Folder Access (SAF) di Langkah 4 (Akses) saat Anda membuat build, dan folder yang diizinkan.

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.');
  }
}

Siapkan tata letak folder proyek saat pertama dipakai, dan laporkan folder mana pun yang tidak dapat dibuat:

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');

Tombol "Folder baru" yang memperingatkan bila nama sudah dipakai (karena mkdir menjawab true untuk folder yang sudah ada):

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 + '".');
});

Catatan: Mengembalikan true bila folder sudah ada, jadi memanggilnya sebelum setiap penyimpanan tidak masalah. Mengembalikan false bila sebuah FILE dengan nama itu menghalangi, path berisi .., atau tidak ada folder yang diizinkan. WebToApkFS.writeText() sudah membuat induk yang hilang, jadi Anda hanya perlu mkdir untuk folder kosong.

WebToApkFS.readText helper#

WebToApkFS.readText(relativePath)

Contoh

Membaca file teks dari folder yang diizinkan sebagai UTF-8.

Mengembalikan: string, sinkron: teks file, atau ''. Membutuhkan: centang Native Folder Access (SAF) di Langkah 4 (Akses) saat Anda membuat build, dan folder yang diizinkan.

'' berarti "file kosong" ATAU "file tidak ada". Gunakan WebToApkFS.stat() bila Anda harus tahu yang mana:

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;
  }
}

Buka catatan ke kotak teks:

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

Impor CSV yang dijatuhkan pengguna ke folder:

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');

Catatan: Seluruh file dimuat sekaligus - untuk file besar atau biner gunakan WebToApkFS.readBytes(). Byte yang bukan UTF-8 valid dikembalikan sebagai karakter pengganti. Folder, path dengan .., atau tidak ada folder yang diizinkan juga mengembalikan ''.

WebToApkFS.rename helper#

WebToApkFS.rename(relativePath, newName)

Contoh

Mengganti nama file atau folder di dalam folder yang diizinkan.

Mengembalikan: boolean, sinkron. Membutuhkan: centang Native Folder Access (SAF) di Langkah 4 (Akses) saat Anda membuat build, dan folder yang diizinkan.

newName hanyalah nama baru, bukan path. Entri tetap di folder yang sama:

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

Ganti nama tanpa bentrok: coba "nama (2)", "nama (3)" … sampai ditemukan nama yang bebas:

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');

Simpan dengan aman: tulis file sementara dulu, lalu tukar (ganti nama menolak nama yang sudah dipakai, jadi file lama dihapus tepat sebelumnya):

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);

Catatan: Mengembalikan false bila entri lain sudah memiliki newName, bila newName kosong, ./.., atau berisi / atau \, bila path tidak ada, atau bila tidak ada folder yang diizinkan. Untuk memindahkan file antar folder, baca, tulis di tempat baru, lalu hapus yang lama.

WebToApkFS.requestAccess helper#

WebToApkFS.requestAccess()

Contoh

Membuka pemilih folder sistem agar pengguna dapat memberikan satu folder ke aplikasi, yang disimpan setelah aplikasi dimulai ulang.

Mengembalikan: Promise yang selalu selesai (tidak pernah ditolak) dengan { success, folderUri, requestId }, ditambah error bila success false. success adalah boolean; folderUri adalah alamat content://…/tree/… folder yang diizinkan, atau ''. Membutuhkan: centang Native Folder Access (SAF) di Langkah 4 (Akses) saat Anda membuat build.

Minta dari tombol yang diketuk pengguna, dan tampilkan folder mana yang terhubung:

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());
});

Minta hanya bila belum ada folder yang diizinkan:

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

Tombol "Ubah folder" yang menjelaskan setiap cara permintaan bisa gagal:

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
});

Catatan: Panggil dari sebuah ketukan, jangan saat halaman dimuat - pemilih menutupi aplikasi. success: false datang bersama error: 'cancelled' (pengguna membatalkan), 'not_persisted' (Android tidak mau menyimpan izin), 'disabled', 'busy' (pemilih sudah terbuka untuk permintaan sebelumnya - permintaan itu tetap mendapat jawabannya sendiri), 'no_picker' atau 'foreign_origin'. Memilih lagi menggantikan folder sebelumnya.

WebToApkFS.writeText helper#

WebToApkFS.writeText(relativePath, content)

Contoh

Menulis file teks UTF-8 ke folder yang diizinkan, membuatnya (dan folder induk yang hilang) atau menggantikannya.

Mengembalikan: boolean, sinkron: true bila file berhasil ditulis. Membutuhkan: centang Native Folder Access (SAF) di Langkah 4 (Akses) saat Anda membuat build, dan folder yang diizinkan.

Simpan dari sebuah tombol:

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';
});

Simpan objek sebagai JSON:

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

Tambahkan baris ke log harian (tidak ada append, jadi baca, tambahkan, dan tulis kembali):

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');

Catatan: File yang ada diganti sepenuhnya - tidak ada append. content diubah menjadi string (null menjadi ''). Mengembalikan false bila path adalah folder yang ada, berisi .., atau tidak ada folder yang diizinkan. Jenis file diambil dari ekstensi, jadi nama di disk persis nama yang Anda berikan.

clearFolderAccess jembatan#

window.WebToApk.clearFolderAccess()

Melepas izin folder yang dipertahankan. Ditolak (tanpa efek) untuk halaman asing.

Contoh

Melupakan folder yang diizinkan dan mengembalikan izin ke Android.

Mengembalikan: tidak ada (sinkron). WebToApkFS.clearAccess() melakukan hal yang sama. Membutuhkan: Native Folder Access (SAF) dicentang di Langkah 4 (Akses); dengan sakelar nonaktif tidak melakukan apa-apa.

Tombol "Putuskan":

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';
});

Saat keluar, lupakan folder juga, agar orang berikutnya di ponsel ini memilih folder miliknya sendiri:

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

Lupakan folder yang dipindahkan atau dihapus di aplikasi File, lalu tawarkan untuk memilih lagi (aplikasi menulis myapp-folder.json saat folder pertama kali terhubung):

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', '{}');
  }
}

Catatan: File di folder TIDAK dihapus; aplikasi hanya tidak dapat menjangkaunya lagi. Setelah ini, hasFolderAccess() menjawab "false" dan setiap metode file gagal sampai pengguna memilih folder lagi.

deleteEntry jembatan#

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

Akses folder yang aman untuk Play memakai ACTION_OPEN_DOCUMENT_TREE dengan izin URI yang dapat dipertahankan. Penggunaan JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." atau "" window.WebToApk.clearFolderAccess() // melepas akses yang dipertahankan Callback dari Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (argumen ke-4, "" bila berhasil) menyatakan mengapa permintaan gagal: "disabled", "foreign_origin", "busy" (pemilih sudah terbuka untuk permintaan sebelumnya - permintaan itu tetap mendapat jawabannya sendiri), "cancelled", "not_persisted", "no_picker".

Dijelaskan oleh grupnya, Native Folder Access (SAF) Bridge, bukan secara tersendiri.

Contoh

Menghapus file atau folder di dalam folder yang diizinkan.

Mengembalikan: STRING "true" atau "false", sinkron. WebToApkFS.delete(path, { recursive }) mengembalikan boolean sungguhan. Membutuhkan: Native Folder Access (SAF) dicentang di Langkah 4 (Akses) dan folder yang diizinkan.

Dengan helper, setelah bertanya kepada pengguna:

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

Panggilan mentah - argumen kedua adalah STRING 'true' atau 'false':

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

Simpan hanya lima cadangan terbaru dan hapus sisanya:

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');

Catatan: Folder yang tidak kosong hanya dihapus dengan 'true' rekursif. Folder yang diizinkan itu sendiri ('') tidak pernah dapat dihapus. Penghapusan nyata dan langsung: tidak ada tempat sampah.

getFolderAccessUri jembatan#

window.WebToApk.getFolderAccessUri(): String

URI tree `content://` dari folder yang diizinkan, atau "" (tidak ada, dinonaktifkan, atau halaman asing).

Contoh

Memberikan alamat folder yang diizinkan pengguna, sehingga Anda dapat menunjukkan folder mana yang terhubung.

Mengembalikan: string, sinkron: URI tree seperti content://com.android.externalstorage.documents/tree/primary%3ADocuments%2FNotes, atau "" bila tidak ada folder yang diizinkan atau sakelar nonaktif. WebToApkFS.getAccessUri() mengembalikan string yang sama. Membutuhkan: Native Folder Access (SAF) dicentang di Langkah 4 (Akses).

Ubah URI menjadi nama yang dikenali pengguna:

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';

Muat ulang daftar file hanya bila pengguna benar-benar memilih folder yang berbeda:

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
  }
});

Katakan apakah folder ada di ponsel, di kartu SD, atau di aplikasi penyimpanan lain:

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()];

Catatan: Anda tidak pernah mengirim URI ini kembali ke metode file - semuanya menerima path relatif terhadap folder ini. Tampilkan namanya di samping tombol "Putuskan" agar pengguna tahu ke mana file disimpan.

hasFolderAccess jembatan#

window.WebToApk.hasFolderAccess(): String

Akses folder yang aman untuk Play memakai ACTION_OPEN_DOCUMENT_TREE dengan izin URI yang dapat dipertahankan. Penggunaan JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." atau "" window.WebToApk.clearFolderAccess() // melepas akses yang dipertahankan Callback dari Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (argumen ke-4, "" bila berhasil) menyatakan mengapa permintaan gagal: "disabled", "foreign_origin", "busy" (pemilih sudah terbuka untuk permintaan sebelumnya - permintaan itu tetap mendapat jawabannya sendiri), "cancelled", "not_persisted", "no_picker".

Dijelaskan oleh grupnya, Native Folder Access (SAF) Bridge, bukan secara tersendiri.

Contoh

Memberi tahu apakah pengguna sudah memberikan sebuah folder kepada aplikasi.

Mengembalikan: STRING "true" atau "false" (bukan boolean), sinkron. WebToApkFS.hasAccess() memberikan boolean sungguhan. Membutuhkan: Native Folder Access (SAF) dicentang di Langkah 4 (Akses); tanpanya jawabannya selalu "false".

Metode mentah ada sejak baris pertama halaman, jadi itu pemeriksaan yang tepat saat halaman masih dimuat (WebToApkFS baru ditambahkan saat pemuatan selesai):

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();

Periksa sebelum menyimpan dan buka pemilih hanya bila belum ada folder yang terhubung:

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));
});

Timer simpan otomatis yang menulis hanya bila folder terhubung dan tidak pernah membuka pemilih sendiri:

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);

Catatan: Bandingkan dengan === 'true'; string "false" bernilai truthy. Ini hanya menyatakan aplikasi mengingat sebuah folder. Bila folder itu dihapus atau dipindahkan di aplikasi File, listFolderEntries('') menjawab [] - minta pengguna memilihnya lagi.

isFolderAccessEnabled jembatan#

window.WebToApk.isFolderAccessEnabled(): String

Akses folder yang aman untuk Play memakai ACTION_OPEN_DOCUMENT_TREE dengan izin URI yang dapat dipertahankan. Penggunaan JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." atau "" window.WebToApk.clearFolderAccess() // melepas akses yang dipertahankan Callback dari Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (argumen ke-4, "" bila berhasil) menyatakan mengapa permintaan gagal: "disabled", "foreign_origin", "busy" (pemilih sudah terbuka untuk permintaan sebelumnya - permintaan itu tetap mendapat jawabannya sendiri), "cancelled", "not_persisted", "no_picker".

Dijelaskan oleh grupnya, Native Folder Access (SAF) Bridge, bukan secara tersendiri.

Contoh

Memberi tahu apakah aplikasi ini dibuat dengan akses folder diaktifkan sama sekali.

Mengembalikan: STRING "true" atau "false", sinkron. Membutuhkan: tidak ada untuk memanggilnya; menjawab "true" hanya bila Native Folder Access (SAF) dicentang di Langkah 4 (Akses).

Gunakan untuk memutuskan apakah akan menampilkan fitur folder. WebToApkFS.isAvailable() menjawab pertanyaan yang sama sebagai boolean setelah halaman dimuat:

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
}

Jalankan baris pengaturan melalui tiga statusnya (tidak ada di build ini, tidak terhubung, terhubung):

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;
}

Tandai halaman sebelum digambar, agar CSS dapat menampilkan kontrol khusus folder tanpa berkedip:

// 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 }
}

Catatan: Ini tidak menyatakan apakah folder sudah dipilih - gunakan hasFolderAccess() untuk itu. Di browser window.WebToApk tidak ada, jadi pemeriksaan di atas menjawab false tanpa melempar kesalahan.

listFolderEntries jembatan#

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

Akses folder yang aman untuk Play memakai ACTION_OPEN_DOCUMENT_TREE dengan izin URI yang dapat dipertahankan. Penggunaan JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." atau "" window.WebToApk.clearFolderAccess() // melepas akses yang dipertahankan Callback dari Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (argumen ke-4, "" bila berhasil) menyatakan mengapa permintaan gagal: "disabled", "foreign_origin", "busy" (pemilih sudah terbuka untuk permintaan sebelumnya - permintaan itu tetap mendapat jawabannya sendiri), "cancelled", "not_persisted", "no_picker".

Dijelaskan oleh grupnya, Native Folder Access (SAF) Bridge, bukan secara tersendiri.

Contoh

Menampilkan file dan subfolder di dalam sebuah folder dari folder yang diizinkan.

Mengembalikan: STRING JSON, sinkron: array { name, uri, isDirectory, length, lastModified } (length dalam byte, lastModified dalam ms sejak 1970). WebToApkFS.list(path) mengurainya untuk Anda. Membutuhkan: Native Folder Access (SAF) dicentang di Langkah 4 (Akses) dan folder yang diizinkan.

Dengan helper ('' adalah folder yang diizinkan itu sendiri):

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

Panggilan mentah, yang mengembalikan teks yang harus Anda urai:

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

Temukan file cadangan terbaru dan tampilkan kapan dibuat:

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';

Catatan: Folder kosong, folder yang tidak ada, path yang berupa file, path dengan .., tidak ada folder yang diizinkan, dan halaman dari situs web asing semuanya menjawab [] - panggil hasFolderAccess() dulu bila Anda perlu membedakan "tanpa folder". Path bersifat relatif, memakai /, dan tidak pernah naik di atas folder yang diizinkan.

mkdir jembatan#

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

Akses folder yang aman untuk Play memakai ACTION_OPEN_DOCUMENT_TREE dengan izin URI yang dapat dipertahankan. Penggunaan JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." atau "" window.WebToApk.clearFolderAccess() // melepas akses yang dipertahankan Callback dari Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (argumen ke-4, "" bila berhasil) menyatakan mengapa permintaan gagal: "disabled", "foreign_origin", "busy" (pemilih sudah terbuka untuk permintaan sebelumnya - permintaan itu tetap mendapat jawabannya sendiri), "cancelled", "not_persisted", "no_picker".

Dijelaskan oleh grupnya, Native Folder Access (SAF) Bridge, bukan secara tersendiri.

Contoh

Membuat folder di dalam folder yang diizinkan, termasuk folder induk yang hilang.

Mengembalikan: STRING "true" atau "false", sinkron. WebToApkFS.mkdir(path) mengembalikan boolean sungguhan. Membutuhkan: Native Folder Access (SAF) dicentang di Langkah 4 (Akses) dan folder yang diizinkan.

Dengan helper:

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');
}

Panggilan mentah:

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

Buat folder bertanggal untuk satu ekspor dan tulis setiap item ke dalamnya:

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;
}

Catatan: Menjawab "true" bila folder sudah ada, jadi aman dipanggil sebelum setiap penyimpanan. Menjawab "false" bila sebuah FILE dengan nama itu menghalangi, path berisi .., atau tidak ada folder yang diizinkan.

readTextFile jembatan#

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

Akses folder yang aman untuk Play memakai ACTION_OPEN_DOCUMENT_TREE dengan izin URI yang dapat dipertahankan. Penggunaan JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." atau "" window.WebToApk.clearFolderAccess() // melepas akses yang dipertahankan Callback dari Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (argumen ke-4, "" bila berhasil) menyatakan mengapa permintaan gagal: "disabled", "foreign_origin", "busy" (pemilih sudah terbuka untuk permintaan sebelumnya - permintaan itu tetap mendapat jawabannya sendiri), "cancelled", "not_persisted", "no_picker".

Dijelaskan oleh grupnya, Native Folder Access (SAF) Bridge, bukan secara tersendiri.

Contoh

Membaca file teks dari folder yang diizinkan sebagai UTF-8.

Mengembalikan: string, sinkron: teks file, atau "". WebToApkFS.readText(path) mengembalikan hal yang sama. Membutuhkan: Native Folder Access (SAF) dicentang di Langkah 4 (Akses) dan folder yang diizinkan.

"" berarti "file kosong" ATAU "file tidak ada", jadi periksa dengan WebToApkFS.stat() bila perbedaannya penting:

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;

Panggilan mentah:

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

Impor setiap catatan .txt dan .md dari sebuah folder:

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'));

Catatan: Seluruh file dibaca ke memori - cocok untuk catatan dan JSON, bukan untuk file besar; gunakan WebToApkFS.readBytes untuk itu. Byte yang bukan UTF-8 valid dikembalikan sebagai karakter pengganti. Path folder, path dengan .., tidak ada folder yang diizinkan, atau halaman dari situs web asing semuanya menjawab "".

renameEntry jembatan#

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

Akses folder yang aman untuk Play memakai ACTION_OPEN_DOCUMENT_TREE dengan izin URI yang dapat dipertahankan. Penggunaan JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." atau "" window.WebToApk.clearFolderAccess() // melepas akses yang dipertahankan Callback dari Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (argumen ke-4, "" bila berhasil) menyatakan mengapa permintaan gagal: "disabled", "foreign_origin", "busy" (pemilih sudah terbuka untuk permintaan sebelumnya - permintaan itu tetap mendapat jawabannya sendiri), "cancelled", "not_persisted", "no_picker".

Dijelaskan oleh grupnya, Native Folder Access (SAF) Bridge, bukan secara tersendiri.

Contoh

Mengganti nama file atau folder di dalam folder yang diizinkan.

Mengembalikan: STRING "true" atau "false", sinkron. WebToApkFS.rename(path, newName) mengembalikan boolean sungguhan. Membutuhkan: Native Folder Access (SAF) dicentang di Langkah 4 (Akses) dan folder yang diizinkan.

newName adalah nama biasa, bukan path - entri tetap di folder yang sama:

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?');
}

Panggilan mentah:

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

Simpan cadangan sebelumnya dengan mengganti namanya sebelum yang baru ditulis:

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);
}

Catatan: Gagal bila entri lain sudah memiliki newName, bila newName kosong atau berisi / atau \, bila path tidak ada, atau bila tidak ada folder yang diizinkan. Untuk memindahkan file ke folder lain, tulis di sana lalu hapus yang lama.

requestFolderAccess jembatan#

window.WebToApk.requestFolderAccess(requestId: String)

Membuka pemilih folder sistem dan mempertahankan akses baca/tulis ke folder yang dipilih. Jawabannya tiba di `WebToApkOnFolderAccessResult`; hanya satu pemilih yang dapat terbuka sekaligus, jadi permintaan yang dibuat saat satu terbuka langsung dijawab dengan `error:"busy"` dan yang terbuka tetap mendapat jawabannya sendiri.

Contoh

Membuka pemilih folder sistem agar pengguna dapat memberikan satu folder ke aplikasi. Halaman biasanya memakai WebToApkFS.requestAccess(), yang membungkus panggilan ini dalam Promise.

Mengembalikan: tidak ada. Jawabannya datang kemudian lewat callback internal milik WebToApkFS; WebToApkFS.requestAccess() selesai dengan { success, folderUri, requestId }, ditambah error bila success false. Membutuhkan: centang Native Folder Access (SAF) di Langkah 4 (Akses) saat Anda membuat build.

Minta dari ketukan tombol, dengan helper:

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
  }
});

Minta hanya pada kali pertama, lalu siapkan subfolder milik aplikasi sendiri:

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());
});

Beri tahu pengguna persis mengapa tidak ada folder yang terhubung, memakai kode error yang sebenarnya:

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.');
});

Catatan: Jangan panggil metode mentah sendiri: satu-satunya jawabannya adalah callback yang didengarkan helper. success bernilai false bila pengguna membatalkan (error:'cancelled'), bila Android tidak mau menyimpan izin ('not_persisted'), dan langsung bila build tidak mengaktifkan sakelar ('disabled') atau halaman di layar bukan milik aplikasi sendiri ('foreign_origin'). Izin disimpan setelah aplikasi dimulai ulang; memilih lagi menggantikan folder. Hanya satu pemilih yang dapat terbuka sekaligus - permintaan yang dibuat saat satu terbuka langsung dijawab dengan error:'busy', dan yang terbuka tetap mendapat jawabannya sendiri.

writeTextFile jembatan#

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

Akses folder yang aman untuk Play memakai ACTION_OPEN_DOCUMENT_TREE dengan izin URI yang dapat dipertahankan. Penggunaan JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." atau "" window.WebToApk.clearFolderAccess() // melepas akses yang dipertahankan Callback dari Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (argumen ke-4, "" bila berhasil) menyatakan mengapa permintaan gagal: "disabled", "foreign_origin", "busy" (pemilih sudah terbuka untuk permintaan sebelumnya - permintaan itu tetap mendapat jawabannya sendiri), "cancelled", "not_persisted", "no_picker".

Dijelaskan oleh grupnya, Native Folder Access (SAF) Bridge, bukan secara tersendiri.

Contoh

Menulis file teks (UTF-8) ke folder yang diizinkan, membuatnya atau mengganti isinya.

Mengembalikan: STRING "true" atau "false", sinkron. WebToApkFS.writeText(path, text) mengembalikan boolean sungguhan. Membutuhkan: Native Folder Access (SAF) dicentang di Langkah 4 (Akses) dan folder yang diizinkan.

Dengan helper:

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);

Panggilan mentah, yang jawabannya berupa teks:

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

Tambahkan baris ke log harian - menulis menggantikan file, jadi baca dulu:

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');

Catatan: File yang ada diganti sepenuhnya. Gagal bila path adalah folder yang ada, berisi .., atau tidak ada folder yang diizinkan. Jenis file diambil dari ekstensi (.json, .txt, .md…), jadi nama yang Anda berikan adalah nama di disk.

Dibuat dari runtime aplikasi dan file contohnya pada setiap build dokumentasi. Baca sebagai Markdown · Semua keluarga.

Diperiksa terhadap jembatan yang dirilis pada 2026-10-22.