nodebb-plugin-fab-cards 0.1.0 → 0.1.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 NodeBB Inc. <sales@nodebb.org>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md CHANGED
@@ -1,52 +1,17 @@
1
- # nodebb-plugin-fab-cards
1
+ # Quickstart Plugin for NodeBB
2
2
 
3
- A NodeBB plugin that automatically detects mentions of Flesh and Blood card names in posts, converts them into links to the official FAB card database, and shows a card image preview on hover/touch.
3
+ A starter kit for quickly creating NodeBB plugins. Comes with a pre-setup SCSS file, server side JS script with an `static:app.load` hook, and a client-side script. Most plugins need at least one of the above, so this ought to save you some time. For a full list of hooks have a look at our [wiki page](https://github.com/NodeBB/NodeBB/wiki/Hooks), and for more information about creating plugins please visit our [documentation portal](https://docs.nodebb.org/).
4
4
 
5
- ## Features
5
+ Fork this or copy it, and using your favourite text editor find and replace all instances of `nodebb-plugin-fab-cards` with `nodebb-plugin-your-plugins-name`. Change the author's name in the LICENSE and package.json files.
6
6
 
7
- - Detects known card names in parsed post content
8
- - Converts matches to links such as `https://cards.fabtcg.com/card/command-and-conquer-1/`
9
- - Opens links in a new tab
10
- - Shows preview image on hover (desktop) and first touch (mobile)
11
- - Uses a static generated card index (`data/cards-index.json`) for fast runtime lookups
7
+ ## Hello World
12
8
 
13
- ## Card data generation
9
+ Really simple, just edit `public/lib/main.js` and paste in `console.log('hello world');`, and that's it!
14
10
 
15
- The plugin uses:
11
+ ## Installation
16
12
 
17
- - API endpoint: `https://cards.fabtcg.com/api/search/v1/cards/?`
18
- - Build script: `scripts/build-card-index.js`
13
+ npm install nodebb-plugin-fab-cards
19
14
 
20
- Generate/update the card index:
15
+ ## Screenshots
21
16
 
22
- ```bash
23
- npm install
24
- npm run build:cards
25
- ```
26
-
27
- ## Installation in NodeBB
28
-
29
- In your plugin folder:
30
-
31
- ```bash
32
- npm link
33
- ```
34
-
35
- In your NodeBB folder:
36
-
37
- ```bash
38
- npm link nodebb-plugin-fab-cards
39
- ./nodebb build
40
- ```
41
-
42
- Then activate `FAB Cards Auto-Link` in ACP (`/admin/extend/plugins`).
43
-
44
- ## Local development NodeBB setup
45
-
46
- A local setup guide is included in [dev/README.md](dev/README.md).
47
-
48
- ## Notes
49
-
50
- - The parser only transforms posts for `type=default` rendering.
51
- - Existing links/code blocks are ignored to avoid corrupting content.
52
- - Re-run `npm run build:cards` periodically to refresh card data.
17
+ Don't forget to add screenshots!
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ extends: ['@commitlint/config-angular'],
5
+ rules: {
6
+ 'header-max-length': [1, 'always', 72],
7
+ 'type-enum': [
8
+ 2,
9
+ 'always',
10
+ [
11
+ 'breaking',
12
+ 'build',
13
+ 'chore',
14
+ 'ci',
15
+ 'docs',
16
+ 'feat',
17
+ 'fix',
18
+ 'perf',
19
+ 'refactor',
20
+ 'revert',
21
+ 'style',
22
+ 'test',
23
+ ],
24
+ ],
25
+ },
26
+ };
@@ -0,0 +1,10 @@
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
+
@@ -0,0 +1,3 @@
1
+ {
2
+ "info": "Dies ist eine Übersetzungszeichenkette, sie wird in die Sprache des Benutzers übersetzt. Siehe den Ordner 'languages' im Stammverzeichnis dieses Plugins."
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "info": "This is a translation string, it gets translated to the users language. See the languages folder in the root of this plugin."
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "info": "This is a translation string, it gets translated to the users language. See the languages folder in the root of this plugin."
3
+ }
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const Controllers = module.exports;
4
+
5
+ Controllers.renderAdminPage = function (req, res/* , next */) {
6
+ /*
7
+ Make sure the route matches your path to template exactly.
8
+
9
+ If your route was:
10
+ myforum.com/some/complex/route
11
+ your template should be:
12
+ templates/some/complex/route.tpl
13
+ and you would render it like so:
14
+ res.render('some/complex/route');
15
+ */
16
+
17
+ res.render('admin/plugins/quickstart', {
18
+ title: 'Quick Start',
19
+ });
20
+ };
package/library.js CHANGED
@@ -1,135 +1,88 @@
1
1
  'use strict';
2
2
 
3
- const path = require('path');
4
- const cheerio = require('cheerio');
3
+ const nconf = require.main.require('nconf');
4
+ const winston = require.main.require('winston');
5
5
 
6
- const CARD_INDEX_PATH = path.join(__dirname, 'data', 'cards-index.json');
7
- const CARD_HOST = 'https://cards.fabtcg.com';
6
+ const meta = require.main.require('./src/meta');
8
7
 
9
- let cardData = null;
10
- let cardMatcher = null;
8
+ const controllers = require('./lib/controllers');
11
9
 
12
- function loadCardData() {
13
- if (cardData) {
14
- return cardData;
15
- }
16
-
17
- cardData = require(CARD_INDEX_PATH);
18
- const cardNames = Object.keys(cardData.cards || {});
19
-
20
- if (!cardNames.length) {
21
- cardMatcher = null;
22
- return cardData;
23
- }
24
-
25
- const escaped = cardNames
26
- .sort((first, second) => second.length - first.length)
27
- .map(escapeRegExp);
28
-
29
- cardMatcher = new RegExp(`(^|[^\\p{L}\\p{N}])(${escaped.join('|')})(?=$|[^\\p{L}\\p{N}])`, 'giu');
30
- return cardData;
31
- }
32
-
33
- function normalize(input) {
34
- return String(input || '')
35
- .normalize('NFKC')
36
- .toLowerCase()
37
- .replace(/\s+/g, ' ')
38
- .trim();
39
- }
40
-
41
- function escapeRegExp(value) {
42
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
43
- }
44
-
45
- function escapeHtml(value) {
46
- return String(value)
47
- .replace(/&/g, '&amp;')
48
- .replace(/"/g, '&quot;')
49
- .replace(/</g, '&lt;')
50
- .replace(/>/g, '&gt;');
51
- }
52
-
53
- function shouldSkipNode(node) {
54
- const parentTag = node.parent && node.parent.tagName ? node.parent.tagName.toLowerCase() : '';
55
- return ['a', 'code', 'pre', 'script', 'style', 'textarea'].includes(parentTag);
56
- }
57
-
58
- export function linkifyText(text, cards) {
59
- if (!cardMatcher || !text || !text.trim()) {
60
- return text;
61
- }
62
-
63
- cardMatcher.lastIndex = 0;
64
- return text.replace(cardMatcher, (fullMatch, prefix, cardName) => {
65
- const normalized = normalize(cardName);
66
- const card = cards[normalized];
67
-
68
- if (!card) {
69
- return fullMatch;
70
- }
71
-
72
- const href = `${CARD_HOST}${card.url}`;
73
- const safeName = escapeHtml(card.name || cardName);
74
- const safeImage = escapeHtml(card.image || '');
75
- const safeHref = escapeHtml(href);
76
-
77
- return `${prefix}<a class="fab-card-link" href="${safeHref}" target="_blank" rel="noopener noreferrer" data-fab-card-name="${safeName}" data-fab-card-image="${safeImage}">${cardName}</a>`;
78
- });
79
- }
80
-
81
- function processNode($, node, cards) {
82
- if (!node) {
83
- return;
84
- }
85
-
86
- if (node.type === 'text' && !shouldSkipNode(node)) {
87
- const original = node.data || '';
88
- const replaced = linkifyText(original, cards);
89
- if (replaced !== original) {
90
- $(node).replaceWith(replaced);
91
- }
92
- return;
93
- }
94
-
95
- if (node.children && node.children.length) {
96
- node.children.slice().forEach((child) => processNode($, child, cards));
97
- }
98
- }
10
+ const routeHelpers = require.main.require('./src/routes/helpers');
99
11
 
100
12
  const plugin = {};
101
13
 
102
- plugin.parsePost = async function (payload) {
103
- const data = payload || {};
104
- const carrier = data.postData && typeof data.postData.content === 'string'
105
- ? data.postData
106
- : (typeof data.content === 'string' ? data : null);
107
-
108
- if (!carrier || !carrier.content) {
109
- return payload;
110
- }
111
-
112
- const type = data.type || data.postData?.type || 'default';
113
- if (type !== 'default') {
114
- return payload;
115
- }
116
-
117
- const loaded = loadCardData();
118
- if (!loaded || !loaded.cards || !cardMatcher) {
119
- return payload;
120
- }
14
+ plugin.init = async (params) => {
15
+ const { router /* , middleware , controllers */ } = params;
16
+
17
+ // Settings saved in the plugin settings can be retrieved via settings methods
18
+ const { setting1, setting2 } = await meta.settings.get('quickstart');
19
+ if (setting1) {
20
+ console.log(setting2);
21
+ }
22
+
23
+ /**
24
+ * We create two routes for every view. One API call, and the actual route itself.
25
+ * Use the `setupPageRoute` helper and NodeBB will take care of everything for you.
26
+ *
27
+ * Other helpers include `setupAdminPageRoute` and `setupAPIRoute`
28
+ * */
29
+ routeHelpers.setupPageRoute(router, '/quickstart', [(req, res, next) => {
30
+ winston.info(`[plugins/quickstart] In middleware. This argument can be either a single middleware or an array of middlewares`);
31
+ setImmediate(next);
32
+ }], (req, res) => {
33
+ winston.info(`[plugins/quickstart] Navigated to ${nconf.get('relative_path')}/quickstart`);
34
+ res.render('quickstart', { uid: req.uid });
35
+ });
36
+
37
+ routeHelpers.setupAdminPageRoute(router, '/admin/plugins/quickstart', controllers.renderAdminPage);
38
+ };
121
39
 
122
- const $ = cheerio.load(carrier.content, {
123
- decodeEntities: false,
124
- }, false);
40
+ /**
41
+ * If you wish to add routes to NodeBB's RESTful API, listen to the `static:api.routes` hook.
42
+ * Define your routes similarly to above, and allow core to handle the response via the
43
+ * built-in helpers.formatApiResponse() method.
44
+ *
45
+ * In this example route, the `ensureLoggedIn` middleware is added, which means a valid login
46
+ * session or bearer token (which you can create via ACP > Settings > API Access) needs to be
47
+ * passed in.
48
+ *
49
+ * To call this example route:
50
+ * curl -X GET \
51
+ * http://example.org/api/v3/plugins/quickstart/test \
52
+ * -H "Authorization: Bearer some_valid_bearer_token"
53
+ *
54
+ * Will yield the following response JSON:
55
+ * {
56
+ * "status": {
57
+ * "code": "ok",
58
+ * "message": "OK"
59
+ * },
60
+ * "response": {
61
+ * "foobar": "test"
62
+ * }
63
+ * }
64
+ */
65
+ plugin.addRoutes = async ({ router, middleware, helpers }) => {
66
+ const middlewares = [
67
+ middleware.ensureLoggedIn, // use this if you want only registered users to call this route
68
+ // middleware.admin.checkPrivileges, // use this to restrict the route to administrators
69
+ ];
70
+
71
+ routeHelpers.setupApiRoute(router, 'get', '/quickstart/:param1', middlewares, (req, res) => {
72
+ helpers.formatApiResponse(200, res, {
73
+ foobar: req.params.param1,
74
+ });
75
+ });
76
+ };
125
77
 
126
- const root = $.root().get(0);
127
- if (root && root.children) {
128
- root.children.slice().forEach((child) => processNode($, child, loaded.cards));
129
- }
78
+ plugin.addAdminNavigation = (header) => {
79
+ header.plugins.push({
80
+ route: '/plugins/quickstart',
81
+ icon: 'fa-tint',
82
+ name: 'Quickstart',
83
+ });
130
84
 
131
- carrier.content = $.root().html() || carrier.content;
132
- return payload;
85
+ return header;
133
86
  };
134
87
 
135
88
  module.exports = plugin;
package/package.json CHANGED
@@ -1,32 +1,49 @@
1
1
  {
2
2
  "name": "nodebb-plugin-fab-cards",
3
- "version": "0.1.0",
4
- "description": "Auto-link Flesh and Blood card names in NodeBB posts with hover/touch previews.",
3
+ "version": "0.1.2",
4
+ "description": "A starter kit for quickly creating NodeBB plugins",
5
5
  "main": "library.js",
6
- "scripts": {
7
- "build:cards": "node scripts/build-card-index.js"
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/nodebb/nodebb-plugin-fab-cards"
8
9
  },
9
10
  "keywords": [
10
11
  "nodebb",
11
- "nodebb-plugin",
12
- "flesh-and-blood",
13
- "fab"
12
+ "plugin",
13
+ "quickstart",
14
+ "shell"
14
15
  ],
16
+ "husky": {
17
+ "hooks": {
18
+ "pre-commit": "lint-staged",
19
+ "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
20
+ }
21
+ },
22
+ "lint-staged": {
23
+ "*.js": [
24
+ "eslint --fix",
25
+ "git add"
26
+ ]
27
+ },
15
28
  "license": "MIT",
16
- "dependencies": {
17
- "cheerio": "^1.0.0"
29
+ "bugs": {
30
+ "url": "https://github.com/nodebb/nodebb-plugin-fab-cards/issues"
18
31
  },
32
+ "readmeFilename": "README.md",
19
33
  "nbbpm": {
20
- "compatibility": "^4.0.0"
21
- },
22
- "devDependencies": {},
23
- "repository": {
24
- "type": "git",
25
- "url": "git+https://github.com/braaar/nodebb-plugin-fab-cards.git"
34
+ "compatibility": "^3.2.0"
26
35
  },
27
36
  "author": "braaar",
28
- "bugs": {
29
- "url": "https://github.com/braaar/nodebb-plugin-fab-cards/issues"
37
+ "homepage": "https://github.com/braaar/nodebb-plugin-fab-cards#readme",
38
+ "devDependencies": {
39
+ "@commitlint/cli": "20.4.2",
40
+ "@commitlint/config-angular": "20.4.2",
41
+ "eslint": "10.0.1",
42
+ "eslint-config-nodebb": "2.0.1",
43
+ "husky": "9.1.7",
44
+ "lint-staged": "16.2.7"
30
45
  },
31
- "homepage": "https://github.com/braaar/nodebb-plugin-fab-cards#readme"
32
- }
46
+ "scripts": {
47
+ "lint": "eslint ."
48
+ }
49
+ }
package/plugin.json CHANGED
@@ -1,18 +1,22 @@
1
1
  {
2
2
  "id": "nodebb-plugin-fab-cards",
3
- "name": "FAB Cards Auto-Link",
4
- "description": "Automatically links Flesh and Blood card names and shows card previews.",
5
- "url": "https://github.com/fabtcg/nodebb-plugin-fab-cards",
3
+ "url": "https://github.com/NodeBB/nodebb-plugin-fab-cards",
6
4
  "library": "./library.js",
7
5
  "hooks": [
8
- {
9
- "hook": "filter:parse.post",
10
- "method": "parsePost"
11
- }
6
+ { "hook": "static:app.load", "method": "init" },
7
+ { "hook": "static:api.routes", "method": "addRoutes" },
8
+ { "hook": "filter:admin.header.build", "method": "addAdminNavigation" }
12
9
  ],
13
10
  "staticDirs": {
14
- "public": "public"
11
+ "static": "./static"
15
12
  },
16
- "scripts": ["public/lib/client.js"],
17
- "css": ["public/css/style.css"]
13
+ "scss": ["scss/quickstart.scss"],
14
+ "scripts": ["public/lib/main.js"],
15
+ "acpScripts": ["public/lib/acp-main.js"],
16
+ "modules": {
17
+ "../client/quickstart.js": "./public/lib/quickstart.js",
18
+ "../admin/plugins/quickstart.js": "./public/lib/admin.js"
19
+ },
20
+ "templates": "templates",
21
+ "languages": "languages"
18
22
  }
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ $(document).ready(function () {
4
+ /*
5
+ This file shows how admin page client-side javascript can be included via a plugin.
6
+ If you check `plugin.json`, you'll see that this file is listed under "acpScripts".
7
+ That array tells NodeBB which files to bundle into the minified javascript
8
+ that is served to the end user.
9
+
10
+ Some events you can elect to listen for:
11
+
12
+ $(document).ready(); Fired when the DOM is ready
13
+ $(window).on('action:ajaxify.end', function(data) { ... }); "data" contains "url"
14
+ */
15
+
16
+ console.log('nodebb-plugin-fab-cards: acp-loaded');
17
+ // Note how this is shown in the console on the first load of every page in the ACP
18
+ });
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ /*
4
+ This file is located in the "modules" block of plugin.json
5
+ It is only loaded when the user navigates to /admin/plugins/quickstart page
6
+ It is not bundled into the min file that is served on the first load of the page.
7
+ */
8
+
9
+ import { save, load } from 'settings';
10
+ import * as uploader from 'uploader';
11
+
12
+ export function init() {
13
+ handleSettingsForm();
14
+ setupUploader();
15
+ };
16
+
17
+ function handleSettingsForm() {
18
+ load('quickstart', $('.quickstart-settings'), function () {
19
+ setupColorInputs();
20
+ });
21
+
22
+ $('#save').on('click', () => {
23
+ save('quickstart', $('.quickstart-settings')); // pass in a function in the 3rd parameter to override the default success/failure handler
24
+ });
25
+ }
26
+
27
+ function setupColorInputs() {
28
+ var colorInputs = $('[data-settings="colorpicker"]');
29
+ colorInputs.on('change', updateColors);
30
+ updateColors();
31
+ }
32
+
33
+ function updateColors() {
34
+ $('#preview').css({
35
+ color: $('#color').val(),
36
+ 'background-color': $('#bgColor').val(),
37
+ });
38
+ }
39
+
40
+ function setupUploader() {
41
+ $('#content input[data-action="upload"]').each(function () {
42
+ var uploadBtn = $(this);
43
+ uploadBtn.on('click', function () {
44
+ uploader.show({
45
+ route: config.relative_path + '/api/admin/upload/file',
46
+ params: {
47
+ folder: 'quickstart',
48
+ },
49
+ accept: 'image/*',
50
+ }, function (image) {
51
+ $('#' + uploadBtn.attr('data-target')).val(image);
52
+ });
53
+ });
54
+ });
55
+ }
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * This file shows how client-side javascript can be included via a plugin.
5
+ * If you check `plugin.json`, you'll see that this file is listed under "scripts".
6
+ * That array tells NodeBB which files to bundle into the minified javascript
7
+ * that is served to the end user.
8
+ *
9
+ * There are two (standard) ways to wait for when NodeBB is ready.
10
+ * This one below executes when NodeBB reports it is ready...
11
+ */
12
+
13
+ (async () => {
14
+ const hooks = await app.require('hooks');
15
+
16
+ hooks.on('action:app.load', () => {
17
+ // called once when nbb has loaded
18
+ console.log('hello fab!');
19
+ });
20
+
21
+ hooks.on('action:ajaxify.end', (/* data */) => {
22
+ // called everytime user navigates between pages including first load
23
+ });
24
+ })();
25
+
26
+ /**
27
+ * ... and this one reports when the DOM is loaded (but NodeBB might not be fully ready yet).
28
+ * For most cases, you'll want the one above.
29
+ */
30
+
31
+ $(document).ready(function () {
32
+ // ...
33
+ });
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ /*
4
+ This file is located in the "modules" block of plugin.json
5
+ It is only loaded when the user navigates to /quickstart page
6
+ It is not bundled into the min file that is served on the first load of the page.
7
+ */
8
+
9
+ define('forum/quickstart', function () {
10
+ var module = {};
11
+ module.init = function () {
12
+ $('#last-p').text('quickstart.js loaded!');
13
+ };
14
+ return module;
15
+ });
package/renovate.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": [
3
+ "config:recommended"
4
+ ]
5
+ }
@@ -0,0 +1 @@
1
+ /* Place any SASS in here */
@@ -0,0 +1,8 @@
1
+ <h1>Hello!</h1>
2
+
3
+ <p>
4
+ This file is served by nodebb at
5
+ domain.com/assets/plugins/nodebb-plugin-fab-cards/static/samplefile.html
6
+ </p>
7
+
8
+ Check plugin.json for the "staticDirs" property if you want to change the path.
@@ -0,0 +1,10 @@
1
+ <form>
2
+ <div class="mb-3">
3
+ <label class="form-label" for="name">Name</label>
4
+ <input type="text" id="name" name="name" class="form-control" placeholder="Name" />
5
+ </div>
6
+ <div class="mb-3">
7
+ <label class="form-label" for="description">Description</label>
8
+ <input type="text" id="description" name="description" class="form-control" placeholder="Description" />
9
+ </div>
10
+ </form>
@@ -0,0 +1,12 @@
1
+ <li data-type="item" class="list-group-item">
2
+ <div class="d-flex gap-2 justify-content-between align-items-start">
3
+ <div class="flex-grow-1">
4
+ <strong>{name}</strong><br />
5
+ <small>{description}</small>
6
+ </div>
7
+ <div class="d-flex gap-1 flex-nowrap">
8
+ <button type="button" data-type="edit" class="btn btn-sm btn-info">Edit</button>
9
+ <button type="button" data-type="remove" class="btn btn-sm btn-danger">Delete</button>
10
+ </div>
11
+ </div>
12
+ </li>