برمجه وتصميم

أضف وظائف Office إلى تطبيق الويب الخاص بك باستخدام OnlyOffice

تم إنشاء هذه المقالة بالشراكة مع أونلي أوفيس. شكرًا لك على دعم الشركاء الذين جعلوا SitePoint ممكنًا.

عندما نجد أنفسنا نحاول إضافة أي وظائف معقدة إلى تطبيق ما ، فإن السؤال الذي يطرح نفسه ، “هل يجب أن أقوم بتشغيل بلدي؟” وما لم يكن هدفك هو بناء تلك الوظيفة ، فستكون الإجابة دائمًا تقريبًا “لا” مباشرة.

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

سأفترض أنك ما زلت هنا لأن ما ورد أعلاه له صدى معك. الآن ، بعد أن قمنا بالمزامنة ، ما أريد أن أوضحه لك في هذه المقالة هو مدى سهولة دمج OnlyOffice في تطبيق الويب الخاص بك.

ما هو OnlyOffice؟

من عند موقعة على الإنترنت:

تقدم OnlyOffice مجموعة المكاتب المتوفرة الأكثر ثراءً بالميزات ، وهي متوافقة للغاية مع تنسيقات ملفات Microsoft Office و OpenDocument. يمكنك عرض المستندات وجداول البيانات والعروض التقديمية وتحريرها والعمل بشكل تعاوني من تطبيق الويب الخاص بك.

في هذه الحالة ، سنستخدم ملف إصدار المطور، نظرًا لأنه الأفضل لغرضنا ، ولكن إذا كنت تتطلع إلى التكامل مع خدمات أخرى مثل SharePoint ، فعليك التحقق من إصدار التكامل.

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

إذا كنت تريد استخدام OnlyOffice ضمن حل مزامنة ومشاركة موجود ، فيجب عليك التحقق من Enterprise Edition. قائمة التكامل هي هنا.

إصدار المطور

لا يمنحك إصدار المطور الحرية الكافية لدمج المحررين داخل تطبيقك فحسب ، ولكنه يأتي أيضًا مع خيار “White Label” الذي يتيح لك تخصيص المحررين بالكامل لاستخدامهم تحت علامتك التجارية الخاصة.

تكامل خادم المستندات

للتكامل مع تطبيق الويب الخاص بك ، تحتاج أولاً إلى تنزيل ملف مستندات OnlyOffice (في حزم كخادم مستندات) وقم بإعداده على الخادم المحلي الخاص بك.

بعد تثبيته ، يمكنك البدء في تنفيذ الطلبات للتعامل مع المستندات على الخادم الخاص بك. يوفر OnlyOffice بعض لطيفة للغاية أمثلة إلى عن على .شبكة، جافا، Node.js، بي أتش بي، بايثون و روبي.

يمكنك تنزيل Document Server والمثال المفضل لديك وتجربته على الفور على جهازك.

سأوضح كيف يمكنك البدء في الاندماج في تطبيقك. لهذا الغرض ، سنستخدم مثالًا بسيطًا جدًا مع Node.js و Express. لن أخوض في الكثير من التفاصيل حول التنفيذ ، سأضع أساسيات العظام العارية وأسمح لك بملء الفراغات لبناء نظام قوي وقابل للتطوير.

لدي تطبيق بالهيكل التالي:

- node_modules
- public
    - backups
    - css
        - main.css
    - documents
        - sample.docx
    - javascript
        - main.js
    - samples
        - new.docx
        - new.xlsx
        - new.pptx
- app.js
- index.html
- package.json

سنستخدم ملف public/documents مجلد لتخزين المستندات. ال app.js الملف حيث يوجد رمز تطبيق Express الخاص بنا ، و index.html هو المكان الذي سنعرض فيه مستنداتنا. لقد أسقطت sample.docx ملف في مجلد المستندات لأغراض الاختبار.

ملفات الشجرة بالداخل public/samples/ هي الملفات الفارغة التي سننسخها عند “إنشاء” ملفات جديدة.

ال backups مجلد ، كما سترى لاحقًا ، لن يساعدنا فقط في الاحتفاظ بنسخ احتياطية من الإصدارات السابقة ، بل سيساعدنا أيضًا في إنشاء المعرف الفريد لمستنداتنا بعد تعديلها.

ال public/css/main.css و public/javascript/main.js سيتم استخدام الملفات بواسطة ملف index.html. سننظر في ذلك لاحقًا.

دعنا نلقي نظرة على app.js ملف:

const express = require('express');
const bodyParser = require("body-parser");
const path = require('path');
const fs = require('fs');
const syncRequest = require('sync-request');

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.use(express.static("public"));

app.get("https://www.sitepoint.com/", (req, res) => {
  res.sendFile(path.join(__dirname, "/index.html"));
});

const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`App listening on http://localhost:${port}`));

ما نفعله هو خدمة الملفات بصيغة localhost:3000/documents/filename.

لقد تقدمت أيضًا على نفسي وأضفت syncRequest، fsو و bodyParser. هذه ليست ذات صلة في الوقت الحالي ولكننا سنستخدمها لاحقًا.

إحضار المستندات

لإظهار المستندات المتاحة ، سنحتاج إلى الحصول على قائمة بجميع أسماء الملفات وإرسالها إلى العميل. سنقوم بإنشاء ملف /documents طريق لهذا:

app.get("/documents", (req, res) => {
  const docsPath = path.join(__dirname, "public/documents");
  const docsPaths = fs.readdirSync(docsPath);

  const fileNames = [];

  docsPaths.forEach(filePath => {
    const fileName = path.basename(filePath);
    fileNames.push(fileName);
  });

  res.send(fileNames);
});

إنشاء المستندات

في البداية ، سيكون لدينا نموذج من المستند فقط ، ولكن هذا ليس ممتعًا على الإطلاق. دعنا نضيف ملف /create الطريق لمساعدتنا في إضافة بعض الملفات. سنأخذ ببساطة ملف fileName وانسخ القالب المقابل في ملف public/documents مجلد باسمه الجديد:

app.post("/create", async (req, res) => {
  const ext = path.extname(req.query.fileName);
  const fileName = req.query.fileName;

  const samplePath = path.join(__dirname, "public/samples", "new" + ext);
  const newFilePath = path.join(__dirname, "public/documents", fileName);

  
  try {
    fs.copyFileSync(samplePath, newFilePath);
    res.sendStatus(200);
  } catch (e) {
    res.sendStatus(400);
  }
});

احذف المستندات

نحتاج أيضًا إلى طريقة لحذف المستندات. لنقم بإنشاء ملف /delete طريق:

app.delete("/delete", (req, res) => {
  const fileName = req.query.fileName;
  const filePath = path.join(__dirname, "public/documents", fileName);

  try {
    fs.unlinkSync(filePath);
    res.sendStatus(200);
  } catch (e) {
    res.sendStatus(400);
  }
});

هذا بسيط للغاية. سنحذف الملف ونرسل ملف 200 رمز الحالة للسماح للمستخدم بمعرفة أن كل شيء سار على ما يرام. خلاف ذلك ، سيحصلون على ملف 400 رمز الحالة.

حفظ المستندات

حتى الآن ، يمكننا فتح مستنداتنا للتحرير ، لكن ليس لدينا طريقة لحفظ التغييرات. لنفعل ذلك الآن. سنضيف ملف /track الطريق لحفظ ملفاتنا:

app.post("/track", async (req, res) => {
  const fileName = req.query.fileName;

  const backupFile = filePath => {
    const time = new Date().getTime();
    const ext = path.extname(filePath);
    const backupFolder = path.join(__dirname, "public/backups", fileName + "-history");

    
    !fs.existsSync(backupFolder) && fs.mkdirSync(backupFolder);

    
    const previousBackup = fs.readdirSync(backupFolder)[0];
    previousBackup && fs.unlinkSync(path.join(backupFolder, previousBackup));

    const backupPath = path.join(backupFolder, time + ext);

    fs.copyFileSync(filePath, backupPath);
  }

  const updateFile = async (response, body, path) => {
    if (body.status == 2) {
      backupFile(path);
      const file = syncRequest("GET", body.url);
      fs.writeFileSync(path, file.getBody());
    }

    response.write("{"error":0}");
    response.end();
  }

  const readbody = (request, response, path) => {
    const content = "";
    request.on("data", function (data) {
      content += data;
    });
    request.on("end", function () {
      const body = JSON.parse(content);
      updateFile(response, body, path);
    });
  }

  if (req.body.hasOwnProperty("status")) {
    const filePath = path.join(__dirname, "public/documents", fileName);
    updateFile(res, req.body, filePath);
  } else {
    readbody(req, res, filePath);
  }
});

هذا أمر صعب ، لأنه سيتم استخدامه بواسطة Document Server عندما يتم حفظ الملف بواسطة المحرر. كما ترون ، نحن نعود "{"error":0}"، الذي يخبر الخادم أن كل شيء على ما يرام.

عند إغلاق المحرر ، سيتم نسخ الإصدار الحالي من الملف احتياطيًا بتنسيق public/backups/fileName-history/ مع الوقت الحالي بالمللي ثانية كاسم الملف. سنستخدم اسم الملف لاحقًا في الواجهة الأمامية ، كما سترى.

في هذا المثال ، نقوم باستبدال النسخة الاحتياطية السابقة في كل مرة نقوم فيها بحفظ نسخة جديدة. كيف ستشرع في الاحتفاظ بنسخ احتياطية أكثر؟

إحضار النسخ الاحتياطية

سنحتاج إلى طريقة للحصول على النسخ الاحتياطية لملف معين ، لذلك نضيف ملف /backups طريق للتعامل مع هذا:

app.get("/backups", (req, res) => {
  const fileName = req.query.fileName;
  const backupsPath = path.join(__dirname, "public/backups", fileName + "-history");

  if (!fs.existsSync(backupsPath)) {
    return res.send([]);
  }

  const backupsPaths = fs.readdirSync(backupsPath);

  const fileNames = [];

  backupsPaths.forEach(filePath => {
    const fileName = path.basename(filePath);
    fileNames.push(fileName);
  });

  res.send(fileNames);
});

نحن هنا نتأكد من وجود مجلد النسخ الاحتياطي لهذا الملف ، ونعيد مجموعة من جميع ملفات النسخ الاحتياطي في هذا المجلد. نعم ، سيساعدك هذا في مهمتك المتمثلة في الاحتفاظ بمزيد من النسخ الاحتياطية لملف واحد. لا يمكنني الاستمرار في القيام بكل العمل من أجلك!

فتح مستند في المستعرض

سنرى كيف يمكننا فتح مستنداتنا لتحريرها مباشرة في المتصفح باستخدام OnlyOffice Docs.

فتح وثيقة

أولاً ، سننشئ ملف HTML بسيطًا:

<!DOCTYPE html>
<html>

<head>
  <title>OnlyOffice Example</title>

  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
  <link rel="stylesheet" href="/public/css/main.css">
</head>

<body>
  <div id="placeholder"></div>
  <div id="documents">
    <h1>Documents</h1>
    <div id="document-controls">
      <div onclick="createDocument('.docx')">Create docx</div>
      <div onclick="createDocument('.xlsx')">Create xlsx</div>
      <div onclick="createDocument('.pptx')">Create pptx</div>
    </div>
  </div>
  <script type="text/javascript" src="http://localhost:8080/web-apps/apps/api/documents/api.js"></script>
  <script type="text/javascript" src="/public/javascript/main.js"></script>
</body>

</html>

كما ترى ، ليس هناك الكثير لهذا الملف. لدينا ال placeholder div حيث سيتم إرفاق المحرر. ثم هناك documents div ، والذي يحتوي على عناصر التحكم لإنشاء المستندات وحاوية لقائمة أسماء الملفات.

أدناه ، لدينا البرنامج النصي مع JavaScript API لخادم المستندات. ضع في اعتبارك أنه قد يتعين عليك استبدال المضيف بموقع خادم المستندات الخاص بك. إذا قمت بتثبيته مع Docker الأمر الذي أعطيته لك ، يجب أن تكون على ما يرام.

أخيرًا وليس آخرًا ، هناك script علامة ، حيث نقوم باستيراد JavaScript للواجهة الأمامية ، و main.js ملف ، حيث سيكون لدينا وصول عالمي إلى ملف DocsAPI موضوع.

CSS

قبل أن نصل إلى الترميز ، دعنا نختتم التصميم ببعض CSS لجعل تطبيقنا أكثر قابلية للاستخدام وأقل قبحًا. أضف ما يلي إلى main.css:

html,
body {
  font-family: monospace;
  height: 100%;
  margin: 0;
  background-color: lavender;
  color: aliceblue;
}

h1 {
  color: lightslategray;
  display: inline-block;
}

#placeholder {
  height: 100%;
}

#documents {
  text-align: center;
}

#document-controls {
  text-align: center;
  margin: 5px;
}

#document-controls>div {
  display: inline-block;
  font-size: 15px;
  cursor: pointer;
  padding: 10px;
  background: mediumaquamarine;
}

#documents-list {
  padding: 5px;
  max-width: 400px;
  margin: auto;
}

.document {
  cursor: pointer;
  font-size: 20px;
  text-align: left;
  padding: 5px;
  margin: 2px;
  background-color: lightsteelblue;
}

.delete-doc {
  color: lightslategray;
  float: right;
  margin: 0 5px 0 5px;
}

إظهار المستندات المتوفرة

بهذا بعيدًا ، نحن على استعداد لبدء ترميز الواجهة الأمامية. سنبدأ بإدراج الملفات في ملف documents مجلد. اذهب إلى main.js وأضف الكود التالي:

const params = new URLSearchParams(window.location.search);
const fileName = params.get("fileName");

if (fileName) {
  editDocument(fileName);
} else {
  listDocuments();
}

function listDocuments() {
  
  document.getElementById("placeholder").style.display = "none";
  
  const oldList = document.getElementById("documents-list");
  oldList && oldList.remove();
  
  const documentsHtml = document.getElementById("documents");
  const docsListHtml = document.createElement("div");
  docsListHtml.id = "documents-list";

  documentsHtml.appendChild(docsListHtml);

  const req = new XMLHttpRequest();

  req.addEventListener("load", function (evt) {
    const docs = JSON.parse(this.response);

    docs.forEach(doc => {
      addDocumentHtml(doc);
    });
  });

  req.open("GET", "/documents");
  req.send();
}

function addDocumentHtml(fileName) {
  const docsListHtml = document.getElementById("documents-list");

  const docElement = document.createElement("div");
  docElement.id = fileName;
  docElement.textContent = fileName;
  docElement.setAttribute("class", "document");

  docElement.onclick = () => {
    openDocument(fileName);
  }

  const deleteElement = document.createElement("span");
  deleteElement.textContent = "X";
  deleteElement.setAttribute("class", "delete-doc");

  deleteElement.onclick = evt => {
    evt.stopPropagation();
    evt.preventDefault();
    deleteDocument(fileName);
  }

  docElement.appendChild(deleteElement);
  docsListHtml.appendChild(docElement);
}

function openDocument(fileName) {
  const url = "/?fileName=" + fileName;
  open(url, "_blank");
}

هنا في الجزء العلوي ، نحصل على معلمات الاستعلام لمعرفة ما إذا كنا نفتح ملفًا أم لا. إذا كنا كذلك ، فسنطلق على editDocument وظيفة. لا تقلق ، سننشئ ذلك لاحقًا.

إذا لم نفتح ملفًا ، فنحن نريد عرض قائمة بالملفات المتاحة وعناصر التحكم لإنشاء المزيد. في listDocuments، نتأكد أولاً من إخفاء ملف placeholder وقم بمسح القائمة للتأكد من إنشائها من جديد. ثم نسمي /documents المسار الذي أنشأناه مسبقًا للحصول على جميع الملفات ، والتكرار من خلالها ، وإنشاء العناصر المقابلة. سنقوم بتعريف كل عنصر باسم الملف باعتباره المعرف. بهذه الطريقة يمكننا استعادتها بسهولة لاحقًا.

لاحظ أننا نسمي addDocumentHtml وظيفة ، والتي سنعيد استخدامها لاحقًا لإضافة ملفات جديدة.

لكل من هذه المستندات ، نسمي أيضًا openDocument، والذي حددناه في الأسفل ، وعلى رمز الصليب نسميه deleteDocument، والتي سنحددها بعد ذلك.

حذف المستندات

لحذف مستنداتنا ، سنطالب المستخدم إذا كان متأكدًا قبل المضي قدمًا والاتصال بـ /delete الطريق والذهاب نووي في هذا الملف. بدلاً من إضاعة مكالمة أخرى لواجهة برمجة التطبيقات الخاصة بنا ، نتحقق من الحالة التي تم إرجاعها 200 لحذف عناصر DOM مباشرة:

function deleteDocument(fileName) {
  const canContinue = confirm("Are you sure you want to delete " + fileName + "?");

  if (!canContinue) {
    return;
  }

  const req = new XMLHttpRequest();

  req.addEventListener("load", function (evt) {
    if (this.status === 200) {
      return removeDocumentHtml(fileName);
    }

    alert("Could not delete " + fileName);
  });

  req.open("DELETE", "/delete?fileName=" + fileName);
  req.send();
}

function removeDocumentHtml(fileName) {
  const el = document.getElementById(fileName);
  el && el.remove();
}

أنشئ المستندات

تذكر تلك الوظيفة التي كنا نستدعيها في onclick من ضوابط إنشاء الوثيقة؟ ها أنت ذا:

function createDocument(extension) {
  const name = prompt("What's the name of your new document?");
  const fileName = name + "." + extension;

  const req = new XMLHttpRequest();

  req.addEventListener("load", function (evt) {
    if (this.status === 200) {
      addDocumentHtml(fileName);
      return;
    }

    alert("Could not create " + fileName);
  });

  req.open("POST", "/create?fileName=" + fileName);
  req.send();
}

بسيط جدا. نطالب بالاسم ، اتصل بـ /create الطريق مع ذلك مثل fileName المعلمة ، وإذا كانت الحالة تعود كـ 200 نسميه addDocumentHtml لإضافة عناصر DOM مباشرة.

فتح المستندات في OnlyOffice Docs

الآن نحن بحاجة إلى تحديد editDocument وظيفة. أضف التعليمات البرمجية التالية إلى main.js:

async function editDocument(fileName) {
  document.getElementById("documents").style.display = "none";

  const extension = fileName.substring(fileName.lastIndexOf(".") + 1);
  const documentType = getDocumentType(extension);
  const documentKey = await generateKey(fileName);

  console.log(documentKey);

  new DocsAPI.DocEditor("placeholder", {
    document: {
      fileType: extension,
      key: documentKey,
      title: fileName,
      url: "http://192.168.0.7:3000/documents/" + fileName,
    },
    documentType,
    editorConfig: {
      callbackUrl: "http://192.168.0.7:3000/track?fileName=" + fileName,
    },
    height: "100%",
    width: "100%",
  });
}

function generateKey(fileName) {
  return new Promise(resolve => {
    const req = new XMLHttpRequest();

    req.addEventListener("load", function (evt) {
      const backups = JSON.parse(this.response);
      const backupName = backups[0];
      const key = backupName ? backupName.substring(0, backupName.indexOf(".")) : new Date().getTime();
      resolve(String(key));
    });

    req.open("GET", "/backups?fileName=" + fileName);
    req.send();
  });
}

function getDocumentType(extension) {
  const documentTypes = {
    text: ["doc", "docx", "docm", "dot", "dotx", "dotm", "odt", "fodt", "ott", "rtf", "txt", "html", "htm", "mht", "pdf", "djvu", "fb2", "epub", "xps"],
    spreadsheet: ["xls", "xlsx", "xlsm", "xlt", "xltx", "xltm", "ods", "fods", "ots", "csv"],
    presentation: ["pps", "ppsx", "ppsm", "ppt", "pptx", "pptm", "pot", "potx", "potm", "odp", "fodp", "otp"],
  }

  if (documentTypes.text.indexOf(extension) >= 0) {
    return "text";
  }
  if (documentTypes.spreadsheet.indexOf(extension) >= 0) {
    return "spreadsheet";
  }
  if (documentTypes.presentation.indexOf(extension) >= 0) {
    return "presentation";
  }
}

إذن ، أضفنا ثلاث وظائف. دعونا نركز على الأخيرين أولاً. (سنتحدث عنه editDocument في لحظة.)

ال generateKey سيساعدنا أيضًا من خلال إنشاء المفتاح. هذا معرّف مستند فريد يُستخدم للتعرف على المستندات بواسطة الخدمة. يمكن أن يكون الحد الأقصى للطول 20 ولا يحتوي على أحرف خاصة. وإليك الحيلة: يجب إعادة إنشائها في كل مرة يتم فيها حفظ المستند. هل ترى إلى أين يتجه هذا؟ بالضبط! سنستفيد من أسماء ملفات النسخ الاحتياطي الخاصة بنا لإنشاء مفاتيحنا.

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

ما الذي يجب تغييره في هذه الوظيفة إذا كنت تريد دعم المزيد من النسخ الاحتياطية؟ [Runs away]

ال getDocumentType سيعود إما text، spreadsheet أو presentation. OnlyOffice يحتاج هذا لمعرفة أي محرر لفتح.

ال editDocument هو ما نحن هنا من أجله. هذا ما كنت تنتظره طوال الوقت. هنا نقوم بإنشاء مثيل DocEditor كائن تمرير معرف الخاص بنا placeholder div وكائن به مجموعة من التكوينات.

تكوين DocEditor

ما أظهرته لك حتى الآن هو الحد الأدنى من الخيارات المطلوبة لإنشاء مثيل DocEditor. يجب عليك التحقق من معلمات متقدمة في المستندات لمعرفة كيف يمكنك الاستفادة من جميع الخيارات المختلفة. في غضون ذلك ، اسمحوا لي أن آخذك من خلال الأساسيات.

في الجزء العلوي ، لدينا ملف وثيقة الحقل الذي يأخذ كائنًا يحتوي على المعلومات المتعلقة بالمستند الذي نريد فتحه.

ثم لدينا ملف documentType، كما رأينا سابقًا ، يمكن أن يكون أيضًا text، spreadsheetأو presentation.

أسفل هذا هو حق محرر تكوين الذي يتيح لك تعيين أشياء مثل spellcheck، unit و zoom، ضمن أشياء أخرى. في هذه الحالة ، نستخدم فقط الامتداد callbackUrl، وهو عنوان URL لملف /track المسار الذي سيستخدمه خادم المستندات لحفظ الملف.

خاتمة

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

آمل أن تكون لديك معلومات كافية لمواصلة تحسين منتجك ، أو ربما مصدر إلهام لبدء مشروع جديد من الصفر. ليس هناك وقت أفضل من الوقت الحالي.

حسنًا ، سأراك في المرحلة التالية. في غضون ذلك ، استمر في الترميز وتذكر أن تستمتع أثناء تواجدك فيه!

المصدر

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

زر الذهاب إلى الأعلى