浏览代码

Oasis release 0.4.1

psy 1 周之前
父节点
当前提交
e8fb072400

+ 28 - 9
src/models/activity_model.js

@@ -8,6 +8,14 @@ module.exports = ({ cooler }) => {
     return ssb;
   };
 
+  const hasBlob = async (ssbClient, url) => {
+    return new Promise((resolve) => {
+      ssbClient.blobs.has(url, (err, has) => {
+        resolve(!err && has);
+      });
+    });
+  };
+
   return {
     async listFeed(filter = 'all') {
       const ssbClient = await openSsb();
@@ -40,20 +48,31 @@ module.exports = ({ cooler }) => {
       for (const oldId of replaces.keys()) latest.delete(oldId);
       for (const t of tombstoned) latest.delete(t);
 
-      const actions = Array.from(latest.values()).filter(a =>
-        a.type !== 'tombstone' &&
-        !tombstoned.has(a.id) &&
-        !(a.content?.root && tombstoned.has(a.content.root)) &&
-        !(a.type === 'vote' && tombstoned.has(a.content.vote.link))
+      const actions = await Promise.all(
+        Array.from(latest.values()).map(async (a) => {
+          if (a.type === 'document') {
+            const url = a.content.url;
+            const validBlob = await hasBlob(ssbClient, url);
+            if (!validBlob) return null;
+          }
+          if (
+            a.type !== 'tombstone' &&
+            !tombstoned.has(a.id) &&
+            !(a.content?.root && tombstoned.has(a.content.root)) &&
+            !(a.type === 'vote' && tombstoned.has(a.content.vote.link))
+          ) {
+            return a;
+          }
+          return null;
+        })
       );
-
+      const validActions = actions.filter(Boolean);
       if (filter === 'mine')
-        return actions
+        return validActions
           .filter(a => a.author === userId)
           .sort((a, b) => b.ts - a.ts);
 
-      return actions.sort((a, b) => b.ts - a.ts);
+      return validActions.sort((a, b) => b.ts - a.ts);
     }
   };
 };
-

+ 14 - 4
src/models/documents_model.js

@@ -87,13 +87,11 @@ module.exports = ({ cooler }) => {
           pull.collect((err, msgs) => err ? rej(err) : res(msgs))
         )
       })
-
       const tombstoned = new Set(
         messages
           .filter(m => m.value.content?.type === 'tombstone')
           .map(m => m.value.content.target)
       )
-
       const replaces = new Map()
       const latest = new Map()
 
@@ -120,14 +118,26 @@ module.exports = ({ cooler }) => {
       for (const oldId of replaces.keys()) {
         latest.delete(oldId)
       }
-
       let documents = Array.from(latest.values())
-
       if (filter === 'mine') {
         documents = documents.filter(d => d.author === userId)
       } else {
         documents = documents.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
       }
+      const hasBlob = (blobId) => {
+        return new Promise((resolve) => {
+          ssbClient.blobs.has(blobId, (err, has) => {
+            resolve(!err && has);
+          });
+        });
+      };
+     documents = await Promise.all(
+        documents.map(async (doc) => {
+          const ok = await hasBlob(doc.url);
+          return ok ? doc : null;
+        })
+      );
+      documents = documents.filter(Boolean);
 
       return documents
     },

+ 1 - 1
src/server/package-lock.json

@@ -1,6 +1,6 @@
 {
   "name": "@krakenslab/oasis",
-  "version": "0.4.0",
+  "version": "0.4.1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {

+ 1 - 1
src/server/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@krakenslab/oasis",
-  "version": "0.4.0",
+  "version": "0.4.1",
   "description": "Oasis Social Networking Project Utopia",
   "repository": {
     "type": "git",

+ 9 - 3
src/views/activity_view.js

@@ -29,6 +29,8 @@ function renderActionCards(actions) {
   if (!validActions.length) {
     return div({ class: "no-actions" }, p(i18n.noActions)); 
   }
+  
+  const seenDocumentTitles = new Set();
 
   return validActions.map(action => {
     const date = action.ts ? new Date(action.ts).toLocaleString() : "";
@@ -186,7 +188,7 @@ function renderActionCards(actions) {
       );
     }
 
-    if (content.type === 'video') {
+    if (type === 'video') {
       const { url, mimeType, title } = content;
       cardBody.push(
         div({ class: 'card-section video' },     
@@ -207,8 +209,12 @@ function renderActionCards(actions) {
       );
     }
 
-    if (content.type === 'document') {
+    if (type === 'document') {
       const { url, title, key } = content;
+      if (title && seenDocumentTitles.has(title.trim())) {
+        return null;
+     }
+      if (title) seenDocumentTitles.add(title.trim());
       cardBody.push(
         div({ class: 'card-section document' },      
           title?.trim() ? h2({ class: 'document-title' }, title) : "",
@@ -555,7 +561,7 @@ exports.activityView = (actions, filter, userId) => {
             div({
               style: 'display: flex; flex-direction: column; gap: 8px;'
             },
-              activityTypes.slice(15, 20).map(({ type, label }) =>
+              activityTypes.slice(15, 21).map(({ type, label }) =>
                 form({ method: 'GET', action: '/activity' },
                   input({ type: 'hidden', name: 'filter', value: type }),
                   button({ type: 'submit', class: filter === type ? 'filter-btn active' : 'filter-btn' }, label)

+ 20 - 10
src/views/document_view.js

@@ -17,8 +17,10 @@ const getFilteredDocuments = (filter, documents, userId) => {
       return sumB - sumA;
     }) :
     documents;
-
-  return filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
+  if (filter !== 'top') {
+    filtered = filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
+  }
+  return filtered;
 };
 
 const renderDocumentActions = (filter, doc) => {
@@ -33,12 +35,21 @@ const renderDocumentActions = (filter, doc) => {
 };
 
 const renderDocumentList = (filteredDocs, filter) => {
-  return filteredDocs.length > 0
-    ? filteredDocs.map(doc =>
+  const seen = new Set();
+  const unique = [];
+  for (const doc of filteredDocs) {
+    if (seen.has(doc.title)) continue;
+    seen.add(doc.title);
+    unique.push(doc);
+  }
+
+  return unique.length > 0
+    ? unique.map(doc =>
         div({ class: "tags-header" },
           renderDocumentActions(filter, doc),
           form({ method: "GET", action: `/documents/${encodeURIComponent(doc.key)}` },
-            button({ type: "submit", class: "filter-btn" }, i18n.viewDetails)),
+            button({ type: "submit", class: "filter-btn" }, i18n.viewDetails)
+          ),
           doc.title?.trim() ? h2(doc.title) : null,
           div({
             id: `pdf-container-${doc.key}`,
@@ -51,14 +62,13 @@ const renderDocumentList = (filteredDocs, filter) => {
                 a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link" }, `#${tag}`)
               ))
             : null,
-          br,
+          br(),
           p({ class: 'card-footer' },
-          span({ class: 'date-link' }, `${moment(doc.createdAt).format('YYYY/MM/DD HH:mm:ss')} ${i18n.performed} `),
-          a({ href: `/author/${encodeURIComponent(doc.author)}`, class: 'user-link' }, `${doc.author}`)
+            span({ class: 'date-link' }, `${moment(doc.createdAt).format('YYYY/MM/DD HH:mm:ss')} ${i18n.performed} `),
+            a({ href: `/author/${encodeURIComponent(doc.author)}`, class: 'user-link' }, doc.author)
           ),
           div({ class: "voting-buttons" },
-            ['interesting','necessary','funny','disgusting','sensible',
-             'propaganda','adultOnly','boring','confusing','inspiring','spam']
+            ['interesting','necessary','funny','disgusting','sensible','propaganda','adultOnly','boring','confusing','inspiring','spam']
               .map(category =>
                 form({ method: "POST", action: `/documents/opinions/${encodeURIComponent(doc.key)}/${category}` },
                   button({ class: "vote-btn" },