nodebb-plugin-link-preview 1.3.2 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/library.js +80 -71
  2. package/package.json +1 -1
package/library.js CHANGED
@@ -82,64 +82,29 @@ async function preview(url) {
82
82
  });
83
83
  }
84
84
 
85
- async function processAttachments({ content, pid, tid }) {
86
- // Retrieve attachments
87
- const hashes = await db.getSortedSetMembers(`post:${pid}:attachments`);
88
- if (!hashes.length) {
89
- return content;
90
- }
91
-
92
- const keys = hashes.map(hash => `attachment:${hash}`);
93
- const attachments = (await db.getObjects(keys)).filter(Boolean);
94
- const urls = attachments
95
- .filter(attachment => cache.has(`link-preview:${attachment.url}`))
96
- .map(attachment => attachment.url);
97
-
98
- const previews = urls.map(url => cache.get(`link-preview:${url}`));
99
- const html = await Promise.all(previews.map(async preview => await render(preview)));
100
-
101
- // Append all readily-available previews to content
102
- content = `${content}\n\n<div class="row">${html.map(html => `<div class="col-6">${html}</div>`).join('\n')}</div>`;
103
-
104
- // Kickstart preview
105
- const toFetch = attachments.filter(attachment => !cache.has(`link-preview:${attachment.url}`));
106
- if (toFetch.length) {
107
- Promise.all(toFetch.map(async attachment => preview(attachment.url))).then(async () => {
108
- // bust posts cache item
109
- if (await posts.exists(pid)) {
110
- postsCache.del(String(pid));
111
-
112
- // fire post edit event with mocked data
113
- if (await topics.exists(tid)) {
114
- const urls = attachments.map(attachment => attachment.url);
115
-
116
- const previews = urls.map(url => cache.get(`link-preview:${url}`));
117
- let html = await Promise.all(previews.map(async preview => await render(preview)));
118
- html = `${content}\n\n<div class="row">${html.map(html => `<div class="col-6">${html}</div>`).join('\n')}</div>`;
119
-
120
- websockets.in(`topic_${tid}`).emit('event:post_edited', {
121
- post: {
122
- tid,
123
- pid,
124
- changed: true,
125
- content: html,
126
- },
127
- topic: {},
128
- });
129
- }
130
- }
131
- });
132
- }
133
-
134
- return content;
135
- }
136
-
137
85
  async function process(content, opts) {
138
86
  const { embedHtml, embedImage, embedAudio, embedVideo } = await meta.settings.get('link-preview');
139
87
  if (![embedHtml, embedImage, embedAudio, embedVideo].some(prop => prop === 'on')) {
140
88
  return content;
141
89
  }
142
90
 
91
+ const requests = new Map();
92
+
93
+ // Retrieve attachments
94
+ if (opts.hasOwnProperty('pid') && await posts.exists(opts.pid)) {
95
+ const hashes = await db.getSortedSetMembers(`post:${opts.pid}:attachments`);
96
+ const keys = hashes.map(hash => `attachment:${hash}`);
97
+ const attachments = (await db.getObjects(keys)).filter(Boolean);
98
+ const urls = attachments
99
+ // .filter(attachment => cache.has(`link-preview:${attachment.url}`))
100
+ .map(attachment => attachment.url);
101
+
102
+ urls.forEach(url => requests.set(url, {
103
+ type: 'attachment',
104
+ }));
105
+ }
106
+
107
+ // Parse inline urls
143
108
  const $ = load(content, null, false);
144
109
  for (const anchor of $('a')) {
145
110
  const $anchor = $(anchor);
@@ -162,28 +127,72 @@ async function process(content, opts) {
162
127
  continue;
163
128
  }
164
129
 
130
+ requests.set(url, {
131
+ type: 'inline',
132
+ target: $anchor,
133
+ });
134
+ }
135
+
136
+ // Render cache hits immediately
137
+ let attachmentHtml = '';
138
+ const cold = new Set();
139
+ await Promise.all(Array.from(requests.keys()).map(async (url) => {
140
+ const options = requests.get(url);
165
141
  const cached = cache.get(`link-preview:${url}`);
166
142
  if (cached) {
167
143
  const html = await render(cached);
168
144
  if (html) {
169
- $anchor.replaceWith($(html));
145
+ switch (options.type) {
146
+ case 'inline': {
147
+ const $anchor = options.target;
148
+ $anchor.replaceWith($(html));
149
+ break;
150
+ }
151
+
152
+ case 'attachment': {
153
+ attachmentHtml += `<div class="col-6">${html}</div>`;
154
+ break;
155
+ }
156
+ }
170
157
  }
171
- continue;
158
+ } else {
159
+ cold.add(url);
172
160
  }
161
+ }));
162
+
163
+ // Start preview for cache misses, but continue for now so as to not block response
164
+ if (cold.size) {
165
+ const coldArr = Array.from(cold);
166
+ Promise.all(coldArr.map(preview)).then(async (previews) => {
167
+ await Promise.all(previews.map(async (preview, idx) => {
168
+ if (!preview) {
169
+ return;
170
+ }
173
171
 
174
- // Generate the preview, but continue for now so as to not block response
175
- preview(url).then(async (preview) => {
176
- if (!preview) {
177
- return;
178
- }
179
-
180
- const parsedUrl = new URL(url);
181
- preview.hostname = parsedUrl.hostname;
172
+ const url = coldArr[idx];
173
+ const options = requests.get(url);
174
+ const parsedUrl = new URL(url);
175
+ preview.hostname = parsedUrl.hostname;
176
+
177
+ const html = await render(preview);
178
+ if (html) {
179
+ switch (options.type) {
180
+ case 'inline': {
181
+ const $anchor = options.target;
182
+ $anchor.replaceWith($(html));
183
+ break;
184
+ }
185
+
186
+ case 'attachment': {
187
+ attachmentHtml += `<div class="col-6">${html}</div>`;
188
+ break;
189
+ }
190
+ }
191
+ }
192
+ }));
182
193
 
183
- const html = await render(preview);
184
- if (!html) {
185
- return;
186
- }
194
+ let content = $.html();
195
+ content += attachmentHtml ? `\n\n<div class="row">${attachmentHtml}</div>` : '';
187
196
 
188
197
  // bust posts cache item
189
198
  if (opts.hasOwnProperty('pid') && await posts.exists(opts.pid)) {
@@ -191,13 +200,12 @@ async function process(content, opts) {
191
200
 
192
201
  // fire post edit event with mocked data
193
202
  if (opts.hasOwnProperty('tid') && await topics.exists(opts.tid)) {
194
- $anchor.replaceWith($(html));
195
203
  websockets.in(`topic_${opts.tid}`).emit('event:post_edited', {
196
204
  post: {
197
205
  tid: opts.tid,
198
206
  pid: opts.pid,
199
207
  changed: true,
200
- content: $.html(),
208
+ content,
201
209
  },
202
210
  topic: {},
203
211
  });
@@ -206,7 +214,9 @@ async function process(content, opts) {
206
214
  });
207
215
  }
208
216
 
209
- return $.html();
217
+ content = $.html();
218
+ content += attachmentHtml ? `\n\n<div class="row">${attachmentHtml}</div>` : '';
219
+ return content;
210
220
  }
211
221
 
212
222
  async function render(preview) {
@@ -285,8 +295,7 @@ plugin.onParse = async (payload) => {
285
295
  if (typeof payload === 'string') { // raw
286
296
  payload = await process(payload, {});
287
297
  } else if (payload && payload.postData && payload.postData.content) { // post
288
- let { content, pid, tid } = payload.postData;
289
- content = await processAttachments({ content, pid, tid });
298
+ const { content, pid, tid } = payload.postData;
290
299
  payload.postData.content = await process(content, { pid, tid });
291
300
  }
292
301
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-link-preview",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "A starter kit for quickly creating NodeBB plugins",
5
5
  "main": "library.js",
6
6
  "repository": {