أنشئ APK مجانًا

واجهة JavaScript bridge API

المشاركة والحافظة والطباعة

ورقة المشاركة في Android، وحافظة النظام، والنص الذي شاركه تطبيق آخر إلى تطبيقك، ومربع الطباعة.

AppMint.pdf دالة مساعدة (helper)#

AppMint.pdf.fromPage(options) / fromHtml(html, options)

مثال

ينشئ PDF حقيقيًا (نص قابل للتحديد ورسومات متجهية) دون نافذة طباعة: fromPage يحفظ الصفحة كما هي على الشاشة، مرتّبة بحسب CSS الخاص بـ @media print؛ وfromHtml يعرض HTML تمرّره في صفحة غير مرئية على عنوان التطبيق نفسه، فتُحمَّل الصور وأوراق الأنماط والخطوط النسبية كما في صفحتك، وينتظر الخطوط وكل الصور، ثم يحفظه. أما window.print() فما زال يفتح نافذة الطباعة المعتادة.

يُرجع: Promise بـ {ok: true, fileName, pages, bytes, saved, uri?, base64?} أو {ok: false, code, message}، حيث code هو busy (PDF واحد في كل مرة) أو bad-options أو failed أو too-large (base64 فوق 25 MB) أو permission-denied (رُفض التخزين على Android 7-9). يتطلب: لا شيء، فكل إنشاء يملكه. الحفظ في التنزيلات لا يحتاج إذنًا على Android 10+.

الخيارات: fileName (تُضاف ".pdf"؛ الافتراضي: عنوان الصفحة)، pageSize 'A4' (افتراضي) | 'Letter' | 'Legal' | 'A3' | 'A5'، landscape (true/false)، margins بالمليمتر (رقم واحد، أو {top, right, bottom, left}؛ وإن تُرك فقاعدة @page الخاصة بك)، save 'downloads' (افتراضي) | 'share' | 'base64'.

احفظ الصفحة الحالية في التنزيلات:

async function saveInvoice() {
  if (!(window.AppMint && AppMint.pdf)) { window.print(); return; }   // in a browser
  var r = await AppMint.pdf.fromPage({ fileName: 'invoice-1042', pageSize: 'A4', margins: 12 });
  if (r.ok) console.log('Saved ' + r.fileName + ' (' + r.pages + ' pages)');
  else alert('Could not make the PDF: ' + r.message);
}

إيصال مبني من HTML، مباشرة إلى ورقة المشاركة (تُحمَّل الصور والخطوط قبل إنشاء PDF):

async function shareReceipt(order) {
  var html = '<style>body{font-family:sans-serif} td{padding:4px 8px}</style>' +
    '<img src="img/logo.png" width="120"><h1>Receipt #' + order.id + '</h1><table>' +
    order.items.map(function (i) { return '<tr><td>' + i.name + '</td><td>' + i.price + '</td></tr>'; }).join('') +
    '</table><p><b>Total ' + order.total + '</b></p>';
  var r = await AppMint.pdf.fromHtml(html, { fileName: 'receipt-' + order.id, pageSize: 'Letter', save: 'share' });
  if (!r.ok) console.warn(r.code, r.message);
}

استرجع البايتات لترفعها بنفسك:

async function uploadReport() {
  var r = await AppMint.pdf.fromPage({ landscape: true, margins: { top: 10, right: 8, bottom: 10, left: 8 }, save: 'base64' });
  if (!r.ok) return;
  var bin = atob(r.base64), buf = new Uint8Array(bin.length);
  for (var i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
  await fetch('/api/reports', { method: 'POST', headers: { 'Content-Type': 'application/pdf' }, body: buf });
}

الاستدعاءات الخام تأخذ الخيارات كنص JSON ومعرّف استدعاء؛ استخدم الأداة المساعدة أعلاه بدلًا منها:

if (window.WebToApk && typeof window.WebToApk.pdfFromPage === 'function') {
  // AppMint.pdf.fromPage wraps: WebToApk.pdfFromPage(JSON.stringify(options), callbackId)
  // and AppMint.pdf.fromHtml wraps: WebToApk.pdfFromHtml(html, JSON.stringify(options), callbackId)
  AppMint.pdf.fromPage({ save: 'downloads' }).then(function (r) { console.log(r); });
}

ملاحظات: أخفِ الأزرار والقوائم من PDF بـ @media print { .no-print { display: none } }. يعرض PDF المنزَّل "تم تنزيل <name> · فتح" كأي تنزيل. fromHtml يشغّل سكربتات HTML (فتظهر المخططات المرسومة بالسكربت) لكن لا وصول له إلى ميزات التطبيق الأصلية. الصفحات الأطول من ورقة واحدة تُقسَّم كما يفعل Chrome؛ استخدم break-before: page للتحكم في مكان القسمة.

navigator.clipboard معيار الويب#

navigator.clipboard.writeText / readText (and write / read)

مثال

واجهة الحافظة القياسية، تُجاب بحافظة الهاتف الحقيقية داخل التطبيق.

يُرجع: writeText(text) ← Promise يُحَل عند النسخ ويُرفض بـ NotAllowedError وإلا. readText() ← Promise بنص الحافظة ("" عند الفراغ). write([item]) / read() ← Promises كما في المعيار. يتطلب: لا شيء. يُثبَّت عند بدء المستند، فالفحص أثناء تحليل صفحتك يراه.

نسخ:

async function copyLink(link) {
  if (!(navigator.clipboard && navigator.clipboard.writeText)) { alert('Copy is not available'); return; }
  try {
    await navigator.clipboard.writeText(link);
    showMessage('Link copied');
  } catch (e) {
    showMessage('Could not copy');
  }
}

لصق من نقرة:

document.getElementById('paste').onclick = async function () {
  try {
    var text = await navigator.clipboard.readText();
    document.getElementById('input').value = text;
  } catch (e) {
    showMessage('Could not read the clipboard');
  }
};

انسخ صورة (مثل رمز QR مرسوم على canvas) بـ ClipboardItem:

async function copyCanvas(canvas) {
  if (!(navigator.clipboard && navigator.clipboard.write && window.ClipboardItem)) {
    alert('Copying images is not available here');
    return;
  }
  var blob = await new Promise(function (r) { canvas.toBlob(r, 'image/png'); });
  try {
    await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
    showMessage('Image copied');
  } catch (e) {
    showMessage('Could not copy the image');
  }
}

ملاحظات: في التطبيق، writeText/readText يستدعيان WebToApk.copyToClipboard وWebToApk.readClipboard. write() يأخذ أول ClipboardItem فقط ويدعم text/plain وtext/html وصورة واحدة (حتى 16 MB؛ والصورة تتغلب على النص في العنصر نفسه). read() يُحَل إلى [item] بنص الحافظة أو HTML أو الصورة ([] عند الفراغ). صفحات التطبيق نفسه فقط يمكنها قراءتها: الموقع الخارجي المفتوح داخل التطبيق مرفوض (NotAllowedError). اقرأها عند نقرة من المستخدم، فـ Android 12+ يعرض إشعار "تم اللصق من الحافظة". في المتصفح تعمل الشيفرة نفسها بقواعد أذونات المتصفح.

navigator.share معيار الويب#

navigator.share(data) / navigator.canShare(data)

مثال

واجهة Web Share القياسية، مهيأة للعمل داخل التطبيق: النص والروابط تفتح ورقة المشاركة في Android، وfiles تُشارك كملفات حقيقية (WhatsApp وGmail وDrive…).

يُرجع: navigator.share ← Promise يُحَل عند فتح الورقة (لا يخبرك أي تطبيق اختير). navigator.canShare ← true/false. يتطلب: لا شيء. يُثبَّت عند بدء المستند، فالفحص أثناء تحليل صفحتك يراه.

نص ورابط:

async function shareItem(item) {
  if (!navigator.share) { alert('Sharing is not available here'); return; }
  try {
    await navigator.share({ title: item.name, text: 'Look at this', url: item.link });
  } catch (e) { /* in a browser: AbortError when the user closes the sheet */ }
}

الملفات: افحص أولًا بـ canShare:

async function sharePdf(blob) {
  var file = new File([blob], 'invoice.pdf', { type: 'application/pdf' });
  var data = { files: [file], title: 'Invoice', text: 'Invoice for March' };
  if (!(navigator.canShare && navigator.canShare(data))) {
    alert('File sharing is not available here');
    return;
  }
  try {
    await navigator.share(data);
  } catch (e) { console.log('Share failed', e); }
}

عدة ملفات اختارها المستخدم: الصور من نوع واحد تُشارك بذلك النوع:

document.getElementById('photos').onchange = async function (e) {
  var files = Array.from(e.target.files);
  if (!files.length) return;
  var data = { files: files, title: 'Trip photos' };
  if (!(navigator.canShare && navigator.canShare(data))) {
    alert('File sharing is not available here');
    return;
  }
  try {
    await navigator.share(data);
  } catch (err) { console.log('Share failed', err); }
};

ملاحظات: في التطبيق يصبح title هو الموضوع، ويُرسل text + url كنص واحد. مع files، يُقرأ كل ملف في الصفحة ويُرسل بصيغة base64 (WebToApk.shareFiles)، فاجعل الملفات بضعة MB. إذا تعذّر فتح ورقة المشاركة تُحفظ الملفات كتنزيلات بدلًا من ذلك ويُحَل الوعد مع ذلك. canShare يُرجع true لأي مشاركة نص. التطبيقات المبنية بالذكاء الاصطناعي يمكنها استخدام share من @/lib/appmintNative.

window.print معيار الويب#

window.print()

مثال

استدعاء الطباعة القياسي، مهيأ للعمل داخل التطبيق: يفتح شاشة الطباعة في Android حيث يطبع المستخدم على الورق أو يحفظ PDF.

يعيد: لا شيء. يتطلب: لا شيء.

document.getElementById('print-receipt').onclick = function () {
  window.print();
};

أخفِ عناصر تحكم التطبيق على الورق بورقة أنماط للطباعة:

<style>
  @media print {
    nav, .toolbar, button { display: none !important; }
    body { background: #fff; color: #000; }
  }
</style>

اطبع ملصق شحن دون الصفحة المحيطة به: iframe بـ srcdoc من النطاق نفسه يطبع مستنده فقط:

function esc(s) {
  return String(s).replace(/[&<>"]/g, function (c) {
    return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
  });
}
function printLabel(name, address) {
  var frame = document.createElement('iframe');
  frame.style.cssText = 'position:fixed;width:0;height:0;border:0';
  frame.srcdoc = '<title>Label</title><style>body{font:18px sans-serif;margin:24px}</style>' +
                 '<b>' + esc(name) + '</b><br>' + esc(address).replace(/\n/g, '<br>');
  frame.onload = function () {
    frame.contentWindow.print();
    setTimeout(function () { frame.remove(); }, 1000);
  };
  document.body.appendChild(frame);
}

ملاحظات: window.print() يطبع الصفحة الرئيسية كما هي مرسومة الآن. و<iframe> من النطاق نفسه، وهو إطار iframe المخفي الذي تملؤه مكتبة طباعة بإيصال وتستدعي عليه iframe.contentWindow.print()، أو صفحة iframe تستدعي window.print() الخاص بها، تطبع مستند ذلك الإطار (راجع __printHtml). أما iframe من نطاق آخر فلا يمكن طباعته من الصفحة. البديل موجود قبل أول سكربت في الصفحة. وbeforeprint لا يمكن إلغاؤه (في أي متصفح).

__clipboardRead الجسر (bridge)#

window.WebToApk.__clipboardRead(): String

navigator.clipboard.read(): `{parts:[{type, text|base64}]}`. له نفس مدى readClipboard() في هذا الغلاف (الذي لا يملك بوابة نطاق).

مثال

ناقل داخلي يقف خلف navigator.clipboard.read(). تستخدم الصفحات الواجهة القياسية؛ لا تستدعِ هذا مباشرةً أبدًا - فقد تتغير دوال __ دون إشعار.

يُرجع: {parts:[{type, text|base64}]}، بنفس مدى readClipboard() في هذا التطبيق. يتطلب: لا شيء لتفعيله.

استخدم الواجهة العامة:

canvas.toBlob(async (png) => {
  await navigator.clipboard.write([new ClipboardItem({ 'image/png': png })]);
});
const [item] = await navigator.clipboard.read();
if (item && item.types.includes('image/png')) preview(await item.getType('image/png'));

الصق نصًا منسّقًا في محرر، مع الإبقاء على التنسيق عندما تحتوي الحافظة على HTML:

async function pasteRich(editor) {
  try {
    var items = await navigator.clipboard.read();
    if (!items.length) { showMessage('The clipboard is empty'); return; }
    var item = items[0];
    if (item.types.includes('text/html')) {
      editor.insertHtml(sanitize(await (await item.getType('text/html')).text()));
    } else if (item.types.includes('text/plain')) {
      editor.insertText(await (await item.getType('text/plain')).text());
    }
  } catch (e) {
    showMessage('Could not read the clipboard');
  }
}

الصق أي صورة منسوخة (PNG أو JPEG أو WebP…) كصورة ملف شخصي، وميّز القراءة المرفوضة:

document.getElementById('paste-photo').onclick = async function () {
  try {
    var items = await navigator.clipboard.read();
    var item = items[0];
    var type = item && item.types.find(function (t) { return t.indexOf('image/') === 0; });
    if (!type) { showMessage('Copy a picture first'); return; }
    uploadAvatar(await item.getType(type));   // a Blob of that type
  } catch (e) {
    if (e.name === 'NotAllowedError') showMessage('This page cannot read the clipboard');
    else showMessage('Could not paste the picture');
  }
};

ملاحظات: يعرض Android 12+ رسالته الخاصة "تم اللصق من الحافظة" عندما يقرأ تطبيق الحافظة.

__clipboardWrite الجسر (bridge)#

window.WebToApk.__clipboardWrite(partsJson: String): Boolean

navigator.clipboard.write(): يكون [partsJson] هو `[{type, text}|{type, base64}]` لعنصر ClipboardItem واحد: text/plain أو text/html أو صورة تُنسخ كـ content URI.

مثال

ناقل داخلي يقف خلف navigator.clipboard.write([ClipboardItem]). تستخدم الصفحات الواجهة القياسية؛ لا تستدعِ هذا مباشرةً أبدًا - فقد تتغير دوال __ دون إشعار.

يُرجع: true عند وضع المقطع. يتطلب: لا شيء لتفعيله.

استخدم الواجهة العامة:

canvas.toBlob(async (png) => {
  await navigator.clipboard.write([new ClipboardItem({ 'image/png': png })]);
});
const [item] = await navigator.clipboard.read();
if (item && item.types.includes('image/png')) preview(await item.getType('image/png'));

انسخ نصًا منسّقًا ونسخته العادية في عنصر واحد، حتى تحصل المحررات الغنية على الجدول وتحصل الحقول العادية على النص:

async function copyTable(table) {
  if (!(navigator.clipboard && navigator.clipboard.write && window.ClipboardItem)) return;
  try {
    await navigator.clipboard.write([new ClipboardItem({
      'text/html': new Blob([table.outerHTML], { type: 'text/html' }),
      'text/plain': new Blob([table.innerText], { type: 'text/plain' })
    })]);
    showMessage('Table copied');
  } catch (e) {
    showMessage('Could not copy (' + e.name + ')');   // NotAllowedError when the write fails
  }
}

انسخ صورة تعرضها الصفحة، مثل رمز QR، ليمكن لصقها في محادثة:

async function copyImage(img) {
  try {
    var blob = await (await fetch(img.src)).blob();   // an image/* type, up to 16 MB
    await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
    showMessage('Image copied');
  } catch (e) {
    showMessage('Could not copy the image');
  }
}

ملاحظات: نص وHTML وصورة واحدة لكل عنصر. تُنسخ الصورة كـ content URI عبر FileProvider الخاص بالتطبيق، فيمكن لصقها في WhatsApp أو Gmail أو Keep.

__printHtml الجسر (bridge)#

window.WebToApk.__printHtml(html: String, baseUrl: String, title: String)

يطبع [html] (مستند iframe من النطاق نفسه بعد تسلسله، يرسله بديل `window.print()` ([PrintShim])) عبر WebView خارج الشاشة على [baseUrl]، لأن محوّل الطباعة في WebView الرئيسي لا يطبع إلا الصفحة كاملة. [title] يسمّي مهمة الطباعة ("<App name> Document" عند تركه فارغًا).

مثال

ناقل داخلي وراء طباعة iframe: يرسل iframe.contentWindow.print() (وwindow.print() الخاص بصفحة iframe) مستند ذلك الإطار إلى هنا، ويطبعه التطبيق عبر شاشة الطباعة في Android. الصفحات تستخدم الاستدعاءات القياسية؛ لا تستدعِ هذا مباشرة.

يعيد: لا شيء. يتطلب: لا شيء.

نمط الإيصال الذي يتيحه هذا: إطار iframe مخفي يُملأ بالإيصال فقط:

function printReceipt(order) {
  var frame = document.createElement('iframe');
  frame.style.display = 'none';
  document.body.appendChild(frame);
  var doc = frame.contentWindow.document;
  doc.open();
  doc.write('<html><head><title>Receipt ' + order.id + '</title>' +
            '<style>body{font:14px sans-serif} td{padding:2px 8px}</style></head><body>' +
            '<h2>Receipt ' + order.id + '</h2><table>' +
            order.lines.map(function (l) { return '<tr><td>' + l.name + '</td><td>' + l.price + '</td></tr>'; }).join('') +
            '</table></body></html>');
  doc.close();
  frame.contentWindow.print();   // prints the RECEIPT, not the page behind it
  setTimeout(function () { frame.remove(); }, 1000);
}

صفحة تذكرة خاصة بك، محمّلة في iframe مخفي من النطاق نفسه: تُحمَّل أنماطها وصورها، ويسمّي <title> الخاص بها مهمة الطباعة:

function printTicket(id) {
  var frame = document.createElement('iframe');
  frame.style.cssText = 'position:fixed;width:0;height:0;border:0';
  frame.src = 'ticket.html?id=' + encodeURIComponent(id);
  frame.onload = function () {
    frame.contentWindow.print();   // prints ticket.html's DOM as it stands now
    setTimeout(function () { frame.remove(); }, 1000);
  };
  document.body.appendChild(frame);
}

عدّل مستند iframe قبل اللقطة مباشرة بـ beforeprint على نافذة الإطار:

function printWithTimestamp(frame) {
  var w = frame.contentWindow;
  w.addEventListener('beforeprint', function () {
    var p = w.document.createElement('p');
    p.textContent = 'Printed ' + new Date().toLocaleString();
    w.document.body.appendChild(p);
  }, { once: true });
  w.print();   // the printed copy includes the line added above
}

ملاحظات: الوسائط هي (html, baseUrl, title): مستند iframe بعد تسلسله، والعنوان الذي تُحَل روابطه النسبية عليه (عنوان الصفحة نفسها لإطار about:blank)، واسم مهمة الطباعة (<title> الخاص بالإطار، وإلا عنوان الصفحة). تُؤخذ اللقطة عند استدعاء print()، ولا تعمل السكربتات داخلها مجددًا، وينطلق beforeprint / afterprint على نافذة الإطار حولها. أما iframe من نطاق آخر فلا يمكن طباعته من الصفحة.

clearSharedText الجسر (bridge)#

window.WebToApk.clearSharedText()

يمسح المشاركة المحفوظة بعد أن تستهلكها الصفحة.

مثال

ينسى النص أو الرابط الذي شورك إلى التطبيق، بعد أن تستخدمه صفحتك.

يُرجع: لا شيء. يتطلب: لا شيء (المشاركة نفسها تتطلب استقبال المشاركات من التطبيقات الأخرى مع تحديد النصوص والروابط).

اقرأ المشاركة المحفوظة، واستخدمها، ثم امسحها حتى لا تُعالَج مجددًا بعد إعادة التحميل:

if (window.WebToApk && typeof window.WebToApk.getSharedText === 'function') {
  var raw = window.WebToApk.getSharedText();
  if (raw) {
    var shared = JSON.parse(raw);          // { text, subject, url, receivedAt }
    saveNote(shared.subject, shared.text);
    window.WebToApk.clearSharedText();     // getSharedText() now returns ""
  }
}

window.addEventListener('appmint:shared', function (e) {
  saveNote(e.detail.subject, e.detail.text);
  if (window.WebToApk && window.WebToApk.clearSharedText) window.WebToApk.clearSharedText();
});

اسأل أولًا، وامسح المشاركة أيًّا كان الزر الذي ينقره المستخدم:

function askAboutShare(s) {
  var dialog = document.getElementById('share-dialog');
  document.getElementById('share-preview').textContent = s.text;
  dialog.hidden = false;
  function done() {
    dialog.hidden = true;
    if (window.WebToApk && typeof window.WebToApk.clearSharedText === 'function') {
      window.WebToApk.clearSharedText();
    }
  }
  document.getElementById('share-save').onclick = function () { saveNote(s.subject, s.text); done(); };
  document.getElementById('share-discard').onclick = done;
}

امسحها فقط بعد حفظها بأمان، حتى يمكن إعادة محاولة رفع فاشل:

async function importShare() {
  if (!(window.WebToApk && typeof window.WebToApk.getSharedText === 'function')) return;
  var raw = window.WebToApk.getSharedText();
  if (!raw) return;
  var s = JSON.parse(raw);
  try {
    var res = await fetch('/api/bookmarks', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ url: s.url || s.text, title: s.subject })
    });
    if (!res.ok) throw new Error('HTTP ' + res.status);
    window.WebToApk.clearSharedText();
  } catch (e) {
    showMessage('Could not save the link — tap Retry');   // Retry calls importShare() again
  }
}

ملاحظات: المسح يؤثر فقط على getSharedText(). المشاركة الجديدة تستبدل المحفوظة وتُطلق appmint:shared مجددًا. الملفات المشاركة إلى التطبيق تُحرَّر على حدة بـ AppMint.releaseOpenedFile().

copyToClipboard الجسر (bridge)#

window.WebToApk.copyToClipboard(text: String): Boolean

استخدم غلاف الوعد: const r = await AppMint.requestReview(); // {result, openedStore} const r = await AppMint.requestReview({ openStore: true }); // صفحة المتجر إن لم تظهر الورقة r.result: 'shown' | 'not_from_play' | 'quota' | 'error'. راجع RatePrompt.

موصوفة في مجموعتها، ورقة التقييم داخل التطبيق من Google Play، لا بمفردها.

مثال

ينسخ نصًا عاديًا إلى حافظة الهاتف. الصفحات تستخدم عادةً navigator.clipboard.writeText(text) القياسي، الذي يوجّهه الغلاف إلى هنا.

يُرجع: true عند النجاح، وfalse عند الفشل (بشكل متزامن). يتطلب: لا شيء.

الواجهة القياسية أولًا:

document.getElementById('copy-btn').onclick = async function () {
  try {
    await navigator.clipboard.writeText('PROMO-2026');
    showMessage('Copied');
  } catch (e) {
    showMessage('Could not copy');
  }
};

الاستدعاء الخام:

if (window.WebToApk && typeof window.WebToApk.copyToClipboard === 'function') {
  var ok = window.WebToApk.copyToClipboard('PROMO-2026');
  showMessage(ok ? 'Copied' : 'Could not copy');
}

زر "نسخ" على كل كتلة شيفرة، مع استخدام الإجابة المتزامنة لتحديث النص:

document.querySelectorAll('pre').forEach(function (pre) {
  var btn = document.createElement('button');
  btn.textContent = 'Copy';
  btn.onclick = function () {
    var ok = !!(window.WebToApk && typeof window.WebToApk.copyToClipboard === 'function' &&
                window.WebToApk.copyToClipboard(pre.innerText));
    btn.textContent = ok ? 'Copied' : 'Copy failed';
    setTimeout(function () { btn.textContent = 'Copy'; }, 1500);
  };
  pre.parentNode.insertBefore(btn, pre);
});

ملاحظات: نص عادي فقط (لا صور ولا HTML). يعرض Android 13+ معاينة "تم النسخ" الصغيرة الخاصة به، فقد لا تحتاج رسالتك الخاصة هناك.

getSharedText الجسر (bridge)#

window.WebToApk.getSharedText(): String

نص أو رابط شاركه تطبيق آخر إلى هذا التطبيق ("مشاركة إلى <app>"، وفيديو YouTube المشارَك من تطبيق YouTube يصل كرابطه)، كـ JSON `{text, subject, url, receivedAt}` أو "" عندما لا شيء مشارَك. يبقى حتى يُقرأ، فالتطبيق الذي يُحمَّل بعد المشاركة ما زال يراه؛ والمشاركة التي تصل أثناء تشغيل التطبيق تُطلق أيضًا `appmint:shared`. الصورة أو الفيديو المشارَك مع تعليق يُسلَّم ملفه عبر AppMint.getOpenedFile() والتعليق هنا. يتطلب خيار الإنشاء "استقبال المشاركات من التطبيقات الأخرى" مع تحديد "النصوص والروابط"، فهو يسجّل الإدخال في ورقة المشاركة.

مثال

نص أو رابط شاركه تطبيق آخر إلى تطبيقك ("مشاركة ← تطبيقك")، كنص JSON، أو "" عندما لا شيء مشارَك.

يُرجع: نص JSON {text, subject, url, receivedAt} أو "" (بشكل متزامن). url هو أول رابط http(s):// موجود في text (أو "")؛ وreceivedAt وقت بالميلي ثانية. يتطلب: تفعيل استقبال المشاركات من التطبيقات الأخرى عند الإنشاء، مع تحديد النصوص والروابط، وهذا يضع تطبيقك في قائمة المشاركة في التطبيقات الأخرى.

تبقى المشاركة محفوظة حتى تمسحها، فاقرأها عند البدء، واستمع للمشاركات التي تصل أثناء تشغيل التطبيق:

function handleShare(s) {
  addBookmark(s.url || s.text, s.subject);
  if (window.WebToApk && window.WebToApk.clearSharedText) window.WebToApk.clearSharedText();
}

// 1. On start (the app may have been opened by the share).
if (window.WebToApk && typeof window.WebToApk.getSharedText === 'function') {
  var raw = window.WebToApk.getSharedText();
  if (raw) handleShare(JSON.parse(raw));
}

// 2. While running: detail has the same keys { text, subject, url, receivedAt }.
window.addEventListener('appmint:shared', function (e) {
  handleShare(e.detail);
});

يتلقى خطّاف عام الكائن نفسه، إن فضّلت واحدًا:

window.onAppMintShared = function (s) { handleShare(s); };

افتح فيديو YouTube جرت مشاركته إلى التطبيق، باستخدام الحقل url:

function openSharedVideo() {
  if (!(window.WebToApk && typeof window.WebToApk.getSharedText === 'function')) return;
  var raw = window.WebToApk.getSharedText();
  if (!raw) return;
  var s = JSON.parse(raw);
  var m = /(?:youtu\.be\/|[?&]v=)([\w-]{11})/.exec(s.url || s.text);
  if (m) {
    showVideo(m[1]);
    window.WebToApk.clearSharedText();
  } else {
    showMessage('That share is not a video link');
  }
}

ملاحظات: فيديو YouTube المشارَك من تطبيق YouTube يصل كرابط. الصورة أو الفيديو المشارَك مع تعليق يُسلَّم ملفه عبر AppMint.getOpenedFile() والتعليق هنا. امسح المشاركة بعد استخدامها وإلا ستراها مجددًا عند التشغيل التالي. التطبيقات المبنية بالذكاء الاصطناعي يمكنها استخدام sharedText() / onShared() من @/lib/appmintNative.

pdfFromHtml الجسر (bridge)#

window.WebToApk.pdfFromHtml(html: String, optionsJson: String, callbackId: String)

يعرض [html] خارج الشاشة على نطاق التطبيق ويحفظه كـ PDF، استخدم `AppMint.pdf.fromHtml(html, options)`.

مثال

موثَّق مع AppMint.pdf - والمثال هناك يعرض هذا أيضًا.

ينشئ PDF حقيقيًا (نص قابل للتحديد ورسومات متجهية) دون نافذة طباعة: fromPage يحفظ الصفحة كما هي على الشاشة، مرتّبة بحسب CSS الخاص بـ @media print؛ وfromHtml يعرض HTML تمرّره في صفحة غير مرئية على عنوان التطبيق نفسه، فتُحمَّل الصور وأوراق الأنماط والخطوط النسبية كما في صفحتك، وينتظر الخطوط وكل الصور، ثم يحفظه. أما window.print() فما زال يفتح نافذة الطباعة المعتادة.

يُرجع: Promise بـ {ok: true, fileName, pages, bytes, saved, uri?, base64?} أو {ok: false, code, message}، حيث code هو busy (PDF واحد في كل مرة) أو bad-options أو failed أو too-large (base64 فوق 25 MB) أو permission-denied (رُفض التخزين على Android 7-9). يتطلب: لا شيء، فكل إنشاء يملكه. الحفظ في التنزيلات لا يحتاج إذنًا على Android 10+.

الخيارات: fileName (تُضاف ".pdf"؛ الافتراضي: عنوان الصفحة)، pageSize 'A4' (افتراضي) | 'Letter' | 'Legal' | 'A3' | 'A5'، landscape (true/false)، margins بالمليمتر (رقم واحد، أو {top, right, bottom, left}؛ وإن تُرك فقاعدة @page الخاصة بك)، save 'downloads' (افتراضي) | 'share' | 'base64'.

احفظ الصفحة الحالية في التنزيلات:

async function saveInvoice() {
  if (!(window.AppMint && AppMint.pdf)) { window.print(); return; }   // in a browser
  var r = await AppMint.pdf.fromPage({ fileName: 'invoice-1042', pageSize: 'A4', margins: 12 });
  if (r.ok) console.log('Saved ' + r.fileName + ' (' + r.pages + ' pages)');
  else alert('Could not make the PDF: ' + r.message);
}

إيصال مبني من HTML، مباشرة إلى ورقة المشاركة (تُحمَّل الصور والخطوط قبل إنشاء PDF):

async function shareReceipt(order) {
  var html = '<style>body{font-family:sans-serif} td{padding:4px 8px}</style>' +
    '<img src="img/logo.png" width="120"><h1>Receipt #' + order.id + '</h1><table>' +
    order.items.map(function (i) { return '<tr><td>' + i.name + '</td><td>' + i.price + '</td></tr>'; }).join('') +
    '</table><p><b>Total ' + order.total + '</b></p>';
  var r = await AppMint.pdf.fromHtml(html, { fileName: 'receipt-' + order.id, pageSize: 'Letter', save: 'share' });
  if (!r.ok) console.warn(r.code, r.message);
}

استرجع البايتات لترفعها بنفسك:

async function uploadReport() {
  var r = await AppMint.pdf.fromPage({ landscape: true, margins: { top: 10, right: 8, bottom: 10, left: 8 }, save: 'base64' });
  if (!r.ok) return;
  var bin = atob(r.base64), buf = new Uint8Array(bin.length);
  for (var i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
  await fetch('/api/reports', { method: 'POST', headers: { 'Content-Type': 'application/pdf' }, body: buf });
}

الاستدعاءات الخام تأخذ الخيارات كنص JSON ومعرّف استدعاء؛ استخدم الأداة المساعدة أعلاه بدلًا منها:

if (window.WebToApk && typeof window.WebToApk.pdfFromPage === 'function') {
  // AppMint.pdf.fromPage wraps: WebToApk.pdfFromPage(JSON.stringify(options), callbackId)
  // and AppMint.pdf.fromHtml wraps: WebToApk.pdfFromHtml(html, JSON.stringify(options), callbackId)
  AppMint.pdf.fromPage({ save: 'downloads' }).then(function (r) { console.log(r); });
}

ملاحظات: أخفِ الأزرار والقوائم من PDF بـ @media print { .no-print { display: none } }. يعرض PDF المنزَّل "تم تنزيل <name> · فتح" كأي تنزيل. fromHtml يشغّل سكربتات HTML (فتظهر المخططات المرسومة بالسكربت) لكن لا وصول له إلى ميزات التطبيق الأصلية. الصفحات الأطول من ورقة واحدة تُقسَّم كما يفعل Chrome؛ استخدم break-before: page للتحكم في مكان القسمة.

pdfFromPage الجسر (bridge)#

window.WebToApk.pdfFromPage(optionsJson: String, callbackId: String)

يحفظ الصفحة كـ PDF دون نافذة طباعة، استخدم `AppMint.pdf.fromPage(options)`. [optionsJson] هو `{fileName, pageSize, landscape, margins, save}`؛ ويُحَل [callbackId].

مثال

موثَّق مع AppMint.pdf - والمثال هناك يعرض هذا أيضًا.

ينشئ PDF حقيقيًا (نص قابل للتحديد ورسومات متجهية) دون نافذة طباعة: fromPage يحفظ الصفحة كما هي على الشاشة، مرتّبة بحسب CSS الخاص بـ @media print؛ وfromHtml يعرض HTML تمرّره في صفحة غير مرئية على عنوان التطبيق نفسه، فتُحمَّل الصور وأوراق الأنماط والخطوط النسبية كما في صفحتك، وينتظر الخطوط وكل الصور، ثم يحفظه. أما window.print() فما زال يفتح نافذة الطباعة المعتادة.

يُرجع: Promise بـ {ok: true, fileName, pages, bytes, saved, uri?, base64?} أو {ok: false, code, message}، حيث code هو busy (PDF واحد في كل مرة) أو bad-options أو failed أو too-large (base64 فوق 25 MB) أو permission-denied (رُفض التخزين على Android 7-9). يتطلب: لا شيء، فكل إنشاء يملكه. الحفظ في التنزيلات لا يحتاج إذنًا على Android 10+.

الخيارات: fileName (تُضاف ".pdf"؛ الافتراضي: عنوان الصفحة)، pageSize 'A4' (افتراضي) | 'Letter' | 'Legal' | 'A3' | 'A5'، landscape (true/false)، margins بالمليمتر (رقم واحد، أو {top, right, bottom, left}؛ وإن تُرك فقاعدة @page الخاصة بك)، save 'downloads' (افتراضي) | 'share' | 'base64'.

احفظ الصفحة الحالية في التنزيلات:

async function saveInvoice() {
  if (!(window.AppMint && AppMint.pdf)) { window.print(); return; }   // in a browser
  var r = await AppMint.pdf.fromPage({ fileName: 'invoice-1042', pageSize: 'A4', margins: 12 });
  if (r.ok) console.log('Saved ' + r.fileName + ' (' + r.pages + ' pages)');
  else alert('Could not make the PDF: ' + r.message);
}

إيصال مبني من HTML، مباشرة إلى ورقة المشاركة (تُحمَّل الصور والخطوط قبل إنشاء PDF):

async function shareReceipt(order) {
  var html = '<style>body{font-family:sans-serif} td{padding:4px 8px}</style>' +
    '<img src="img/logo.png" width="120"><h1>Receipt #' + order.id + '</h1><table>' +
    order.items.map(function (i) { return '<tr><td>' + i.name + '</td><td>' + i.price + '</td></tr>'; }).join('') +
    '</table><p><b>Total ' + order.total + '</b></p>';
  var r = await AppMint.pdf.fromHtml(html, { fileName: 'receipt-' + order.id, pageSize: 'Letter', save: 'share' });
  if (!r.ok) console.warn(r.code, r.message);
}

استرجع البايتات لترفعها بنفسك:

async function uploadReport() {
  var r = await AppMint.pdf.fromPage({ landscape: true, margins: { top: 10, right: 8, bottom: 10, left: 8 }, save: 'base64' });
  if (!r.ok) return;
  var bin = atob(r.base64), buf = new Uint8Array(bin.length);
  for (var i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
  await fetch('/api/reports', { method: 'POST', headers: { 'Content-Type': 'application/pdf' }, body: buf });
}

الاستدعاءات الخام تأخذ الخيارات كنص JSON ومعرّف استدعاء؛ استخدم الأداة المساعدة أعلاه بدلًا منها:

if (window.WebToApk && typeof window.WebToApk.pdfFromPage === 'function') {
  // AppMint.pdf.fromPage wraps: WebToApk.pdfFromPage(JSON.stringify(options), callbackId)
  // and AppMint.pdf.fromHtml wraps: WebToApk.pdfFromHtml(html, JSON.stringify(options), callbackId)
  AppMint.pdf.fromPage({ save: 'downloads' }).then(function (r) { console.log(r); });
}

ملاحظات: أخفِ الأزرار والقوائم من PDF بـ @media print { .no-print { display: none } }. يعرض PDF المنزَّل "تم تنزيل <name> · فتح" كأي تنزيل. fromHtml يشغّل سكربتات HTML (فتظهر المخططات المرسومة بالسكربت) لكن لا وصول له إلى ميزات التطبيق الأصلية. الصفحات الأطول من ورقة واحدة تُقسَّم كما يفعل Chrome؛ استخدم break-before: page للتحكم في مكان القسمة.

print الجسر (bridge)#

window.WebToApk.print()

يسلّم الصفحة كما هي الآن إلى مربع الطباعة في Android، الذي يمكنه الطباعة على الورق أو حفظ PDF. لا شيء لضبطه، فـ WebView يعرض المستند.

موصوف ضمن مجموعته، الطباعة، وليس على حدة.

مثال

يفتح شاشة الطباعة في Android للصفحة كما تبدو الآن: اطبع على الورق أو "احفظ كـ PDF". الصفحات تستخدم عادةً window.print() القياسي، الذي يوجّهه الغلاف إلى هنا.

يعيد: لا شيء. يتطلب: لا شيء.

الواجهة القياسية أولًا:

document.getElementById('print-btn').onclick = function () {
  window.print();   // in the app: the Android print screen
};

الاستدعاء الخام يؤدي الغرض نفسه:

if (window.WebToApk && typeof window.WebToApk.print === 'function') {
  window.WebToApk.print();
}

اطبع فاتورة واحدة من قائمة بوسمها لورقة أنماط الطباعة (اترك الوسم، فشاشة الطباعة ترتب الصفحة بعد عودة print()):

// CSS: @media print { .invoice:not(.print-me), nav, button { display: none !important; } }
function printInvoice(id) {
  document.querySelectorAll('.invoice.print-me').forEach(function (el) {
    el.classList.remove('print-me');
  });
  document.getElementById('invoice-' + id).classList.add('print-me');
  if (window.WebToApk && typeof window.WebToApk.print === 'function') {
    window.WebToApk.print();
  } else {
    window.print();   // a normal browser
  }
}

ملاحظات: يطبع WebView كاملًا، أي الصفحة الرئيسية. أما print() الخاص بـ iframe من النطاق نفسه فيطبع ذلك الإطار فقط (راجع window.print). استخدم ورقة أنماط @media print لإخفاء الأزرار والقوائم. تُسمّى المهمة "<App name> Document". تُسجَّل الأخطاء فقط.

readClipboard الجسر (bridge)#

window.WebToApk.readClipboard(): String

استخدم غلاف الوعد: const r = await AppMint.requestReview(); // {result, openedStore} const r = await AppMint.requestReview({ openStore: true }); // صفحة المتجر إن لم تظهر الورقة r.result: 'shown' | 'not_from_play' | 'quota' | 'error'. راجع RatePrompt.

موصوفة في مجموعتها، ورقة التقييم داخل التطبيق من Google Play، لا بمفردها.

مثال

يقرأ النص الموجود الآن في حافظة الهاتف. الصفحات تستخدم عادةً navigator.clipboard.readText() القياسي، الذي يوجّهه الغلاف إلى هنا.

يُرجع: النص، أو "" عندما تكون الحافظة فارغة أو لا يمكن قراءتها (بشكل متزامن). يتطلب: لا شيء.

الواجهة القياسية أولًا، مثلًا زر "لصق الرمز":

document.getElementById('paste-btn').onclick = async function () {
  try {
    var text = await navigator.clipboard.readText();
    if (text) document.getElementById('code').value = text.trim();
    else showMessage('The clipboard is empty');
  } catch (e) {
    showMessage('Could not read the clipboard');
  }
};

الاستدعاء الخام:

if (window.WebToApk && typeof window.WebToApk.readClipboard === 'function') {
  var text = window.WebToApk.readClipboard();
  if (text) document.getElementById('code').value = text;
}

زر "لصق الرابط" يأخذ أول عنوان ويب من أي شيء نُسخ:

document.getElementById('paste-link').onclick = function () {
  if (!(window.WebToApk && typeof window.WebToApk.readClipboard === 'function')) return;
  var text = window.WebToApk.readClipboard();
  var m = /https?:\/\/\S+/.exec(text);
  if (m) document.getElementById('link').value = m[0];
  else showMessage('There is no link on the clipboard');
};

ملاحظات: صفحات تطبيقك فقط يمكنها قراءتها: الموقع الخارجي المفتوح داخل التطبيق يحصل على "". لا يسمح Android للتطبيق بقراءة الحافظة إلا وهو ظاهر على الشاشة، ويعرض Android 12+ إشعار "تم اللصق من الحافظة"، فاقرأها عند نقرة من المستخدم وليس عند تحميل الصفحة.

shareFiles الجسر (bridge)#

window.WebToApk.shareFiles(filesJson: String, title: String, text: String): Boolean

مشاركة ملفات أصلية حقيقية لـ navigator.share({files:[...]}). لا يملك Android WebView واجهة Web Share، ولا يستطيع ACTION_SEND بنوع text/plain حمل ملف، لذا كانت الصورة المشارَكة قبل هذا تُحفظ فقط ولا تُشارك. filesJson هو [{name, mimeType, base64}, ...]؛ نكتب كل ملف في الذاكرة المؤقتة ونعرضه عبر FileProvider الخاص بالتطبيق ونطلق ACTION_SEND / ACTION_SEND_MULTIPLE فتُفتح ورقة المشاركة الحقيقية في Android (WhatsApp وGmail…). يُرجع true إذا فُتحت الورقة.

مثال

يشارك ملفًا حقيقيًا أو أكثر (صورة، PDF…) عبر ورقة المشاركة في Android. الصفحات تستخدم عادةً navigator.share({ files: [...] }) القياسي، الذي يوجّهه الغلاف إلى هنا.

يُرجع: true إذا فُتحت ورقة المشاركة، وfalse وإلا (بشكل متزامن). يتطلب: لا شيء.

الواجهة القياسية أولًا:

async function shareCanvas(canvas) {
  var blob = await new Promise(function (r) { canvas.toBlob(r, 'image/png'); });
  var file = new File([blob], 'drawing.png', { type: 'image/png' });
  if (!(navigator.canShare && navigator.canShare({ files: [file] }))) {
    alert('File sharing is not available here');
    return;
  }
  try {
    await navigator.share({ files: [file], title: 'My drawing', text: 'Made in my app' });
  } catch (e) { /* in a browser: the user closed the sheet */ }
}

يأخذ الاستدعاء الخام نص JSON لـ [{name, mimeType, base64}] (base64 بدون البادئة data:)، ثم title و text:

function shareBase64Png(base64) {
  if (!(window.WebToApk && typeof window.WebToApk.shareFiles === 'function')) return false;
  var files = [{ name: 'receipt.png', mimeType: 'image/png', base64: base64 }];
  return window.WebToApk.shareFiles(JSON.stringify(files), 'Receipt', 'Your receipt');
}

صدّر جدولًا كملف CSV (يُرمَّز النص بـ UTF-8 قبل base64):

function shareCsv(rows) {
  if (!(window.WebToApk && typeof window.WebToApk.shareFiles === 'function')) return;
  var csv = rows.map(function (r) {
    return r.map(function (c) { return '"' + String(c).replace(/"/g, '""') + '"'; }).join(',');
  }).join('\n');
  var base64 = btoa(unescape(encodeURIComponent(csv)));
  var files = [{ name: 'expenses.csv', mimeType: 'text/csv', base64: base64 }];
  if (!window.WebToApk.shareFiles(JSON.stringify(files), 'Expenses', '')) {
    showMessage('Could not open the share sheet');
  }
}

ملاحظات: تُكتب الملفات في ذاكرة التطبيق المؤقتة وتُشارك عبر FileProvider الخاص به. الأحرف غير الآمنة في name تصبح _. عدة ملفات من نوع واحد تُشارك بذلك النوع؛ والأنواع المختلطة تُشارك كـ */*. يسافر الملف كاملًا بصيغة base64 عبر الجسر، فاجعله بضعة MB. عندما يتعذّر على navigator.share({files}) فتح الورقة، يحفظ الغلاف الملفات كتنزيلات بدلًا من ذلك ويُحَل الوعد مع ذلك.

shareNative الجسر (bridge)#

window.WebToApk.shareNative(title: String, text: String, url: String)

ورقة المشاركة في Android، وحافظة النظام، وإغلاق نافذة فتحتها الصفحة. هذه معادلات الويب العادي (navigator.share وnavigator.clipboard وwindow.close) تُجاب أصليًا لتعمل بالطريقة نفسها داخل التطبيق.

موصوف ضمن مجموعته، المشاركة والحافظة والنافذة، وليس على حدة.

مثال

يفتح ورقة المشاركة في Android بعنوان ونص ورابط. الصفحات تستخدم عادةً navigator.share({title, text, url}) القياسي، الذي يوجّهه الغلاف إلى هنا.

يُرجع: لا شيء (لا يمكنه إخبارك أي تطبيق اختاره المستخدم). يتطلب: لا شيء.

الواجهة القياسية أولًا، فهي تعمل أيضًا في متصفح الجوال:

document.getElementById('share-btn').onclick = async function () {
  if (!navigator.share) { alert('Sharing is not available here'); return; }
  try {
    await navigator.share({ title: 'My app', text: 'Look at this', url: 'https://mysite.com/item/42' });
  } catch (e) {
    // in a browser: AbortError when the user closes the sheet
  }
};

الاستدعاء الخام، والوسائط الثلاث كلها نصوص (استخدم '' لترك أحدها):

if (window.WebToApk && typeof window.WebToApk.shareNative === 'function') {
  window.WebToApk.shareNative('My app', 'Look at this', 'https://mysite.com/item/42');
}

نص فقط بلا رابط، مثل نتيجة لعبة:

function shareScore(score) {
  if (!(window.WebToApk && typeof window.WebToApk.shareNative === 'function')) return;
  window.WebToApk.shareNative('My score', 'I scored ' + score + ' points. Can you beat it?', '');
}

ملاحظات: النص المشارَك هو text + سطر جديد + url (أو أحدهما فقط). يصبح title موضوع البريد وعنوان ورقة المشاركة. نص وروابط فقط، ولمشاركة ملف استخدم navigator.share({files}) (راجع shareFiles).

أزرار share: في رسائل داخل التطبيق. الزر الذي رابطه share:<text> يفتح ورقة المشاركة نفسها بذلك النص، دون شيفرة في الصفحة. قالب دعوة الأصدقاء يستخدم share:Try {app}: {store}؛ ويملأ التطبيق اسمه وعنوانه في Play. يمكن لصفحتك معرفة أن ذلك حدث:

window.addEventListener('appmint:inapp', function (e) {
  var d = e.detail;
  if (d.type === 'clicked' && d.link && d.link.indexOf('share:') === 0) analytics('invite_shared', d.name);
});

إذا رسمت رسائل داخل التطبيق بنفسك (setInAppPresentation('none'))، فشغّل زر share: بالطريقة نفسها:

function runInAppButton(link) {
  if (link.indexOf('share:') === 0 && window.WebToApk && WebToApk.shareNative) {
    WebToApk.shareNative('', link.slice(6), '');   // text only
  }
}

رابط copy:<text> ينسخ بدل أن يشارك، مثل رمز قسيمة من رسالة ترويجية:

window.addEventListener('appmint:inapp', function (e) {
  var d = e.detail;
  if (d.type === 'clicked' && d.link === 'copy:SAVE20') showToast('Code SAVE20 copied');
});

يتم إنشاؤه من بيئة تشغيل التطبيق وملفات الأمثلة الخاصة به عند كل بناء للمستندات. اقرأه بصيغة Markdown · جميع العائلات.

تمت المراجعة مقابل الجسر المُرفق بتاريخ 2026-10-22.