nodebb-plugin-anti-account-sharing 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 +22 -0
- package/LICENSE +21 -0
- package/README.md +17 -0
- package/commitlint.config.js +26 -0
- package/eslint.config.mjs +10 -0
- package/languages/de/quickstart.json +3 -0
- package/languages/en-GB/quickstart.json +3 -0
- package/languages/en-US/quickstart.json +3 -0
- package/lib/controllers.js +20 -0
- package/library.js +83 -0
- package/package.json +37 -0
- package/plugin.json +19 -0
- package/public/lib/acp-main.js +18 -0
- package/public/lib/admin.js +55 -0
- package/public/lib/main.js +32 -0
- package/public/lib/quickstart.js +15 -0
- package/renovate.json +5 -0
- package/scss/quickstart.scss +1 -0
- package/static/samplefile.html +5 -0
- package/static/security.js +71 -0
- package/templates/admin/plugins/quickstart/partials/sorted-list/form.tpl +10 -0
- package/templates/admin/plugins/quickstart/partials/sorted-list/item.tpl +12 -0
- package/templates/admin/plugins/quickstart.tpl +68 -0
- package/templates/quickstart.tpl +7 -0
- package/test/.eslintrc +9 -0
- package/test/index.js +41 -0
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,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
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Quickstart Plugin for NodeBB
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
Fork this or copy it, and using your favourite text editor find and replace all instances of `nodebb-plugin-quickstart` with `nodebb-plugin-your-plugins-name`. Change the author's name in the LICENSE and package.json files.
|
|
6
|
+
|
|
7
|
+
## Hello World
|
|
8
|
+
|
|
9
|
+
Really simple, just edit `public/lib/main.js` and paste in `console.log('hello world');`, and that's it!
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
npm install nodebb-plugin-quickstart
|
|
14
|
+
|
|
15
|
+
## Screenshots
|
|
16
|
+
|
|
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,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
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const db = require.main.require('./src/database');
|
|
4
|
+
const user = require.main.require('./src/user');
|
|
5
|
+
|
|
6
|
+
const Plugin = {};
|
|
7
|
+
|
|
8
|
+
// --- AYARLAR ---
|
|
9
|
+
const MAX_DEVICES = 1; // Kaç bilgisayara izin verilsin?
|
|
10
|
+
|
|
11
|
+
// Cihazın Mobil olup olmadığını anlayan fonksiyon
|
|
12
|
+
function isMobile(req) {
|
|
13
|
+
const ua = req.headers['user-agent'];
|
|
14
|
+
return /Mobile|Android|iPhone|iPad|iPod/i.test(ua);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
Plugin.init = async function (params) {
|
|
18
|
+
console.log(`[Anti-Share] Güvenlik Aktif (Limit: ${MAX_DEVICES} Bilgisayar) 🛡️`);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// 1. GİRİŞ YAPILDIĞINDA (Listeye Ekle)
|
|
22
|
+
Plugin.recordSession = async function (data) {
|
|
23
|
+
// Sadece Bilgisayar girişlerini takip ediyoruz
|
|
24
|
+
if (!isMobile(data.req)) {
|
|
25
|
+
if (data.uid && data.req.sessionID) {
|
|
26
|
+
const key = `antishare:sessions:${data.uid}`;
|
|
27
|
+
|
|
28
|
+
// 1. Yeni oturumu listenin SONUNA ekle
|
|
29
|
+
await db.listAppend(key, data.req.sessionID);
|
|
30
|
+
|
|
31
|
+
// 2. Listeyi kırp (Sadece son 5 taneyi tut)
|
|
32
|
+
// (Eğer 6. kişi girerse, listenin başındaki 1. kişi silinir)
|
|
33
|
+
await db.listTrim(key, -MAX_DEVICES, -1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// 2. HER SAYFA GEZİNTİSİNDE (Kontrol Et)
|
|
39
|
+
Plugin.checkSession = async function (data) {
|
|
40
|
+
const req = data.req;
|
|
41
|
+
const res = data.res;
|
|
42
|
+
|
|
43
|
+
// Giriş yapmamışsa veya Mobilden giriyorsa KARIŞMA
|
|
44
|
+
if (!req.uid || req.uid <= 0 || isMobile(req)) {
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const isAdmin = await user.isAdministrator(req.uid);
|
|
49
|
+
if (isAdmin) return data;
|
|
50
|
+
|
|
51
|
+
// --- BİLGİSAYAR KONTROLÜ ---
|
|
52
|
+
const key = `antishare:sessions:${req.uid}`;
|
|
53
|
+
|
|
54
|
+
// Veritabanındaki "Geçerli Oturum Listesi"ni çek
|
|
55
|
+
const validSessions = await db.getListRange(key, 0, -1);
|
|
56
|
+
|
|
57
|
+
// Eğer şu anki oturum ID'si, izin verilenler listesinde YOKSA -> AT!
|
|
58
|
+
if (validSessions && !validSessions.includes(req.sessionID)) {
|
|
59
|
+
|
|
60
|
+
// Oturumu Öldür
|
|
61
|
+
req.logout();
|
|
62
|
+
if (req.session) req.session.destroy();
|
|
63
|
+
|
|
64
|
+
if (req.xhr || (req.headers.accept && req.headers.accept.indexOf('json') > -1)) {
|
|
65
|
+
return res.status(401).json({
|
|
66
|
+
error: 'session_terminated',
|
|
67
|
+
message: 'Maksimum cihaz sınırına ulaşıldı.'
|
|
68
|
+
});
|
|
69
|
+
} else {
|
|
70
|
+
return res.redirect('/login?error=session-conflict');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return data;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Client scriptini ekle
|
|
78
|
+
Plugin.addScripts = async function (scripts) {
|
|
79
|
+
scripts.push('plugins/nodebb-plugin-anti-account-sharing/static/security.js');
|
|
80
|
+
return scripts;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
module.exports = Plugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nodebb-plugin-anti-account-sharing",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Prevents account sharing by enforcing a single active session policy for desktop devices.",
|
|
5
|
+
"main": "library.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"nodebb",
|
|
8
|
+
"plugin",
|
|
9
|
+
"security",
|
|
10
|
+
"anti-share",
|
|
11
|
+
"session-limit",
|
|
12
|
+
"account-protection"
|
|
13
|
+
],
|
|
14
|
+
"author": {
|
|
15
|
+
"name": "IEU Forum Team",
|
|
16
|
+
"email": "forum@ieu.app"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/sucreistaken/nodebb-plugin-anti-account-sharing"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/sucreistaken/nodebb-plugin-anti-account-sharing/issues"
|
|
25
|
+
},
|
|
26
|
+
"nbbpm": {
|
|
27
|
+
"compatibility": "^4.0.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"eslint": "9.39.2",
|
|
32
|
+
"eslint-config-nodebb": "1.1.11"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"lint": "eslint ."
|
|
36
|
+
}
|
|
37
|
+
}
|
package/plugin.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "nodebb-plugin-niki-antishare",
|
|
3
|
+
"name": "Niki Anti-Share Security",
|
|
4
|
+
"description": "Bilgisayar oturumlarını tekilleştirerek hesap paylaşımını engeller (Mobil hariç).",
|
|
5
|
+
"url": "https://forum.ieu.app",
|
|
6
|
+
"library": "./library.js",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{ "hook": "static:app.load", "method": "init" },
|
|
9
|
+
{ "hook": "action:user.loggedIn", "method": "recordSession" },
|
|
10
|
+
{ "hook": "filter:router.page", "method": "checkSession" },
|
|
11
|
+
{ "hook": "filter:scripts.get", "method": "addScripts" }
|
|
12
|
+
],
|
|
13
|
+
"scripts": [
|
|
14
|
+
"static/security.js"
|
|
15
|
+
],
|
|
16
|
+
"staticDirs": {
|
|
17
|
+
"static": "./static"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -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-quickstart: 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,32 @@
|
|
|
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
|
+
});
|
|
19
|
+
|
|
20
|
+
hooks.on('action:ajaxify.end', (/* data */) => {
|
|
21
|
+
// called everytime user navigates between pages including first load
|
|
22
|
+
});
|
|
23
|
+
})();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ... and this one reports when the DOM is loaded (but NodeBB might not be fully ready yet).
|
|
27
|
+
* For most cases, you'll want the one above.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
$(document).ready(function () {
|
|
31
|
+
// ...
|
|
32
|
+
});
|
|
@@ -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 @@
|
|
|
1
|
+
/* Place any SASS in here */
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
$(document).ready(function() {
|
|
4
|
+
// 1. URL Kontrolü (Sayfa yenileyince atıldıysa)
|
|
5
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
6
|
+
if (urlParams.get('error') === 'session-conflict') {
|
|
7
|
+
showSecurityModal();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 2. AJAX Kontrolü (Sayfada gezerken atıldıysa)
|
|
11
|
+
$(document).ajaxError(function(event, jqxhr) {
|
|
12
|
+
if (jqxhr.status === 401 && jqxhr.responseJSON && jqxhr.responseJSON.error === 'session_terminated') {
|
|
13
|
+
showSecurityModal();
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function showSecurityModal() {
|
|
18
|
+
// Varsa eski modalları temizle
|
|
19
|
+
$('#antishare-modal').remove();
|
|
20
|
+
|
|
21
|
+
// Niki'den bağımsız, genel ve şık bir tasarım
|
|
22
|
+
const modalHtml = `
|
|
23
|
+
<div id="antishare-modal" style="
|
|
24
|
+
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
|
25
|
+
background: rgba(0,0,0,0.92); z-index: 999999;
|
|
26
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
27
|
+
text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
28
|
+
animation: fadeIn 0.3s ease;
|
|
29
|
+
">
|
|
30
|
+
<div style="
|
|
31
|
+
background: #ffffff; padding: 40px; border-radius: 16px;
|
|
32
|
+
max-width: 90%; width: 400px; box-shadow: 0 10px 40px rgba(0,0,0,0.5);
|
|
33
|
+
position: relative; overflow: hidden;
|
|
34
|
+
">
|
|
35
|
+
<div style="position: absolute; top:0; left:0; width:100%; height:6px; background:#d9534f;"></div>
|
|
36
|
+
|
|
37
|
+
<div style="
|
|
38
|
+
width: 70px; height: 70px; background: #fdf2f2; border-radius: 50%;
|
|
39
|
+
display: flex; align-items: center; justify-content: center; margin: 0 auto 20px;
|
|
40
|
+
">
|
|
41
|
+
<i class="fa fa-shield" style="font-size: 32px; color: #d9534f;"></i>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<h2 style="color: #333; margin: 0 0 10px; font-size: 22px; font-weight: 700;">Oturum Sonlandırıldı</h2>
|
|
45
|
+
|
|
46
|
+
<p style="color: #666; font-size: 14px; line-height: 1.6; margin: 0 0 30px;">
|
|
47
|
+
Hesabınıza <strong>başka bir bilgisayardan</strong> giriş yapıldığı tespit edildi.<br><br>
|
|
48
|
+
Hesap güvenliğiniz için önceki cihazdaki oturum otomatik olarak kapatılmıştır.
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
<a href="/login" style="
|
|
52
|
+
display: block; width: 100%; padding: 14px 0;
|
|
53
|
+
background: #d9534f; color: #fff; text-decoration: none;
|
|
54
|
+
font-weight: 600; border-radius: 8px; font-size: 15px;
|
|
55
|
+
transition: background 0.2s;
|
|
56
|
+
" onmouseover="this.style.background='#c9302c'" onmouseout="this.style.background='#d9534f'">
|
|
57
|
+
TEKRAR GİRİŞ YAP
|
|
58
|
+
</a>
|
|
59
|
+
|
|
60
|
+
<div style="margin-top: 20px; font-size: 12px; color: #999;">
|
|
61
|
+
Mobil cihazlarınız bu durumdan etkilenmez.
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<style>@keyframes fadeIn { from { opacity:0; transform:scale(0.95); } to { opacity:1; transform:scale(1); } }</style>
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
$('body').append(modalHtml);
|
|
69
|
+
$('body').css('overflow', 'hidden'); // Scrollu kilitle
|
|
70
|
+
}
|
|
71
|
+
});
|
|
@@ -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>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<div class="acp-page-container">
|
|
2
|
+
<!-- IMPORT admin/partials/settings/header.tpl -->
|
|
3
|
+
|
|
4
|
+
<div class="row m-0">
|
|
5
|
+
<div id="spy-container" class="col-12 col-md-8 px-0 mb-4" tabindex="0">
|
|
6
|
+
<form role="form" class="quickstart-settings">
|
|
7
|
+
<div class="mb-4">
|
|
8
|
+
<h5 class="fw-bold tracking-tight settings-header">General</h5>
|
|
9
|
+
|
|
10
|
+
<p class="lead">
|
|
11
|
+
Adjust these settings. You can then retrieve these settings in code via:
|
|
12
|
+
<br/><code>await meta.settings.get('quickstart');</code>
|
|
13
|
+
</p>
|
|
14
|
+
<div class="mb-3">
|
|
15
|
+
<label class="form-label" for="setting-1">Setting 1</label>
|
|
16
|
+
<input type="text" id="setting-1" name="setting-1" title="Setting 1" class="form-control" placeholder="Setting 1">
|
|
17
|
+
</div>
|
|
18
|
+
<div class="mb-3">
|
|
19
|
+
<label class="form-label" for="setting-2">Setting 2</label>
|
|
20
|
+
<input type="text" id="setting-2" name="setting-2" title="Setting 2" class="form-control" placeholder="Setting 2">
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="form-check form-switch">
|
|
24
|
+
<input type="checkbox" class="form-check-input" id="setting-3" name="setting-3">
|
|
25
|
+
<label for="setting-3" class="form-check-label">Setting 3</label>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="mb-4">
|
|
30
|
+
<h5 class="fw-bold tracking-tight settings-header">Colors</h5>
|
|
31
|
+
|
|
32
|
+
<p class="alert" id="preview">
|
|
33
|
+
Here is some preview text. Use the inputs below to modify this alert's appearance.
|
|
34
|
+
</p>
|
|
35
|
+
<div class="mb-3 d-flex gap-2">
|
|
36
|
+
<label class="form-label" for="color">Foreground</label>
|
|
37
|
+
<input data-settings="colorpicker" type="color" id="color" name="color" title="Background Color" class="form-control p-1" placeholder="#ffffff" value="#ffffff" style="width: 64px;"/>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="mb-3 d-flex gap-2">
|
|
40
|
+
<label class="form-label" for="bgColor">Background</label>
|
|
41
|
+
<input data-settings="colorpicker" type="color" id="bgColor" name="bgColor" title="Background Color" class="form-control p-1" placeholder="#000000" value="#000000" style="width: 64px;" />
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="mb-4">
|
|
46
|
+
<h5 class="fw-bold tracking-tight settings-header">Sorted List</h5>
|
|
47
|
+
|
|
48
|
+
<div class="mb-3" data-type="sorted-list" data-sorted-list="sample-list" data-item-template="admin/plugins/quickstart/partials/sorted-list/item" data-form-template="admin/plugins/quickstart/partials/sorted-list/form">
|
|
49
|
+
<ul data-type="list" class="list-group mb-2"></ul>
|
|
50
|
+
<button type="button" data-type="add" class="btn btn-info">Add Item</button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div>
|
|
55
|
+
<h5 class="fw-bold tracking-tight settings-header">Uploads</h5>
|
|
56
|
+
|
|
57
|
+
<label class="form-label" for="uploadedImage">Upload Image</label>
|
|
58
|
+
<div class="d-flex gap-1">
|
|
59
|
+
<input id="uploadedImage" name="uploadedImage" type="text" class="form-control" />
|
|
60
|
+
<input value="Upload" data-action="upload" data-target="uploadedImage" type="button" class="btn btn-light" />
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</form>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<!-- IMPORT admin/partials/settings/toc.tpl -->
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
package/test/.eslintrc
ADDED
package/test/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* You can run these tests by executing `npx mocha test/plugins-installed.js`
|
|
3
|
+
* from the NodeBB root folder. The regular test runner will also run these
|
|
4
|
+
* tests.
|
|
5
|
+
*
|
|
6
|
+
* Keep in mind tests do not activate all plugins, so if you are testing
|
|
7
|
+
* hook listeners, socket.io, or mounted routes, you will need to add your
|
|
8
|
+
* plugin to `config.json`, e.g.
|
|
9
|
+
*
|
|
10
|
+
* {
|
|
11
|
+
* "test_plugins": [
|
|
12
|
+
* "nodebb-plugin-quickstart"
|
|
13
|
+
* ]
|
|
14
|
+
* }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
/* globals describe, it, before */
|
|
20
|
+
|
|
21
|
+
const assert = require('assert');
|
|
22
|
+
|
|
23
|
+
const db = require.main.require('./test/mocks/databasemock');
|
|
24
|
+
|
|
25
|
+
describe('nodebb-plugin-quickstart', () => {
|
|
26
|
+
before(() => {
|
|
27
|
+
// Prepare for tests here
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should pass', (done) => {
|
|
31
|
+
const actual = 'value';
|
|
32
|
+
const expected = 'value';
|
|
33
|
+
assert.strictEqual(actual, expected);
|
|
34
|
+
done();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should load config object', async () => { // Tests can be async functions too
|
|
38
|
+
const config = await db.getObject('config');
|
|
39
|
+
assert(config);
|
|
40
|
+
});
|
|
41
|
+
});
|