nodebb-plugin-recent-cards-2 1.0.0

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.
package/.gitattributes ADDED
@@ -0,0 +1,22 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
3
+
4
+ # Custom for Visual Studio
5
+ *.cs diff=csharp
6
+ *.sln merge=union
7
+ *.csproj merge=union
8
+ *.vbproj merge=union
9
+ *.fsproj merge=union
10
+ *.dbproj merge=union
11
+
12
+ # Standard to msysgit
13
+ *.doc diff=astextplain
14
+ *.DOC diff=astextplain
15
+ *.docx diff=astextplain
16
+ *.DOCX diff=astextplain
17
+ *.dot diff=astextplain
18
+ *.DOT diff=astextplain
19
+ *.pdf diff=astextplain
20
+ *.PDF diff=astextplain
21
+ *.rtf diff=astextplain
22
+ *.RTF diff=astextplain
package/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ Copyright (c) 2013-2014, psychobunny <psycho.bunny@hotmail.com>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Recent Cards plugin for NodeBB's Persona Theme
2
+
3
+ This is a plugin that creates a new widget that can be placed on various widget areas. It's inspired by the previous default theme, Lavender, which used Modern UI styling for the category layout.
4
+
5
+
6
+ ## Installation
7
+
8
+ Install via one-click activation in the Admin Control Panel or run the following command:
9
+
10
+ npm i nodebb-plugin-recent-cards
11
+
12
+ Then head over to Admin -> Extend -> Widgets and place the widget. Additional settings can be found at Admin -> Plugins -> Recent Cards and under the individual widget settings.
13
+
14
+ ## Screenshot
15
+
16
+ ![](https://i.imgur.com/r3NKmY1.jpg)
17
+
18
+ # Standalone installation for external websites (Advanced)
19
+
20
+ Use this plugin on any external non-nodebb site (ex. Wordpress, etc) to show recent topics from your NodeBB install.
21
+
22
+ ### Header Scripts + Styles
23
+
24
+ Place these in the `header` section of your external site, and replace all instances of `{forumURL}` to your forum's URL:
25
+
26
+ ```
27
+ <script src="{forumURL}/plugins/nodebb-plugin-recent-cards/static/bxslider/jquery.bxslider.min.js"></script>
28
+ <script>
29
+ window.path_to_nodebb = '{forumURL}';
30
+ </script>
31
+ <script src="{forumURL}/plugins/nodebb-plugin-recent-cards/static/lib/external.js"></script>
32
+ <link rel="stylesheet" type="text/css" href="{forumURL}/plugins/nodebb-plugin-recent-cards/render/style.css" />
33
+ <link rel="stylesheet" type="text/css" href="{forumURL}/plugins/nodebb-plugin-recent-cards/static/bxslider/jquery.bxslider.css" />
34
+ ```
35
+
36
+ If your external site doesn't have jQuery included, you will have include it above the previous lines. Get the latest jQuery at https://code.jquery.com/
37
+
38
+ You should also (optionally) require the jQuery Timeago library in order to display human-readable timestamps:
39
+
40
+ ```
41
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.6.1/jquery.timeago.min.js"></script>
42
+ ```
43
+
44
+ If your external site doesn't have Bootstrap included, you will have to include this line as well in your `header`, which is the bare minimum (grid + responsive utilities) required for this plugin:
45
+
46
+ ```
47
+ <link rel="stylesheet" type="text/css" href="{forumURL}/plugins/nodebb-plugin-recent-cards/static/external/bootstrap-grid.css" />
48
+ ```
49
+
50
+ Similarly, if your external site does not use FontAwesome, then you will have to include this line as well in order to display category icons:
51
+
52
+ ```
53
+ <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
54
+ ```
55
+
56
+
57
+ Finally, if you need to include the default Persona font to match your external site's recent cards with your forum's, then include this:
58
+
59
+ ```
60
+ <link rel="prefetch stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" />
61
+ ```
62
+
63
+
64
+ ### Body Content
65
+
66
+ Place the following code wherever you'd like recent cards to be displayed:
67
+
68
+ ```
69
+ <div id="nodebb-plugin-recent-cards"></div>
70
+ ```
71
+
72
+ ### Configure ACAO in NodeBB
73
+
74
+ Under Settings -> Advanced in the NodeBB control panel, add the external site's URL to `Access-Control-Allow-Origin`
75
+
76
+ ### Stuck?
77
+
78
+ No problem! Visit https://yourforum.com/admin/plugins/nodebb-plugin-recent-cards/tests/external, which will render the standalone version of the plugin tailored for your website. Keep in mind that this includes all extra scripts and styling that you may not necessarily need if you already have Bootstrap, jQuery, etc. on your external site.
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ import serverConfig from 'eslint-config-nodebb';
4
+ import publicConfig from 'eslint-config-nodebb/public';
5
+
6
+ export default [
7
+ ...publicConfig,
8
+ ...serverConfig,
9
+ {
10
+ ignores: ['static/slick/**'],
11
+ },
12
+ ];
package/library.js ADDED
@@ -0,0 +1,340 @@
1
+ /* eslint-disable no-await-in-loop */
2
+
3
+ 'use strict';
4
+
5
+ const nconf = require.main.require('nconf');
6
+ const _ = require.main.require('lodash');
7
+ const validator = require.main.require('validator');
8
+ const db = require.main.require('./src/database');
9
+ const topics = require.main.require('./src/topics');
10
+ const settings = require.main.require('./src/settings');
11
+ const groups = require.main.require('./src/groups');
12
+ const user = require.main.require('./src/user');
13
+
14
+ const defaultSettings = {
15
+ enableCarousel: 1,
16
+ enableCarouselPagination: 0,
17
+ minSlides: 1,
18
+ maxSlides: 4,
19
+ };
20
+
21
+ const plugin = module.exports;
22
+ let app;
23
+
24
+ plugin.init = async function (params) {
25
+ app = params.app;
26
+ const { router } = params;
27
+ const routeHelpers = require.main.require('./src/routes/helpers');
28
+ routeHelpers.setupAdminPageRoute(router, '/admin/plugins/recentcards', renderAdmin);
29
+
30
+ router.get('/plugins/nodebb-plugin-recent-cards/render', renderExternal);
31
+ router.get('/plugins/nodebb-plugin-recent-cards/render/style.css', renderExternalStyle);
32
+ router.get('/plugins/nodebb-plugin-recent-cards/filter', renderFiltered);
33
+ router.get('/admin/plugins/nodebb-plugin-recent-cards/tests/external', testRenderExternal);
34
+
35
+ plugin.settings = new settings('recentcards', '1.0.0', defaultSettings);
36
+ };
37
+
38
+ plugin.addAdminNavigation = async function (header) {
39
+ header.plugins.push({
40
+ route: '/plugins/recentcards',
41
+ icon: 'fa-tint',
42
+ name: 'Recent Cards',
43
+ });
44
+ return header;
45
+ };
46
+
47
+ plugin.defineWidgets = async function (widgets) {
48
+ const groupNames = await db.getSortedSetRevRange('groups:visible:createtime', 0, -1);
49
+ let groupsData = await groups.getGroupsData(groupNames);
50
+ groupsData = groupsData.filter(Boolean);
51
+ groupsData.forEach((group) => {
52
+ group.name = validator.escape(String(group.name));
53
+ });
54
+
55
+ const html = await app.renderAsync('admin/plugins/nodebb-plugin-recent-cards/widget', {
56
+ groups: groupsData,
57
+ });
58
+
59
+ widgets.push({
60
+ widget: 'recentCards',
61
+ name: 'Recent Cards',
62
+ description: 'Recent topics carousel',
63
+ content: html,
64
+ });
65
+ return widgets;
66
+ };
67
+
68
+ plugin.getConfig = async function (config) {
69
+ config.recentCards = {
70
+ title: plugin.settings.get('title'),
71
+ opacity: plugin.settings.get('opacity'),
72
+ textShadow: plugin.settings.get('shadow'),
73
+ enableCarousel: plugin.settings.get('enableCarousel'),
74
+ enableCarouselPagination: plugin.settings.get('enableCarouselPagination'),
75
+ minSlides: plugin.settings.get('minSlides'),
76
+ maxSlides: plugin.settings.get('maxSlides'),
77
+ };
78
+ return config;
79
+ };
80
+
81
+ plugin.renderWidget = async function (widget) {
82
+ if (!isVisibleInCategory(widget)) {
83
+ return null;
84
+ }
85
+ const topics = await getTopics(widget);
86
+
87
+ const categoryMap = {};
88
+ topics.forEach((t) => {
89
+ if (t && t.category && !categoryMap[t.category.cid]) {
90
+ categoryMap[t.category.cid] = {
91
+ cid: t.category.cid,
92
+ name: t.category.name,
93
+ icon: t.category.icon,
94
+ bgColor: t.category.bgColor,
95
+ color: t.category.color,
96
+ };
97
+ }
98
+ });
99
+ const categories = Object.values(categoryMap);
100
+
101
+ const widgetConfig = {
102
+ sort: widget.data.sort || 'recent',
103
+ teaserPost: widget.data.teaserPost || 'first',
104
+ teaserParseType: widget.data.teaserParseType || 'default',
105
+ thumbnailStyle: widget.data.thumbnailStyle || 'background',
106
+ };
107
+
108
+ widget.html = await app.renderAsync('partials/nodebb-plugin-recent-cards/header', {
109
+ topics: topics,
110
+ config: widget.templateData.config,
111
+ title: widget.data.title || '',
112
+ carouselMode: plugin.settings.get('enableCarousel'),
113
+ categories: JSON.stringify(categories).replace(/</g, '\\u003c'),
114
+ widgetConfig: JSON.stringify(widgetConfig).replace(/</g, '\\u003c'),
115
+ });
116
+ return widget;
117
+ };
118
+
119
+ function getIdsArray(data, field) {
120
+ const ids = String(data[field] || '');
121
+ return ids.split(',').map(c => c.trim()).filter(Boolean);
122
+ }
123
+
124
+ function isVisibleInCategory(widget) {
125
+ const cids = getIdsArray(widget.data, 'cid');
126
+ return !(
127
+ cids.length &&
128
+ (widget.templateData.template.category || widget.templateData.template.topic) &&
129
+ !cids.includes(String(widget.templateData.cid))
130
+ );
131
+ }
132
+
133
+ async function getTopics(widget) {
134
+ const teaserPost = widget.data.teaserPost || 'first';
135
+ const teaserParseType = widget.data.teaserParseType || 'default';
136
+ const getTopicsOptions = {
137
+ uid: widget.uid,
138
+ teaserPost,
139
+ teaserParseType,
140
+ thumbsOnly: true,
141
+ };
142
+ async function getTopicsFromSet(set) {
143
+ let start = 0;
144
+ const topicsData = [];
145
+
146
+ do {
147
+ let tids = await db.getSortedSetRevRangeByScore(set, start, 20, Date.now(), '-inf');
148
+ if (!tids.length) {
149
+ break;
150
+ }
151
+
152
+ tids = await topics.filterNotIgnoredTids(tids, widget.uid);
153
+ let nextTopics = await topics.getTopics(tids, getTopicsOptions);
154
+
155
+ nextTopics = await user.blocks.filter(widget.uid, nextTopics);
156
+ topicsData.push(...nextTopics);
157
+ start += 20;
158
+ } while (topicsData.length < 20);
159
+ return { topics: topicsData.slice(0, 20) };
160
+ }
161
+
162
+ let topicsData = {
163
+ topics: [],
164
+ };
165
+ let filterCids = getIdsArray(widget.data, 'topicsFromCid');
166
+ if (!filterCids.length && widget.templateData.cid) {
167
+ filterCids = [String(widget.templateData.cid)];
168
+ }
169
+
170
+ widget.data.sort = widget.data.sort || 'recent';
171
+ let fromGroups = widget.data.fromGroups || [];
172
+ if (fromGroups && !Array.isArray(fromGroups)) {
173
+ fromGroups = [fromGroups];
174
+ }
175
+ // hard coded to show these topic tids only
176
+ const topicsTids = getIdsArray(widget.data, 'topicsTids');
177
+ if (topicsTids.length) {
178
+ topicsData.topics = await topics.getTopics(topicsTids, getTopicsOptions);
179
+ } else if (fromGroups.length) {
180
+ const uids = _.uniq(_.flatten(await groups.getMembersOfGroups(fromGroups)));
181
+ const sets = uids.map((uid) => {
182
+ if (filterCids.length) {
183
+ return filterCids.map(cid => `cid:${cid}:uid:${uid}:tids`);
184
+ }
185
+ return `uid:${uid}:topics`;
186
+ });
187
+ topicsData = await getTopicsFromSet(sets.flat());
188
+ topicsData.topics.sort((t1, t2) => {
189
+ if (widget.data.sort === 'recent') {
190
+ return t2.lastposttime - t1.lastposttime;
191
+ } else if (widget.data.sort === 'votes') {
192
+ return t2.votes - t1.votes;
193
+ } else if (widget.data.sort === 'posts') {
194
+ return t2.postcount - t1.postcount;
195
+ }
196
+ return 0;
197
+ });
198
+ } else if (filterCids.length) {
199
+ let searchSuffix = '';
200
+ if (widget.data.sort === 'recent') {
201
+ searchSuffix += ':lastposttime';
202
+ } else if (widget.data.sort === 'votes' || widget.data.sort === 'posts' || widget.data.sort === 'create') {
203
+ searchSuffix += `:${widget.data.sort}`;
204
+ }
205
+ topicsData = await getTopicsFromSet(
206
+ filterCids.map(cid => `cid:${cid}:tids${searchSuffix}`)
207
+ );
208
+ } else {
209
+ const map = {
210
+ votes: 'topics:votes',
211
+ posts: 'topics:posts',
212
+ recent: 'topics:recent',
213
+ create: 'topics:tid',
214
+ };
215
+ topicsData = await getTopicsFromSet(map[widget.data.sort]);
216
+ }
217
+
218
+ let i = 0;
219
+ const cids = [];
220
+ let finalTopics = [];
221
+
222
+ if (!plugin.settings.get('enableCarousel')) {
223
+ while (finalTopics.length < 4 && i < topicsData.topics.length) {
224
+ const cid = String(topicsData.topics[i].cid);
225
+
226
+ if (filterCids.length || !cids.includes(cid) || topicsData.topics.length <= 4) {
227
+ cids.push(cid);
228
+ finalTopics.push(topicsData.topics[i]);
229
+ }
230
+
231
+ i += 1;
232
+ }
233
+ } else {
234
+ finalTopics = topicsData.topics;
235
+ }
236
+
237
+ const thumbStyle = widget.data.thumbnailStyle || 'background';
238
+ finalTopics.forEach((t) => {
239
+ if (t) {
240
+ const showThumbs = t.thumbs?.length && String(t?.teaser.pid) === String(t.mainPid);
241
+ t.showThumbnailInBackground = showThumbs && thumbStyle === 'background';
242
+ t.showThumbnailInline = showThumbs && thumbStyle === 'inline';
243
+ }
244
+ });
245
+
246
+ return finalTopics;
247
+ }
248
+
249
+ async function renderExternal(req, res, next) {
250
+ try {
251
+ const topicsData = await getTopics({
252
+ uid: req.uid,
253
+ data: {
254
+ teaserPost: 'first',
255
+ },
256
+ templateData: {},
257
+ });
258
+
259
+ const categoryMap = {};
260
+ topicsData.forEach((t) => {
261
+ if (t && t.category && !categoryMap[t.category.cid]) {
262
+ categoryMap[t.category.cid] = {
263
+ cid: t.category.cid,
264
+ name: t.category.name,
265
+ icon: t.category.icon,
266
+ bgColor: t.category.bgColor,
267
+ color: t.category.color,
268
+ };
269
+ }
270
+ });
271
+
272
+ res.render('partials/nodebb-plugin-recent-cards/header', {
273
+ topics: topicsData,
274
+ config: {
275
+ relative_path: nconf.get('url'),
276
+ },
277
+ categories: JSON.stringify(Object.values(categoryMap)).replace(/</g, '\\u003c'),
278
+ widgetConfig: '{}',
279
+ });
280
+ } catch (err) {
281
+ next(err);
282
+ }
283
+ }
284
+
285
+ async function renderFiltered(req, res, next) {
286
+ try {
287
+ const cids = String(req.query.cids || '');
288
+ const sort = String(req.query.sort || 'recent');
289
+ const teaserPost = String(req.query.teaserPost || 'first');
290
+ const teaserParseType = String(req.query.teaserParseType || 'default');
291
+ const thumbnailStyle = String(req.query.thumbnailStyle || 'background');
292
+
293
+ const allowedSorts = ['recent', 'create', 'votes', 'posts'];
294
+ const allowedTeasers = ['first', 'last-post'];
295
+ const allowedParseTypes = ['default', 'plaintext'];
296
+ const allowedThumbs = ['background', 'inline'];
297
+
298
+ const filteredTopics = await getTopics({
299
+ uid: req.uid,
300
+ data: {
301
+ topicsFromCid: cids,
302
+ sort: allowedSorts.includes(sort) ? sort : 'recent',
303
+ teaserPost: allowedTeasers.includes(teaserPost) ? teaserPost : 'first',
304
+ teaserParseType: allowedParseTypes.includes(teaserParseType) ? teaserParseType : 'default',
305
+ thumbnailStyle: allowedThumbs.includes(thumbnailStyle) ? thumbnailStyle : 'background',
306
+ },
307
+ templateData: {},
308
+ });
309
+
310
+ const html = await app.renderAsync('partials/nodebb-plugin-recent-cards/header', {
311
+ topics: filteredTopics,
312
+ config: { relative_path: nconf.get('relative_path') },
313
+ carouselMode: plugin.settings.get('enableCarousel'),
314
+ categories: '[]',
315
+ widgetConfig: '{}',
316
+ });
317
+
318
+ res.json({ html: html });
319
+ } catch (err) {
320
+ next(err);
321
+ }
322
+ }
323
+
324
+ function renderExternalStyle(req, res) {
325
+ res.render('partials/nodebb-plugin-recent-cards/external/style', {
326
+ forumURL: nconf.get('url'),
327
+ });
328
+ }
329
+
330
+ function testRenderExternal(req, res) {
331
+ res.render('admin/plugins/nodebb-plugin-recent-cards/tests/external', {
332
+ forumURL: nconf.get('url'),
333
+ });
334
+ }
335
+
336
+ async function renderAdmin(req, res) {
337
+ res.render('admin/plugins/recentcards', {
338
+ title: 'Recent Cards',
339
+ });
340
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "nodebb-plugin-recent-cards-2",
3
+ "version": "1.0.0",
4
+ "description": "Add lavender-style cards of recent topics to Persona's category homepage",
5
+ "main": "library.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/psychobunny/nodebb-plugin-recent-cards"
9
+ },
10
+ "scripts": {
11
+ "lint": "eslint ."
12
+ },
13
+ "keywords": [
14
+ "nodebb",
15
+ "plugin",
16
+ "recent",
17
+ "cards",
18
+ "persona"
19
+ ],
20
+ "author": {
21
+ "name": "psychobunny",
22
+ "email": "psycho.bunny@hotmail.com"
23
+ },
24
+ "license": "BSD-2-Clause",
25
+ "bugs": {
26
+ "url": "https://github.com/psychobunny/nodebb-plugin-recent-cards/issues"
27
+ },
28
+ "readmeFilename": "README.md",
29
+ "devDependencies": {
30
+ "eslint": "^9.x",
31
+ "eslint-config-nodebb": "1.1.11",
32
+ "eslint-plugin-import": "2.32.0"
33
+ },
34
+ "nbbpm": {
35
+ "compatibility": "^4.0.0"
36
+ }
37
+ }
package/plugin.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "id": "nodebb-plugin-recent-cards",
3
+ "name": "Recent Cards plugin for NodeBB's Persona Theme",
4
+ "description": "Add lavender-style cards of recent topics to Persona's category homepage",
5
+ "url": "https://github.com/NodeBB/nodebb-plugin-recent-cards",
6
+ "library": "./library.js",
7
+ "hooks": [
8
+ {
9
+ "hook": "static:app.load", "method": "init"
10
+ },
11
+ {
12
+ "hook": "filter:config.get", "method": "getConfig"
13
+ },
14
+ {
15
+ "hook": "filter:admin.header.build", "method": "addAdminNavigation"
16
+ },
17
+ {
18
+ "hook": "filter:widgets.getWidgets", "method": "defineWidgets"
19
+ },
20
+ {
21
+ "hook": "filter:widget.render:recentCards", "method": "renderWidget"
22
+ }
23
+ ],
24
+ "staticDirs": {
25
+ "static": "./static"
26
+ },
27
+ "css": [
28
+ "static/slick/slick.css",
29
+ "static/slick/slick-theme.css"
30
+ ],
31
+ "scss": [
32
+ "static/style.scss"
33
+ ],
34
+ "scripts": [
35
+ "static/slick/slick.min.js",
36
+ "static/lib/main.js"
37
+ ],
38
+ "modules": {
39
+ "../admin/plugins/recentcards.js": "static/lib/admin.js"
40
+ },
41
+ "templates": "static/templates"
42
+ }
@@ -0,0 +1,14 @@
1
+ /*!
2
+ * Bootstrap v3.3.7 (http://getbootstrap.com)
3
+ * Copyright 2011-2018 Twitter, Inc.
4
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5
+ */
6
+
7
+ /*!
8
+ * Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.3/customize/?id=cd87b217e33874f77fb457b6338264bb)
9
+ * Config saved to config.json and https://gist.github.com/cd87b217e33874f77fb457b6338264bb
10
+ *//*!
11
+ * Bootstrap v3.3.7 (http://getbootstrap.com)
12
+ * Copyright 2011-2016 Twitter, Inc.
13
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
14
+ *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:hover,a:focus{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}.clearfix:before,.clearfix:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after{content:" ";display:table}.clearfix:after,.container:after,.container-fluid:after,.row:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ define('admin/plugins/recentcards', ['settings'], function (settings) {
4
+ const admin = {};
5
+ admin.init = function () {
6
+ settings.sync('recentcards', $('#recentcards'));
7
+
8
+ $('#save').click(function () {
9
+ settings.persist('recentcards', $('#recentcards'));
10
+ });
11
+ };
12
+ return admin;
13
+ });
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ $(document).ready(function () {
4
+ $.get(window.path_to_nodebb + '/plugins/nodebb-plugin-recent-cards/render', {}, function (html) {
5
+ html = $(html);
6
+ if (html.length !== 5) {
7
+ return;
8
+ }
9
+
10
+ $('#nodebb-plugin-recent-cards').html($(html[2]));
11
+
12
+ var ajaxifyData = $(html[4]);
13
+
14
+ if (ajaxifyData.length) {
15
+ ajaxifyData = JSON.parse(ajaxifyData.text());
16
+
17
+ if (ajaxifyData.recentCards.enableCarousel) {
18
+ $('#nodebb-plugin-recent-cards .recent-cards').bxSlider({
19
+ slideWidth: 292,
20
+ minSlides: 1,
21
+ maxSlides: 4,
22
+ pager: ajaxifyData.recentCards.enableCarouselPagination ? true : false,
23
+ });
24
+ } else {
25
+ $('#nodebb-plugin-recent-cards .recent-cards').removeClass('carousel-mode');
26
+ }
27
+
28
+ if ($.timeago) {
29
+ $('#nodebb-plugin-recent-cards .timeago').not('[datetime]').timeago();
30
+ }
31
+ }
32
+ });
33
+ });