@saltcorn/server 1.0.0-rc.9 → 1.1.0-beta.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/app.js +22 -5
- package/auth/admin.js +9 -2
- package/auth/routes.js +17 -4
- package/locales/en.json +14 -1
- package/locales/pl.json +17 -1
- package/locales/ru.json +104 -15
- package/markup/admin.js +1 -0
- package/package.json +9 -9
- package/public/saltcorn-common.js +2 -2
- package/public/saltcorn.css +4 -0
- package/routes/actions.js +8 -1
- package/routes/admin.js +4 -0
- package/routes/fields.js +2 -0
- package/routes/list.js +5 -2
- package/routes/menu.js +54 -3
- package/routes/notifications.js +40 -0
- package/routes/page.js +1 -0
- package/routes/page_groupedit.js +1 -1
- package/routes/pageedit.js +7 -2
- package/routes/plugins.js +6 -5
- package/routes/registry.js +53 -7
- package/routes/tables.js +5 -4
- package/routes/utils.js +6 -3
- package/routes/viewedit.js +7 -4
- package/tests/api.test.js +88 -2
- package/tests/auth.test.js +3 -3
- package/tests/plugin_install.test.js +3 -3
- package/tests/plugins.test.js +14 -5
- package/public/bootstrap-iconpicker.min.js +0 -11
package/app.js
CHANGED
|
@@ -74,7 +74,7 @@ const noCsrfLookup = (state) => {
|
|
|
74
74
|
if (!state.plugin_routes) return null;
|
|
75
75
|
else {
|
|
76
76
|
const result = new Set();
|
|
77
|
-
for (const
|
|
77
|
+
for (const routes of Object.values(state.plugin_routes)) {
|
|
78
78
|
for (const url of routes
|
|
79
79
|
.filter((r) => r.noCsrf === true)
|
|
80
80
|
.map((r) => r.url)) {
|
|
@@ -87,7 +87,7 @@ const noCsrfLookup = (state) => {
|
|
|
87
87
|
|
|
88
88
|
const prepPluginRouter = (pluginRoutes) => {
|
|
89
89
|
const router = express.Router();
|
|
90
|
-
for (const
|
|
90
|
+
for (const routes of Object.values(pluginRoutes)) {
|
|
91
91
|
for (const route of routes) {
|
|
92
92
|
switch (route.method) {
|
|
93
93
|
case "post":
|
|
@@ -133,17 +133,32 @@ const getApp = async (opts = {}) => {
|
|
|
133
133
|
);
|
|
134
134
|
|
|
135
135
|
const helmetOptions = {
|
|
136
|
-
contentSecurityPolicy:
|
|
136
|
+
contentSecurityPolicy: {
|
|
137
|
+
directives: {
|
|
138
|
+
defaultSrc: ["'self'"],
|
|
139
|
+
connectSrc: ["'self'", "data:"],
|
|
140
|
+
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
|
|
141
|
+
"script-src-attr": ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
|
|
142
|
+
styleSrc: ["'self'", "https://fonts.googleapis.com", "https://fonts.gstatic.com", "'unsafe-inline'"],
|
|
143
|
+
imgSrc: ["'self'", "data:"],
|
|
144
|
+
fontSrc: ["'self'", "data:", "https://fonts.gstatic.com",],
|
|
145
|
+
"form-action": ["'self'", "javascript:"],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
137
148
|
referrerPolicy: {
|
|
138
149
|
policy: ["same-origin"],
|
|
139
150
|
},
|
|
140
151
|
};
|
|
152
|
+
if (
|
|
153
|
+
getState().getConfig("content_security_policy", "Disabled") === "Disabled"
|
|
154
|
+
)
|
|
155
|
+
helmetOptions.contentSecurityPolicy = false;
|
|
141
156
|
|
|
142
157
|
if (cross_domain_iframe) helmetOptions.xFrameOptions = false;
|
|
143
158
|
app.use(helmet(helmetOptions));
|
|
144
159
|
|
|
145
160
|
// TODO ch find a better solution
|
|
146
|
-
app.use(cors());
|
|
161
|
+
if (getState().getConfig("cors_enabled", true)) app.use(cors());
|
|
147
162
|
const bodyLimit = getState().getConfig("body_limit");
|
|
148
163
|
app.use(
|
|
149
164
|
express.json({
|
|
@@ -366,7 +381,9 @@ const getApp = async (opts = {}) => {
|
|
|
366
381
|
req.url === "/auth/login-with/jwt" ||
|
|
367
382
|
req.url === "/auth/signup")) ||
|
|
368
383
|
jwt_extractor(req) ||
|
|
369
|
-
req.url === "/auth/callback/saml"
|
|
384
|
+
req.url === "/auth/callback/saml" ||
|
|
385
|
+
req.url.startsWith("/notifications/share-handler") ||
|
|
386
|
+
req.url.startsWith("/notifications/manifest")
|
|
370
387
|
)
|
|
371
388
|
return disabledCsurf(req, res, next);
|
|
372
389
|
csurf(req, res, next);
|
package/auth/admin.js
CHANGED
|
@@ -356,11 +356,16 @@ const auth_settings_form = async (req) =>
|
|
|
356
356
|
"allow_signup",
|
|
357
357
|
"login_menu",
|
|
358
358
|
"allow_forgot",
|
|
359
|
-
|
|
359
|
+
{
|
|
360
|
+
section_header: req.__("Signup and login views"),
|
|
361
|
+
sublabel: "Login and signup views should be accessible by public users",
|
|
362
|
+
},
|
|
360
363
|
"login_form",
|
|
361
364
|
"signup_form",
|
|
365
|
+
"new_user_form",
|
|
362
366
|
"user_settings_form",
|
|
363
367
|
"verification_view",
|
|
368
|
+
{ section_header: req.__("Additional login and signup settings") },
|
|
364
369
|
"logout_url",
|
|
365
370
|
"signup_role",
|
|
366
371
|
"elevate_verified",
|
|
@@ -383,7 +388,9 @@ const http_settings_form = async (req) =>
|
|
|
383
388
|
"timeout",
|
|
384
389
|
"cookie_duration",
|
|
385
390
|
"cookie_duration_remember",
|
|
386
|
-
|
|
391
|
+
"cookie_samesite",
|
|
392
|
+
"content_security_policy",
|
|
393
|
+
"cors_enabled",
|
|
387
394
|
"public_cache_maxage",
|
|
388
395
|
"custom_http_headers",
|
|
389
396
|
"cross_domain_iframe",
|
package/auth/routes.js
CHANGED
|
@@ -306,8 +306,20 @@ router.get(
|
|
|
306
306
|
getAuthLinks("login")
|
|
307
307
|
);
|
|
308
308
|
else {
|
|
309
|
-
const resp = await login_form.run_possibly_on_page(
|
|
310
|
-
|
|
309
|
+
const resp = await login_form.run_possibly_on_page(
|
|
310
|
+
{},
|
|
311
|
+
req,
|
|
312
|
+
res,
|
|
313
|
+
false,
|
|
314
|
+
true
|
|
315
|
+
);
|
|
316
|
+
if (!resp) {
|
|
317
|
+
res.sendAuthWrap(
|
|
318
|
+
req.__(`Login`),
|
|
319
|
+
loginForm(req),
|
|
320
|
+
getAuthLinks("login")
|
|
321
|
+
);
|
|
322
|
+
} else if (login_form.default_render_page) {
|
|
311
323
|
const page = Page.findOne({ name: login_form.default_render_page });
|
|
312
324
|
res.sendWrap(
|
|
313
325
|
{ title: req.__(`Login`), no_menu: page?.attributes?.no_menu },
|
|
@@ -509,7 +521,8 @@ router.get(
|
|
|
509
521
|
if (!signup_form) await defaultSignup();
|
|
510
522
|
else {
|
|
511
523
|
const resp = await signup_form.run_possibly_on_page({}, req, res);
|
|
512
|
-
if (
|
|
524
|
+
if (!resp) await defaultSignup();
|
|
525
|
+
else if (signup_form.default_render_page) {
|
|
513
526
|
const page = Page.findOne({ name: signup_form.default_render_page });
|
|
514
527
|
res.sendWrap(
|
|
515
528
|
{ title: req.__(`Sign up`), no_menu: page?.attributes?.no_menu },
|
|
@@ -673,7 +686,7 @@ const getNewUserForm = async (new_user_view_name, req, askEmail) => {
|
|
|
673
686
|
layout,
|
|
674
687
|
submitLabel: req.__("Sign up"),
|
|
675
688
|
});
|
|
676
|
-
await form.fill_fkey_options();
|
|
689
|
+
await form.fill_fkey_options(false, undefined, req.user || { role_id: 100 });
|
|
677
690
|
if (askEmail) {
|
|
678
691
|
form.fields.push(
|
|
679
692
|
new Field({
|
package/locales/en.json
CHANGED
|
@@ -1477,5 +1477,18 @@
|
|
|
1477
1477
|
"After delete": "After delete",
|
|
1478
1478
|
"Search only on exact match, not substring match. Useful for large tables": "Search only on exact match, not substring match. Useful for large tables",
|
|
1479
1479
|
"Please select an entry point.": "Please select an entry point.",
|
|
1480
|
-
"Group by": "Group by"
|
|
1480
|
+
"Group by": "Group by",
|
|
1481
|
+
"View %s added to menu. Adjust access permissions in <a href=\"/menu\">Settings » Menu</a>": "View %s added to menu. Adjust access permissions in <a href=\"/menu\">Settings » Menu</a>",
|
|
1482
|
+
"Page %s added to menu. Adjust access permissions in <a href=\"/menu\">Settings » Menu</a>": "Page %s added to menu. Adjust access permissions in <a href=\"/menu\">Settings » Menu</a>",
|
|
1483
|
+
"SameSite": "SameSite",
|
|
1484
|
+
"Restrict use of cookie to third-party sites. Strict is more secure, but may impact authentication": "Restrict use of cookie to third-party sites. Strict is more secure, but may impact authentication",
|
|
1485
|
+
"Content Security Policy": "Content Security Policy",
|
|
1486
|
+
"CORS": "CORS",
|
|
1487
|
+
"Cross-origin resource sharing": "Cross-origin resource sharing",
|
|
1488
|
+
"Signup and login views": "Signup and login views",
|
|
1489
|
+
"Additional login and signup settings": "Additional login and signup settings",
|
|
1490
|
+
"Login and signup views should be accessible by public users": "Login and signup views should be accessible by public users",
|
|
1491
|
+
"Shared: %s": "Shared: %s",
|
|
1492
|
+
"Sharing not enabled": "Sharing not enabled",
|
|
1493
|
+
"You must be logged in to share": "You must be logged in to share"
|
|
1481
1494
|
}
|
package/locales/pl.json
CHANGED
|
@@ -1475,5 +1475,21 @@
|
|
|
1475
1475
|
"Invalid build directory path": "Nieprawidłowa ścieżka katalogu kompilacji",
|
|
1476
1476
|
"Invalid build directory name": "Nieprawidłowa nazwa katalogu kompilacji",
|
|
1477
1477
|
"clean node_modules": "wyczyść node_modules",
|
|
1478
|
-
"After delete": "Po usunięciu"
|
|
1478
|
+
"After delete": "Po usunięciu",
|
|
1479
|
+
"Search only on exact match, not substring match. Useful for large tables": "Wyszukuj tylko dokładne dopasowania, nie dopasowania do podciągów. Przydatne w przypadku dużych tabel",
|
|
1480
|
+
"Please select an entry point.": "Proszę wybrać punkt wejścia",
|
|
1481
|
+
"Group by": "Grupuj według",
|
|
1482
|
+
"View %s added to menu. Adjust access permissions in <a href=\"/menu\">Settings » Menu</a>": "Widok %s został dodany do menu. Dostosuj uprawnienia dostępu w <a href=\"/menu\">Ustawienia » Menu</a>",
|
|
1483
|
+
"Page %s added to menu. Adjust access permissions in <a href=\"/menu\">Settings » Menu</a>": "Strona %s została dodana do menu. Dostosuj uprawnienia dostępu w <a href=\"/menu\">Ustawienia » Menu</a>",
|
|
1484
|
+
"SameSite": "SameSite",
|
|
1485
|
+
"Restrict use of cookie to third-party sites. Strict is more secure, but may impact authentication": "Ogranicz użycie ciasteczka na stronach trzecich. Tryb „Strict” jest bardziej bezpieczny, ale może wpływać na uwierzytelnianie",
|
|
1486
|
+
"Content Security Policy": "Polityka bezpieczeństwa treści",
|
|
1487
|
+
"CORS": "CORS",
|
|
1488
|
+
"Cross-origin resource sharing": "Udostępnianie zasobów między źródłami (CORS)",
|
|
1489
|
+
"Signup and login views": "Widoki rejestracji i logowania",
|
|
1490
|
+
"Additional login and signup settings": "Dodatkowe ustawienia logowania i rejestracji",
|
|
1491
|
+
"Login and signup views should be accessible by public users": "Widoki logowania i rejestracji powinny być dostępne dla użytkowników publicznych",
|
|
1492
|
+
"Shared: %s": "Udostępnione: %s",
|
|
1493
|
+
"Sharing not enabled": "Udostępnianie nie jest włączone",
|
|
1494
|
+
"You must be logged in to share": "Musisz być zalogowany, aby udostępniać"
|
|
1479
1495
|
}
|
package/locales/ru.json
CHANGED
|
@@ -242,8 +242,8 @@
|
|
|
242
242
|
"Role required to access page": "Роль, которая требуется для доступа к странице",
|
|
243
243
|
"Page attributes": "Атрибуты страницы",
|
|
244
244
|
"Page configuration": "Настройки страницы",
|
|
245
|
-
"Fixed state for %s view": "Фиксированное состояние для
|
|
246
|
-
"Set fixed states for views": "Установите фиксированные состояния для
|
|
245
|
+
"Fixed state for %s view": "Фиксированное состояние для Вида %s ",
|
|
246
|
+
"Set fixed states for views": "Установите фиксированные состояния для Видов",
|
|
247
247
|
"Page %s saved": "Страница %s сохранена",
|
|
248
248
|
"Your pages": "Ваши страницы",
|
|
249
249
|
"Add page": "Создать страницу",
|
|
@@ -1096,7 +1096,7 @@
|
|
|
1096
1096
|
"Click to edit?": "Редактировать по клику?",
|
|
1097
1097
|
"Format": "Формат",
|
|
1098
1098
|
"Page '%s' was loaded": "Страница '%s' была загружена",
|
|
1099
|
-
"Table provider": "Провайдер
|
|
1099
|
+
"Table provider": "Провайдер таблиц",
|
|
1100
1100
|
"Database table": "Таблица БД",
|
|
1101
1101
|
"Disable on mobile": "Отключить на мобильных устройствах",
|
|
1102
1102
|
"Installed theme": "Установленная тема",
|
|
@@ -1108,28 +1108,117 @@
|
|
|
1108
1108
|
"Use this to restrict your field to a list of options (separated by commas). For instance, enter <kbd class=\"fst-normal\">Red, Green, Blue</kbd> here if the permissible values are Red, Green and Blue. Leave blank if the string can hold any value.": "Используйте это, чтобы ограничить ваше поле списком вариантов (разделенных запятыми). Например, введите <kbd class=\"fst-normal\">Красный, Зеленый, Синий</kbd> здесь, если допустимые значения - Красный, Зеленый и Синий. Оставьте пустым, если строка может содержать любое значение.",
|
|
1109
1109
|
"String value must match regular expression": "Строковое значение должно соответствовать регулярному выражению",
|
|
1110
1110
|
"Pagegroup": "Группа страниц",
|
|
1111
|
-
"Auto public login": "
|
|
1111
|
+
"Auto public login": "Авто логин для анонима (public)",
|
|
1112
1112
|
"Table Synchronization": "Синхронизация таблиц",
|
|
1113
1113
|
"unsynched": "не синхронно",
|
|
1114
1114
|
"synched": "синхронно",
|
|
1115
|
-
"exclude": "
|
|
1116
|
-
"include": "
|
|
1117
|
-
"Cordova builder": "Cordova
|
|
1118
|
-
"not available": "не доступно",
|
|
1115
|
+
"exclude": "исключить(я)",
|
|
1116
|
+
"include": "включить(я)",
|
|
1117
|
+
"Cordova builder": "Конструктор Cordova",
|
|
1118
|
+
"not available": "не доступно(ен)",
|
|
1119
1119
|
"pull": "получить",
|
|
1120
1120
|
"refresh": "обновить",
|
|
1121
|
-
"Missing screen info": "
|
|
1122
|
-
"What to do if no screen info is given. Reload with parmeters or guess it from the user-agent.": "Что делать, если
|
|
1123
|
-
"Guess from user agent": "
|
|
1121
|
+
"Missing screen info": "Нет такой конфигурации экрана",
|
|
1122
|
+
"What to do if no screen info is given. Reload with parmeters or guess it from the user-agent.": "Что делать, если такая конфигурация экрана не настроена: Перезагрузить с параметрами или Определить по User-Agent.",
|
|
1123
|
+
"Guess from user agent": "Определить по User Agent",
|
|
1124
1124
|
"Reload": "Перезагрузить",
|
|
1125
|
-
"User Agent screen infos": "User Agent
|
|
1126
|
-
"This screen infos are used when the browser does not send them. With 'Missing screen info' set to 'Guess from user agent', the user agent gets mapped to a device with the following values.": "
|
|
1127
|
-
"Add screen info": "Добавить
|
|
1125
|
+
"User Agent screen infos": "Конфигурации экранов для User Agent(ов)",
|
|
1126
|
+
"This screen infos are used when the browser does not send them. With 'Missing screen info' set to 'Guess from user agent', the user agent gets mapped to a device with the following values.": "Настроим конфигурации экранов. При установке 'Нет такой конфигурации экрана' в значение 'Определить по User-Agent', будет использоваться соответствующая конфигурация экрана.",
|
|
1127
|
+
"Add screen info": "Добавить конфигурацию экрана",
|
|
1128
1128
|
"Page Group settings": "Группа страниц (настройки)",
|
|
1129
1129
|
"Delete table": "Удалить таблицу",
|
|
1130
1130
|
"Sync information": "Информация по синхронизации",
|
|
1131
1131
|
"Sync information tracks the last modification or deletion timestamp so that the table data can be synchronized with the mobile app": "Информация синхронизации отслеживает временную метку последнего изменения или удаления, чтобы данные таблицы могли быть синхронизированы с мобильным приложением",
|
|
1132
1132
|
"Do not pick or compare time": "Игнорировать время",
|
|
1133
1133
|
"The current theme has no user specific settings": "Текущая тема не имеет настроек, специфичных для пользователя",
|
|
1134
|
-
"Create trigger": "
|
|
1134
|
+
"Create trigger": "Создать триггер",
|
|
1135
|
+
"Allow self-signed": "Разрешить самоподписанные",
|
|
1136
|
+
"Open a connection to TLS server with self-signed or invalid TLS certificate": "Разрешить подключение к серверу TLS с саподписанным или некорректным TLS сертификатом",
|
|
1137
|
+
"Check updates": "Проверить обновления",
|
|
1138
|
+
"Description header": "Описание как заголовок",
|
|
1139
|
+
"Use table description instead of name as header": "Использовать описание таблицы вместо имени таблицы как заголовок",
|
|
1140
|
+
"Registry editor": "Редактор объектов",
|
|
1141
|
+
"Minimum user role required to create a new tenant<div class=\"alert alert-danger fst-normal\" role=\"alert\" data-show-if=\"showIfFormulaInputs($('select[name=role_to_create_tenant]'), '+role_to_create_tenant>1')\">Giving non-trusted users access to create tenants is a security risk and not recommended.</div>": "Минимальная рольк пользователя, требуемая для создания нового тенанта<div class=\"alert alert-danger fst-normal\" role=\"alert\" data-show-if=\"showIfFormulaInputs($('select[name=role_to_create_tenant]'), '+role_to_create_tenant>1')\">Предоставление Разрешения на создание тенантов Недоверенным Пользователям является серьезным риском безопасности, поэтому не рекомендуется.</div>",
|
|
1142
|
+
"Your page groups": "Ваши группы страниц",
|
|
1143
|
+
"A group has pages with an eligible formula. When you request a group, then the first page where the formula matches gets served. This way, you can choose a page depending on the screen of the device.": "Группа содержить страницы с разрешающей формулой. При запросе группы используется первая страница, где выполняется формула. Таким образом, вы можете выбрать страницу на основе размерности экрана устройства.",
|
|
1144
|
+
"Create page group": "Создать группу страниц",
|
|
1145
|
+
"Models": "Модели",
|
|
1146
|
+
"Destination page group": "Целевая группа страниц",
|
|
1147
|
+
"Show if formula": "Показать если формула выполнилась",
|
|
1148
|
+
"Show link or embed if true, don't show if false. Based on state variables from URL query string and <code>user</code>. For the full state use <code>row</code>. Example: <code>!!row.createlink</code> to show link if and only if state has <code>createlink</code>.": "Если значение Истина, то показывать ссылку или эмбед. В ином случае не показывать. Определяется по переменным состояния из строки запроса URL и <code>user</code> (пользователю). Для полного сосстояния используйте <code>row</code> (строка). Пример: <code>!!row.createlink</code>, чтобы показать ссылку, если и только если состояние имеет <code>createlink</code>.",
|
|
1149
|
+
"The view name is part of the URL when it is shown alone.": "Имя (название) вида - это часть URL, когда вид отображается отдельно.",
|
|
1150
|
+
"Some view patterns accept interpolations. Ex: <code>{{ name }}</code> or <code>{{ row ? `Edit ${row.name}` : `New person` }}</code>": "Some view patterns accept interpolations. Ex: <code>{{ name }}</code> or <code>{{ row ? `Edit ${row.name}` : `New person` }}</code>",
|
|
1151
|
+
"Page description": "Описание страницы",
|
|
1152
|
+
"For search engines. Some view patterns accept interpolations.": "Для поисковых движков. Некоторые паттерны (типы) видов принимают интерполяции.",
|
|
1153
|
+
"No menu": "Нет меню",
|
|
1154
|
+
"Omit the menu from this view": "Исключить меню из данного Вида",
|
|
1155
|
+
"Popup width": "Ширина попапа (width)",
|
|
1156
|
+
"Popup min width": "Минимальная ширина попапа",
|
|
1157
|
+
"Show an icon in the title bar to indicate when form data is being saved": "Показывать в заголовке значок (иконку) во время сохранения данных",
|
|
1158
|
+
"New model": "Новая модель",
|
|
1159
|
+
"Restart here": "Перезапустить",
|
|
1160
|
+
"New user view": "Вид для нового пользователя",
|
|
1161
|
+
"A view to show to new users, to finalise registration (if Edit) or as a welcome view": "Вид отображается новым пользователям для завершения регистрации (если тип вида Edit) или как Приветственный Вид",
|
|
1162
|
+
"Logout URL": "Logout URL",
|
|
1163
|
+
"The URL to direct to after logout": "URL для переадресации после разлогина (logout)",
|
|
1164
|
+
"Signup role": "Signup role",
|
|
1165
|
+
"The initial role of signed up users": "Начальная роль для зарегистрированных пользователей",
|
|
1166
|
+
"Plain password trigger row": "Открытые пароли в триггерах",
|
|
1167
|
+
"Send plaintext password changes to Users table triggers (Insert, Update and Validate).": "Разрешает посылать пароли в открытом виде триггерам таблицы Users (Insert, Update и Validate).",
|
|
1168
|
+
"Cross-domain iframe": "Кросс-доменный iframe",
|
|
1169
|
+
"Allow embedding in iframe on different domains. Unsets the X-Frame-Options header": "Разрешание на встраивание в iframe на других доменах. Выключает параметр X-Frame-Options в заголовке",
|
|
1170
|
+
"Body size limit (Kb)": "Ограничие размера запроса (Kb)",
|
|
1171
|
+
"Maximum request body size in kilobytes": "Максимальный размер тела запроса в килобайтах",
|
|
1172
|
+
"URL encoded size limit (Kb)": "URL encoded size limit (Kb)",
|
|
1173
|
+
"Maximum URL encoded request size in kilobytes": "Максимальный размер закодированного URL запроса в килобайтах",
|
|
1174
|
+
"Prune session interval (seconds)": "Срок истекания сессий (секунлы)",
|
|
1175
|
+
"Interval in seconds to check for expred sessions in the postgres db. 0, empty or a negative number to disable": "Интервал в секундах для проверки просроченных сессий для БД Postgres. Установить 0, пустое знаение или отрицательное число, чтобы не использовать",
|
|
1176
|
+
"Periodic trigger timing (next event)": "Интервал времени для регулярных триггеров (следующее событие)",
|
|
1177
|
+
"Hourly": "Ежечасно",
|
|
1178
|
+
"Daily": "Ежедневно",
|
|
1179
|
+
"Weekly": "Еженедельно",
|
|
1180
|
+
"Events and Trigger settings": "События и Триггеры (настройки)",
|
|
1181
|
+
"Configure action": "Настройть действие",
|
|
1182
|
+
"Log IP address": "Логировать IP адрес",
|
|
1183
|
+
"Record the request IP address in log messages": "Записывать IP в сообщения логирования",
|
|
1184
|
+
"Runtime informations": "Runtime информация",
|
|
1185
|
+
"open logs viewer": "Посмотреть логи",
|
|
1186
|
+
"Constants and function code": "Константы и код функции",
|
|
1187
|
+
"Server logs": "Логи сервера",
|
|
1188
|
+
"Timestamp": "Дата Время",
|
|
1189
|
+
"Message": "Сообщение",
|
|
1190
|
+
"Include table history in backup": "Включить историю таблиц в бекап",
|
|
1191
|
+
"Include Event Logs": "Включиь логи событий",
|
|
1192
|
+
"Backup with event logs": "Включить логи событий в бекап",
|
|
1193
|
+
"Use system zip": "Использовать системный zip",
|
|
1194
|
+
"Recommended. Executable <code>zip</code> must be installed": "Рекомендованно. Требуется установка испольняемого файла <code>zip</code> в системе",
|
|
1195
|
+
"Zip compression level": "Уровень сжатия Zip",
|
|
1196
|
+
"1=Fast, larger file, 9=Slow, smaller files": "1=Быстро, больше файлы архива, 9=Медленно, меньше размер файлов архива",
|
|
1197
|
+
"Server host": "Имя хоста сервера (hostname)",
|
|
1198
|
+
"Username": "Имя пользователя",
|
|
1199
|
+
"Port": "Порт",
|
|
1200
|
+
"Retain local directory": "Локальная папка с копиями",
|
|
1201
|
+
"Retain a local backup copy in this directory (optional)": "Оставлять локальные копии бекапов в папке (опционально)",
|
|
1202
|
+
"All tenants": "Все тенанты",
|
|
1203
|
+
"Also backup all tenants": "Также выполнять бекап всех тенантов",
|
|
1204
|
+
"Mobile": "Mobile",
|
|
1205
|
+
"install a different version": "установить другую версию модуля",
|
|
1206
|
+
"Module dependencies": "Зависимости модуля",
|
|
1207
|
+
"Types": "Типы",
|
|
1208
|
+
"Functions": "Функции",
|
|
1209
|
+
"Page group": "Группа страниц",
|
|
1210
|
+
"Tooltip formula": "Формула для подсказки",
|
|
1211
|
+
"Tooltip": "Подсказка",
|
|
1212
|
+
"Mobile HTML": "Mobile HTML",
|
|
1213
|
+
"HTML for the item in the bottom navigation bar. Currently, only supported by the metronic theme.": "HTML для элемента в нижнем навигационном блоке. В настоящий момент поддерживается только для темы Metronic.",
|
|
1214
|
+
"Some themes support only one level of menu nesting.": "Не все темы поддерживают многоуровневые меню.",
|
|
1215
|
+
"Settings saved": "Настройки сохранены",
|
|
1216
|
+
"width": "ширина",
|
|
1217
|
+
"height": "высота",
|
|
1218
|
+
"innerWidth": "внутренняя ширина",
|
|
1219
|
+
"innerHeight": "внутренняя высота",
|
|
1220
|
+
"Cancel": "Отмена",
|
|
1221
|
+
"Add %s to tag %s": "Добавить %s в тег %s",
|
|
1222
|
+
"Add entries to tag %s": "Добавить элементы к тегу %s",
|
|
1223
|
+
"Module %s removed.": "Module %s removed."
|
|
1135
1224
|
}
|
package/markup/admin.js
CHANGED
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0-beta.0",
|
|
4
4
|
"description": "Server app for Saltcorn, open-source no-code platform",
|
|
5
5
|
"homepage": "https://saltcorn.com",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@aws-sdk/client-s3": "^3.451.0",
|
|
10
|
-
"@saltcorn/base-plugin": "1.
|
|
11
|
-
"@saltcorn/builder": "1.
|
|
12
|
-
"@saltcorn/data": "1.
|
|
13
|
-
"@saltcorn/admin-models": "1.
|
|
14
|
-
"@saltcorn/filemanager": "1.
|
|
15
|
-
"@saltcorn/markup": "1.
|
|
16
|
-
"@saltcorn/plugins-loader": "1.
|
|
17
|
-
"@saltcorn/sbadmin2": "1.
|
|
10
|
+
"@saltcorn/base-plugin": "1.1.0-beta.0",
|
|
11
|
+
"@saltcorn/builder": "1.1.0-beta.0",
|
|
12
|
+
"@saltcorn/data": "1.1.0-beta.0",
|
|
13
|
+
"@saltcorn/admin-models": "1.1.0-beta.0",
|
|
14
|
+
"@saltcorn/filemanager": "1.1.0-beta.0",
|
|
15
|
+
"@saltcorn/markup": "1.1.0-beta.0",
|
|
16
|
+
"@saltcorn/plugins-loader": "1.1.0-beta.0",
|
|
17
|
+
"@saltcorn/sbadmin2": "1.1.0-beta.0",
|
|
18
18
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
19
19
|
"@socket.io/sticky": "^1.0.1",
|
|
20
20
|
"adm-zip": "0.5.10",
|
|
@@ -33,7 +33,7 @@ function setScreenInfoCookie() {
|
|
|
33
33
|
innerHeight: window.innerHeight,
|
|
34
34
|
})}; expires=Thu, 01 Jan 2100 00:00:00 GMT; path=/; domain=.${
|
|
35
35
|
window.location.hostname
|
|
36
|
-
}`;
|
|
36
|
+
}; samesite=strict`;
|
|
37
37
|
}
|
|
38
38
|
setScreenInfoCookie();
|
|
39
39
|
$(window).resize(() => {
|
|
@@ -1352,7 +1352,7 @@ function buildToast(txt, type, spin) {
|
|
|
1352
1352
|
role="alert"
|
|
1353
1353
|
aria-live="assertive"
|
|
1354
1354
|
aria-atomic="true"
|
|
1355
|
-
style="min-width: 350px; max-width: 50vw; width: auto; z-index:
|
|
1355
|
+
style="min-width: 350px; max-width: 50vw; width: auto; z-index: 9999; ${
|
|
1356
1356
|
!isNode ? "transform: translateX(-50%);" : ""
|
|
1357
1357
|
}"
|
|
1358
1358
|
>
|
package/public/saltcorn.css
CHANGED
package/routes/actions.js
CHANGED
|
@@ -564,10 +564,17 @@ router.get(
|
|
|
564
564
|
|
|
565
565
|
const subtitle =
|
|
566
566
|
span(
|
|
567
|
-
{ class: "ms-
|
|
567
|
+
{ class: "ms-2" },
|
|
568
568
|
trigger.action,
|
|
569
|
+
" ",
|
|
570
|
+
trigger.when_trigger,
|
|
569
571
|
table ? ` on ` + a({ href: `/table/${table.name}` }, table.name) : ""
|
|
570
572
|
) +
|
|
573
|
+
a(
|
|
574
|
+
{ href: `/actions/edit/${id}`, class: "ms-2" },
|
|
575
|
+
req.__("Edit"),
|
|
576
|
+
' <i class="fas fa-edit"></i>'
|
|
577
|
+
) +
|
|
571
578
|
a(
|
|
572
579
|
{ href: `/actions/testrun/${id}`, class: "ms-2" },
|
|
573
580
|
req.__("Test run") + " »"
|
package/routes/admin.js
CHANGED
|
@@ -3844,6 +3844,10 @@ admin_config_route({
|
|
|
3844
3844
|
{ section_header: "Progressive Web Application" },
|
|
3845
3845
|
"pwa_enabled",
|
|
3846
3846
|
{ name: "pwa_display", showIf: { pwa_enabled: true } },
|
|
3847
|
+
{
|
|
3848
|
+
name: "pwa_share_to_enabled",
|
|
3849
|
+
showIf: { pwa_enabled: true },
|
|
3850
|
+
},
|
|
3847
3851
|
{ name: "pwa_set_colors", showIf: { pwa_enabled: true } },
|
|
3848
3852
|
{
|
|
3849
3853
|
name: "pwa_theme_color",
|
package/routes/fields.js
CHANGED
|
@@ -1158,6 +1158,8 @@ router.post(
|
|
|
1158
1158
|
try {
|
|
1159
1159
|
if (!field.calculated) {
|
|
1160
1160
|
result = row[field.name];
|
|
1161
|
+
} else if (field.stored && field.expression === "__aggregation") {
|
|
1162
|
+
result = row[field.name];
|
|
1161
1163
|
} else if (field.stored) {
|
|
1162
1164
|
const f = get_async_expression_function(formula, fields);
|
|
1163
1165
|
//are there join fields in formula?
|
package/routes/list.js
CHANGED
|
@@ -303,7 +303,10 @@ router.get(
|
|
|
303
303
|
type: "breadcrumbs",
|
|
304
304
|
crumbs: [
|
|
305
305
|
{ text: req.__("Tables"), href: "/table" },
|
|
306
|
-
{
|
|
306
|
+
{
|
|
307
|
+
href: `/table/${table.id || encodeURIComponent(table.name)}`,
|
|
308
|
+
text: table.name,
|
|
309
|
+
},
|
|
307
310
|
{ text: req.__("Data") },
|
|
308
311
|
],
|
|
309
312
|
right: div(
|
|
@@ -376,7 +379,7 @@ router.get(
|
|
|
376
379
|
})
|
|
377
380
|
})
|
|
378
381
|
window.tabulator_table = new Tabulator("#jsGrid", {
|
|
379
|
-
ajaxURL:"/api/${table.name}${
|
|
382
|
+
ajaxURL:"/api/${encodeURIComponent(table.name)}${
|
|
380
383
|
table.versioned ? "?versioncount=on" : ""
|
|
381
384
|
}",
|
|
382
385
|
layout:"fitColumns",
|
package/routes/menu.js
CHANGED
|
@@ -20,11 +20,19 @@ const { save_menu_items } = require("@saltcorn/data/models/config");
|
|
|
20
20
|
const db = require("@saltcorn/data/db");
|
|
21
21
|
|
|
22
22
|
const { renderForm } = require("@saltcorn/markup");
|
|
23
|
-
const {
|
|
23
|
+
const {
|
|
24
|
+
script,
|
|
25
|
+
domReady,
|
|
26
|
+
div,
|
|
27
|
+
ul,
|
|
28
|
+
i,
|
|
29
|
+
style,
|
|
30
|
+
} = require("@saltcorn/markup/tags");
|
|
24
31
|
const { send_infoarch_page } = require("../markup/admin.js");
|
|
25
32
|
const Table = require("@saltcorn/data/models/table");
|
|
26
33
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
27
34
|
const { run_action_column } = require("@saltcorn/data/plugin-helper");
|
|
35
|
+
const path = require("path");
|
|
28
36
|
|
|
29
37
|
/**
|
|
30
38
|
* @type {object}
|
|
@@ -497,7 +505,7 @@ router.get(
|
|
|
497
505
|
script: static_pre + "/jquery-menu-editor.min.js",
|
|
498
506
|
},
|
|
499
507
|
{
|
|
500
|
-
script:
|
|
508
|
+
script: "/menu/icon-options?format=bootstrap-iconpicker",
|
|
501
509
|
},
|
|
502
510
|
{
|
|
503
511
|
script: static_pre + "/bootstrap-iconpicker.js",
|
|
@@ -526,7 +534,8 @@ router.get(
|
|
|
526
534
|
),
|
|
527
535
|
div(
|
|
528
536
|
renderForm(form, req.csrfToken()),
|
|
529
|
-
script(domReady(menuEditorScript(menu_items)))
|
|
537
|
+
script(domReady(menuEditorScript(menu_items))),
|
|
538
|
+
style(setIconStyle())
|
|
530
539
|
),
|
|
531
540
|
],
|
|
532
541
|
},
|
|
@@ -599,3 +608,45 @@ router.post(
|
|
|
599
608
|
else res.status(404).json({ error: "Action not found" });
|
|
600
609
|
})
|
|
601
610
|
);
|
|
611
|
+
|
|
612
|
+
const getIcons = () => {
|
|
613
|
+
return getState().icons;
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const setIconStyle = () => {
|
|
617
|
+
const icons = getIcons();
|
|
618
|
+
return icons
|
|
619
|
+
.filter((icon) => icon.startsWith("unicode-"))
|
|
620
|
+
.map(
|
|
621
|
+
(icon) =>
|
|
622
|
+
`i.${icon}:after {content: '${String.fromCharCode(
|
|
623
|
+
parseInt(icon.substring(8, 12), 16)
|
|
624
|
+
)}'}`
|
|
625
|
+
)
|
|
626
|
+
.join("\n");
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
router.get(
|
|
630
|
+
"/icon-options",
|
|
631
|
+
isAdmin,
|
|
632
|
+
error_catcher(async (req, res) => {
|
|
633
|
+
const { format } = req.query;
|
|
634
|
+
const icons = getIcons();
|
|
635
|
+
switch (format) {
|
|
636
|
+
case "bootstrap-iconpicker":
|
|
637
|
+
res.type("text/javascript");
|
|
638
|
+
res.send(
|
|
639
|
+
`jQuery.iconset_fontawesome_5={iconClass:"",iconClassFix:"",version:"5.3.1",icons:${JSON.stringify(
|
|
640
|
+
icons
|
|
641
|
+
)}}`
|
|
642
|
+
);
|
|
643
|
+
break;
|
|
644
|
+
case "json":
|
|
645
|
+
res.json(icons);
|
|
646
|
+
|
|
647
|
+
default:
|
|
648
|
+
res.send("");
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
})
|
|
652
|
+
);
|
package/routes/notifications.js
CHANGED
|
@@ -13,6 +13,7 @@ const { getState } = require("@saltcorn/data/db/state");
|
|
|
13
13
|
const Form = require("@saltcorn/data/models/form");
|
|
14
14
|
const File = require("@saltcorn/data/models/file");
|
|
15
15
|
const User = require("@saltcorn/data/models/user");
|
|
16
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
16
17
|
const { renderForm, post_btn } = require("@saltcorn/markup");
|
|
17
18
|
const db = require("@saltcorn/data/db");
|
|
18
19
|
|
|
@@ -193,6 +194,32 @@ router.post(
|
|
|
193
194
|
})
|
|
194
195
|
);
|
|
195
196
|
|
|
197
|
+
router.post(
|
|
198
|
+
"/share-handler",
|
|
199
|
+
error_catcher(async (req, res) => {
|
|
200
|
+
const role = req.user?.role_id || 100;
|
|
201
|
+
if (role === 100) {
|
|
202
|
+
req.flash("error", req.__("You must be logged in to share"));
|
|
203
|
+
res.redirect("/auth/login");
|
|
204
|
+
} else if (!getState().getConfig("pwa_share_to_enabled", false)) {
|
|
205
|
+
req.flash("error", req.__("Sharing not enabled"));
|
|
206
|
+
res.redirect("/");
|
|
207
|
+
} else {
|
|
208
|
+
Trigger.emitEvent("ReceiveMobileShareData", null, req.user, {
|
|
209
|
+
row: req.body,
|
|
210
|
+
});
|
|
211
|
+
req.flash(
|
|
212
|
+
"success",
|
|
213
|
+
req.__(
|
|
214
|
+
"Shared: %s",
|
|
215
|
+
req.body.title || req.body.text || req.body.url || ""
|
|
216
|
+
)
|
|
217
|
+
);
|
|
218
|
+
res.status(303).redirect("/");
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
|
|
196
223
|
router.get(
|
|
197
224
|
"/manifest.json:opt_cache_bust?",
|
|
198
225
|
error_catcher(async (req, res) => {
|
|
@@ -205,6 +232,19 @@ router.get(
|
|
|
205
232
|
};
|
|
206
233
|
const site_logo = state.getConfig("site_logo_id");
|
|
207
234
|
const pwa_icons = state.getConfig("pwa_icons");
|
|
235
|
+
const pwa_share_to_enabled = state.getConfig("pwa_share_to_enabled", false);
|
|
236
|
+
if (pwa_share_to_enabled) {
|
|
237
|
+
manifest.share_target = {
|
|
238
|
+
action: "/notifications/share-handler",
|
|
239
|
+
method: "POST",
|
|
240
|
+
enctype: "multipart/form-data",
|
|
241
|
+
params: {
|
|
242
|
+
title: "title",
|
|
243
|
+
text: "text",
|
|
244
|
+
url: "url",
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
208
248
|
if (Array.isArray(pwa_icons) && pwa_icons.length > 0)
|
|
209
249
|
manifest.icons = pwa_icons.map(({ image, size, maskable }) => ({
|
|
210
250
|
src: `/files/serve/${image}`,
|
package/routes/page.js
CHANGED
package/routes/page_groupedit.js
CHANGED
|
@@ -678,7 +678,7 @@ router.post(
|
|
|
678
678
|
req.flash(
|
|
679
679
|
"success",
|
|
680
680
|
req.__(
|
|
681
|
-
"Page %s added to menu. Adjust access permissions in Settings » Menu",
|
|
681
|
+
"Page %s added to menu. Adjust access permissions in <a href=\"/menu\">Settings » Menu</a>",
|
|
682
682
|
group.name
|
|
683
683
|
)
|
|
684
684
|
);
|
package/routes/pageedit.js
CHANGED
|
@@ -229,6 +229,7 @@ const pageBuilderData = async (req, context) => {
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
//console.log(fixed_state_fields.ListTasks);
|
|
232
|
+
const icons = getState().icons;
|
|
232
233
|
return {
|
|
233
234
|
views: views.map((v) => v.select_option),
|
|
234
235
|
images,
|
|
@@ -244,6 +245,7 @@ const pageBuilderData = async (req, context) => {
|
|
|
244
245
|
page_id: context.id,
|
|
245
246
|
mode: "page",
|
|
246
247
|
roles,
|
|
248
|
+
icons,
|
|
247
249
|
fixed_state_fields,
|
|
248
250
|
next_button_label: "Done",
|
|
249
251
|
fonts: getState().fonts,
|
|
@@ -528,7 +530,10 @@ const getEditNormalPage = async (req, res, page) => {
|
|
|
528
530
|
version_tag: db.connectObj.version_tag,
|
|
529
531
|
};
|
|
530
532
|
res.sendWrap(
|
|
531
|
-
|
|
533
|
+
{
|
|
534
|
+
title: req.__(`%s configuration`, page.name),
|
|
535
|
+
requestFluidLayout: true,
|
|
536
|
+
},
|
|
532
537
|
wrap(renderBuilder(builderData, req.csrfToken()), true, req, page)
|
|
533
538
|
);
|
|
534
539
|
};
|
|
@@ -780,7 +785,7 @@ router.post(
|
|
|
780
785
|
req.flash(
|
|
781
786
|
"success",
|
|
782
787
|
req.__(
|
|
783
|
-
|
|
788
|
+
'Page %s added to menu. Adjust access permissions in <a href="/menu">Settings » Menu</a>',
|
|
784
789
|
page.name
|
|
785
790
|
)
|
|
786
791
|
);
|
package/routes/plugins.js
CHANGED
|
@@ -899,12 +899,13 @@ router.post(
|
|
|
899
899
|
};
|
|
900
900
|
await plugin.upsert();
|
|
901
901
|
await load_plugins.loadPlugin(plugin);
|
|
902
|
+
|
|
903
|
+
getState().processSend({
|
|
904
|
+
refresh_plugin_cfg: plugin.name,
|
|
905
|
+
tenant: db.getTenantSchema(),
|
|
906
|
+
});
|
|
907
|
+
res.json({ success: "ok" });
|
|
902
908
|
}
|
|
903
|
-
getState().processSend({
|
|
904
|
-
refresh_plugin_cfg: plugin.name,
|
|
905
|
-
tenant: db.getTenantSchema(),
|
|
906
|
-
});
|
|
907
|
-
res.json({ success: "ok" });
|
|
908
909
|
}
|
|
909
910
|
})
|
|
910
911
|
);
|
package/routes/registry.js
CHANGED
|
@@ -135,25 +135,70 @@ router.get(
|
|
|
135
135
|
},
|
|
136
136
|
],
|
|
137
137
|
});
|
|
138
|
+
let cfg_link = "";
|
|
138
139
|
switch (etype) {
|
|
139
140
|
case "table":
|
|
140
141
|
const tpack = await table_pack(
|
|
141
142
|
all_tables.find((t) => t.name === ename)
|
|
142
143
|
);
|
|
144
|
+
cfg_link = a(
|
|
145
|
+
{ href: `/table/${encodeURIComponent(ename)}` },
|
|
146
|
+
`${ename} ${etype}`
|
|
147
|
+
);
|
|
143
148
|
edContents = renderForm(mkForm(tpack), req.csrfToken());
|
|
144
149
|
break;
|
|
145
150
|
case "view":
|
|
151
|
+
cfg_link =
|
|
152
|
+
`${ename} ${etype}` +
|
|
153
|
+
a(
|
|
154
|
+
{
|
|
155
|
+
class: "ms-2",
|
|
156
|
+
href: `/viewedit/edit/${encodeURIComponent(ename)}`,
|
|
157
|
+
},
|
|
158
|
+
"Edit ",
|
|
159
|
+
i({ class: "fas fa-edit" })
|
|
160
|
+
) +
|
|
161
|
+
a(
|
|
162
|
+
{
|
|
163
|
+
class: "ms-1 me-3",
|
|
164
|
+
href: `/viewedit/config/${encodeURIComponent(ename)}`,
|
|
165
|
+
},
|
|
166
|
+
"Configure ",
|
|
167
|
+
i({ class: "fas fa-cog" })
|
|
168
|
+
);
|
|
146
169
|
const vpack = await view_pack(all_views.find((v) => v.name === ename));
|
|
147
170
|
edContents = renderForm(mkForm(vpack), req.csrfToken());
|
|
148
171
|
break;
|
|
149
172
|
case "page":
|
|
173
|
+
cfg_link = a(
|
|
174
|
+
{ href: `/pageedit/edit/${encodeURIComponent(ename)}` },
|
|
175
|
+
`${ename} ${etype}`
|
|
176
|
+
);
|
|
150
177
|
const ppack = await page_pack(all_pages.find((v) => v.name === ename));
|
|
151
178
|
edContents = renderForm(mkForm(ppack), req.csrfToken());
|
|
152
179
|
break;
|
|
153
180
|
case "trigger":
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
181
|
+
const trigger = all_triggers.find((t) => t.name === ename);
|
|
182
|
+
const trpack = await trigger_pack(trigger);
|
|
183
|
+
cfg_link =
|
|
184
|
+
`${ename} ${etype}` +
|
|
185
|
+
a(
|
|
186
|
+
{
|
|
187
|
+
class: "ms-2",
|
|
188
|
+
href: `/actions/edit/${trigger?.id}`,
|
|
189
|
+
},
|
|
190
|
+
"Edit ",
|
|
191
|
+
i({ class: "fas fa-edit" })
|
|
192
|
+
) +
|
|
193
|
+
a(
|
|
194
|
+
{
|
|
195
|
+
class: "ms-1 me-3",
|
|
196
|
+
href: `/actions/configure/${trigger?.id}`,
|
|
197
|
+
},
|
|
198
|
+
"Configure ",
|
|
199
|
+
i({ class: "fas fa-cog" })
|
|
200
|
+
);
|
|
201
|
+
|
|
157
202
|
edContents = renderForm(mkForm(trpack), req.csrfToken());
|
|
158
203
|
break;
|
|
159
204
|
}
|
|
@@ -243,10 +288,11 @@ router.get(
|
|
|
243
288
|
},
|
|
244
289
|
{
|
|
245
290
|
type: "card",
|
|
246
|
-
title:
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
291
|
+
title: cfg_link
|
|
292
|
+
? `Registry editor: ${cfg_link}`
|
|
293
|
+
: ename && etype
|
|
294
|
+
? `Registry editor: ${ename} ${etype}`
|
|
295
|
+
: "Registry editor",
|
|
250
296
|
contents: edContents,
|
|
251
297
|
},
|
|
252
298
|
],
|
package/routes/tables.js
CHANGED
|
@@ -926,7 +926,7 @@ router.get(
|
|
|
926
926
|
? `/useradmin/`
|
|
927
927
|
: fields.length === 1
|
|
928
928
|
? `javascript:;` // Fix problem with edition of table with only one column ID / Primary Key
|
|
929
|
-
: `/list/${table.name}`,
|
|
929
|
+
: `/list/${encodeURIComponent(table.name)}`,
|
|
930
930
|
},
|
|
931
931
|
i({ class: "fas fa-2x fa-edit" }),
|
|
932
932
|
"<br/>",
|
|
@@ -949,7 +949,7 @@ router.get(
|
|
|
949
949
|
div(
|
|
950
950
|
{ class: "mx-auto" },
|
|
951
951
|
a(
|
|
952
|
-
{ href: `/table/download/${table.name}` },
|
|
952
|
+
{ href: `/table/download/${encodeURIComponent(table.name)}` },
|
|
953
953
|
i({ class: "fas fa-2x fa-download" }),
|
|
954
954
|
"<br/>",
|
|
955
955
|
req.__("Download CSV")
|
|
@@ -1014,13 +1014,13 @@ router.get(
|
|
|
1014
1014
|
'<i class="fas fa-edit"></i> ' + req.__("Rename table")
|
|
1015
1015
|
),
|
|
1016
1016
|
post_dropdown_item(
|
|
1017
|
-
`/table/recalc-stored/${table.name}`,
|
|
1017
|
+
`/table/recalc-stored/${encodeURIComponent(table.name)}`,
|
|
1018
1018
|
'<i class="fas fa-sync"></i> ' +
|
|
1019
1019
|
req.__("Recalculate stored fields"),
|
|
1020
1020
|
req
|
|
1021
1021
|
),
|
|
1022
1022
|
post_dropdown_item(
|
|
1023
|
-
`/table/delete-all-rows/${table.name}`,
|
|
1023
|
+
`/table/delete-all-rows/${encodeURIComponent(table.name)}`,
|
|
1024
1024
|
'<i class="far fa-trash-alt"></i> ' +
|
|
1025
1025
|
req.__("Delete all rows"),
|
|
1026
1026
|
req,
|
|
@@ -1111,6 +1111,7 @@ router.post(
|
|
|
1111
1111
|
const v = req.body;
|
|
1112
1112
|
if (typeof v.id === "undefined" && typeof v.external === "undefined") {
|
|
1113
1113
|
// insert
|
|
1114
|
+
v.name = v.name.trim()
|
|
1114
1115
|
const { name, ...rest } = v;
|
|
1115
1116
|
const alltables = await Table.find({});
|
|
1116
1117
|
const existing_tables = [
|
package/routes/utils.js
CHANGED
|
@@ -312,14 +312,17 @@ const getSessionStore = (pruneInterval) => {
|
|
|
312
312
|
maxAge: 30 * 24 * 60 * 60 * 1000,
|
|
313
313
|
sameSite: "strict",
|
|
314
314
|
});
|
|
315
|
-
} else*/
|
|
315
|
+
} else*/
|
|
316
|
+
let sameSite = getState().getConfig("cookie_samesite", "None").toLowerCase();
|
|
317
|
+
if (sameSite === "unset") sameSite = undefined;
|
|
318
|
+
if (db.isSQLite) {
|
|
316
319
|
var SQLiteStore = require("connect-sqlite3")(session);
|
|
317
320
|
return session({
|
|
318
321
|
store: new SQLiteStore({ db: "sessions.sqlite" }),
|
|
319
322
|
secret: db.connectObj.session_secret || is.str.generate(),
|
|
320
323
|
resave: false,
|
|
321
324
|
saveUninitialized: false,
|
|
322
|
-
cookie: { maxAge: 30 * 24 * 60 * 60 * 1000, sameSite
|
|
325
|
+
cookie: { maxAge: 30 * 24 * 60 * 60 * 1000, sameSite }, // 30 days
|
|
323
326
|
});
|
|
324
327
|
} else {
|
|
325
328
|
const pgSession = require("connect-pg-simple")(session);
|
|
@@ -333,7 +336,7 @@ const getSessionStore = (pruneInterval) => {
|
|
|
333
336
|
secret: db.connectObj.session_secret || is.str.generate(),
|
|
334
337
|
resave: false,
|
|
335
338
|
saveUninitialized: false,
|
|
336
|
-
cookie: { maxAge: 30 * 24 * 60 * 60 * 1000 }, // 30 days
|
|
339
|
+
cookie: { maxAge: 30 * 24 * 60 * 60 * 1000, sameSite }, // 30 days
|
|
337
340
|
});
|
|
338
341
|
}
|
|
339
342
|
};
|
package/routes/viewedit.js
CHANGED
|
@@ -286,7 +286,7 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
286
286
|
parent_field: "attributes",
|
|
287
287
|
attributes: {
|
|
288
288
|
inline: true,
|
|
289
|
-
options: ["px", "%", "vw", "em", "rem"],
|
|
289
|
+
options: ["px", "%", "vw", "em", "rem", "cm"],
|
|
290
290
|
},
|
|
291
291
|
},
|
|
292
292
|
{
|
|
@@ -306,7 +306,7 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
306
306
|
parent_field: "attributes",
|
|
307
307
|
attributes: {
|
|
308
308
|
inline: true,
|
|
309
|
-
options: ["px", "%", "vw", "em", "rem"],
|
|
309
|
+
options: ["px", "%", "vw", "em", "rem", "cm"],
|
|
310
310
|
},
|
|
311
311
|
},
|
|
312
312
|
{
|
|
@@ -586,7 +586,10 @@ const respondWorkflow = (view, wf, wfres, req, res, table) => {
|
|
|
586
586
|
text: view.name,
|
|
587
587
|
postLinkText: `[${view.viewtemplate}${
|
|
588
588
|
table
|
|
589
|
-
? ` on ${a(
|
|
589
|
+
? ` on ${a(
|
|
590
|
+
{ href: `/table/` + encodeURIComponent(table.name) },
|
|
591
|
+
table.name
|
|
592
|
+
)}`
|
|
590
593
|
: ""
|
|
591
594
|
}]`,
|
|
592
595
|
},
|
|
@@ -761,7 +764,7 @@ router.post(
|
|
|
761
764
|
req.flash(
|
|
762
765
|
"success",
|
|
763
766
|
req.__(
|
|
764
|
-
|
|
767
|
+
'View %s added to menu. Adjust access permissions in <a href="/menu">Settings » Menu</a>',
|
|
765
768
|
view.name
|
|
766
769
|
)
|
|
767
770
|
);
|
package/tests/api.test.js
CHANGED
|
@@ -3,9 +3,12 @@ const getApp = require("../app");
|
|
|
3
3
|
const Table = require("@saltcorn/data/models/table");
|
|
4
4
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
5
5
|
const File = require("@saltcorn/data/models/file");
|
|
6
|
+
const Field = require("@saltcorn/data/models/field");
|
|
7
|
+
const User = require("@saltcorn/data/models/user");
|
|
8
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
9
|
+
|
|
6
10
|
const fs = require("fs").promises;
|
|
7
11
|
|
|
8
|
-
const Field = require("@saltcorn/data/models/field");
|
|
9
12
|
const {
|
|
10
13
|
getStaffLoginCookie,
|
|
11
14
|
getAdminLoginCookie,
|
|
@@ -14,10 +17,11 @@ const {
|
|
|
14
17
|
succeedJsonWith,
|
|
15
18
|
notAuthorized,
|
|
16
19
|
toRedirect,
|
|
20
|
+
toInclude,
|
|
17
21
|
succeedJsonWithWholeBody,
|
|
18
22
|
} = require("../auth/testhelp");
|
|
19
23
|
const db = require("@saltcorn/data/db");
|
|
20
|
-
const
|
|
24
|
+
const { sleep } = require("@saltcorn/data/tests/mocks");
|
|
21
25
|
|
|
22
26
|
beforeAll(async () => {
|
|
23
27
|
await resetToFixtures();
|
|
@@ -505,3 +509,85 @@ describe("API action", () => {
|
|
|
505
509
|
expect(counts.map((c) => c.thing)).toContain("no body");
|
|
506
510
|
});
|
|
507
511
|
});
|
|
512
|
+
|
|
513
|
+
describe("test share handler", () => {
|
|
514
|
+
beforeAll(async () => {
|
|
515
|
+
await getState().setConfig("pwa_share_to_enabled", true);
|
|
516
|
+
|
|
517
|
+
const sharedData = await Table.create("shared_data");
|
|
518
|
+
await Field.create({
|
|
519
|
+
table: sharedData,
|
|
520
|
+
name: "title",
|
|
521
|
+
label: "Title",
|
|
522
|
+
type: "String",
|
|
523
|
+
});
|
|
524
|
+
await Field.create({
|
|
525
|
+
table: sharedData,
|
|
526
|
+
name: "user",
|
|
527
|
+
label: "user",
|
|
528
|
+
type: "String",
|
|
529
|
+
});
|
|
530
|
+
await Trigger.create({
|
|
531
|
+
action: "run_js_code",
|
|
532
|
+
when_trigger: "ReceiveMobileShareData",
|
|
533
|
+
name: "my_receive_share",
|
|
534
|
+
min_role: 100,
|
|
535
|
+
configuration: {
|
|
536
|
+
code: `
|
|
537
|
+
const sharedData = Table.findOne({ name: "shared_data" });
|
|
538
|
+
await sharedData.insertRow({
|
|
539
|
+
title: row.title, user: JSON.stringify(user)
|
|
540
|
+
});`,
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it("shares as admin", async () => {
|
|
546
|
+
const app = await getApp({ disableCsrf: true });
|
|
547
|
+
const loginCookie = await getAdminLoginCookie();
|
|
548
|
+
await request(app)
|
|
549
|
+
.post("/notifications/share-handler")
|
|
550
|
+
.set("Cookie", loginCookie)
|
|
551
|
+
.send({ title: "share_as_admin" })
|
|
552
|
+
.expect(toRedirect("/"));
|
|
553
|
+
await sleep(1000);
|
|
554
|
+
const sharedData = Table.findOne({ name: "shared_data" });
|
|
555
|
+
const rows = await sharedData.getRows({});
|
|
556
|
+
const row = rows.find(
|
|
557
|
+
(r) =>
|
|
558
|
+
r.title === "share_as_admin" &&
|
|
559
|
+
r.user ===
|
|
560
|
+
'{"email":"admin@foo.com","id":1,"role_id":1,"language":null,"tenant":"public","attributes":{}}'
|
|
561
|
+
);
|
|
562
|
+
expect(row).toBeDefined();
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("pwa_disabled as admin", async () => {
|
|
566
|
+
const app = await getApp({ disableCsrf: true });
|
|
567
|
+
const loginCookie = await getAdminLoginCookie();
|
|
568
|
+
await getState().setConfig("pwa_share_to_enabled", false);
|
|
569
|
+
await request(app)
|
|
570
|
+
.post("/notifications/share-handler")
|
|
571
|
+
.set("Cookie", loginCookie)
|
|
572
|
+
.send({ title: "pwa_disabled_as_admin" })
|
|
573
|
+
.expect(toRedirect("/"));
|
|
574
|
+
await sleep(1000);
|
|
575
|
+
const sharedData = Table.findOne({ name: "shared_data" });
|
|
576
|
+
const rows = await sharedData.getRows({});
|
|
577
|
+
const row = rows.find((r) => r.title === "pwa_disabled_as_admin");
|
|
578
|
+
expect(row).toBeUndefined();
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it("does not share as public", async () => {
|
|
582
|
+
const app = await getApp({ disableCsrf: true });
|
|
583
|
+
await request(app)
|
|
584
|
+
.post("/notifications/share-handler")
|
|
585
|
+
.send({ title: "does_not_share_as_public" })
|
|
586
|
+
.expect(toRedirect("/auth/login"));
|
|
587
|
+
await sleep(1000);
|
|
588
|
+
const sharedData = Table.findOne({ name: "shared_data" });
|
|
589
|
+
const rows = await sharedData.getRows({});
|
|
590
|
+
const row = rows.find((r) => r.title === "does_not_share_as_public");
|
|
591
|
+
expect(row).toBeUndefined();
|
|
592
|
+
});
|
|
593
|
+
});
|
package/tests/auth.test.js
CHANGED
|
@@ -372,7 +372,7 @@ describe("User fields", () => {
|
|
|
372
372
|
],
|
|
373
373
|
},
|
|
374
374
|
},
|
|
375
|
-
min_role:
|
|
375
|
+
min_role: 100,
|
|
376
376
|
});
|
|
377
377
|
await getState().setConfig("new_user_form", "newuser");
|
|
378
378
|
});
|
|
@@ -508,7 +508,7 @@ describe("signup with custom login form", () => {
|
|
|
508
508
|
viewname: "loginform",
|
|
509
509
|
view_when_done: "publicissueboard",
|
|
510
510
|
},
|
|
511
|
-
min_role:
|
|
511
|
+
min_role: 100,
|
|
512
512
|
//default_render_page: "loginpage",
|
|
513
513
|
});
|
|
514
514
|
|
|
@@ -598,7 +598,7 @@ describe("signup with custom login form", () => {
|
|
|
598
598
|
viewname: "loginform",
|
|
599
599
|
view_when_done: "publicissueboard",
|
|
600
600
|
},
|
|
601
|
-
min_role:
|
|
601
|
+
min_role: 100,
|
|
602
602
|
//default_render_page: "signuppage",
|
|
603
603
|
});
|
|
604
604
|
await getState().setConfig("signup_form", "signupform");
|
|
@@ -161,7 +161,7 @@ describe("Stable versioning install", () => {
|
|
|
161
161
|
name: "@christianhugoch/empty_sc_test_plugin",
|
|
162
162
|
});
|
|
163
163
|
expect(dbPlugin).not.toBe(null);
|
|
164
|
-
expect(dbPlugin.version).toBe("0.0
|
|
164
|
+
expect(dbPlugin.version).toBe("0.1.0");
|
|
165
165
|
});
|
|
166
166
|
|
|
167
167
|
it("installs a fixed version", async () => {
|
|
@@ -178,7 +178,7 @@ describe("Stable versioning install", () => {
|
|
|
178
178
|
name: "@christianhugoch/empty_sc_test_plugin",
|
|
179
179
|
});
|
|
180
180
|
expect(dbPlugin).not.toBe(null);
|
|
181
|
-
expect(dbPlugin.version).toBe("0.0
|
|
181
|
+
expect(dbPlugin.version).toBe("0.1.0");
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
it("installs and downgrades a fixed version", async () => {
|
|
@@ -276,7 +276,7 @@ describe("Stable versioning upgrade", () => {
|
|
|
276
276
|
name: "@christianhugoch/empty_sc_test_plugin",
|
|
277
277
|
});
|
|
278
278
|
expect(newPlugin).not.toBe(null);
|
|
279
|
-
expect(newPlugin.version).toBe("0.0
|
|
279
|
+
expect(newPlugin.version).toBe("0.1.0");
|
|
280
280
|
});
|
|
281
281
|
|
|
282
282
|
it("upgrades to fixed version", async () => {
|
package/tests/plugins.test.js
CHANGED
|
@@ -236,7 +236,7 @@ describe("Pack Endpoints", () => {
|
|
|
236
236
|
.send(
|
|
237
237
|
"pack=les%22%3A+%5B%5D%2C+%22views%22%3A+%5B%5D%2C+%22plugins%22%3A+%5B%5D%2C+%22pages%22%3A+%5B%5D+%7D"
|
|
238
238
|
)
|
|
239
|
-
.expect(toInclude("Unexpected token
|
|
239
|
+
.expect(toInclude("Unexpected token"));
|
|
240
240
|
});
|
|
241
241
|
it("should install named", async () => {
|
|
242
242
|
const loginCookie = await getAdminLoginCookie();
|
|
@@ -404,7 +404,7 @@ describe("Upgrade plugin to supported version", () => {
|
|
|
404
404
|
const upgradedPlugin = await Plugin.findOne({
|
|
405
405
|
name: "@christianhugoch/empty_sc_test_plugin",
|
|
406
406
|
});
|
|
407
|
-
expect(upgradedPlugin.version).toBe("0.0
|
|
407
|
+
expect(upgradedPlugin.version).toBe("0.1.0");
|
|
408
408
|
});
|
|
409
409
|
|
|
410
410
|
it("upgrades with a downgrade of the most current fixed version", async () => {
|
|
@@ -427,7 +427,7 @@ describe("Upgrade plugin to supported version", () => {
|
|
|
427
427
|
const upgradedPlugin = await Plugin.findOne({
|
|
428
428
|
name: "@christianhugoch/empty_sc_test_plugin",
|
|
429
429
|
});
|
|
430
|
-
expect(upgradedPlugin.version).toBe("0.0
|
|
430
|
+
expect(upgradedPlugin.version).toBe("0.1.0");
|
|
431
431
|
});
|
|
432
432
|
});
|
|
433
433
|
|
|
@@ -452,7 +452,16 @@ describe("install a different version dialog", () => {
|
|
|
452
452
|
)}`
|
|
453
453
|
)
|
|
454
454
|
.set("Cookie", loginCookie)
|
|
455
|
-
.expect(
|
|
456
|
-
|
|
455
|
+
.expect(
|
|
456
|
+
toInclude([
|
|
457
|
+
"0.0.1",
|
|
458
|
+
"0.0.2",
|
|
459
|
+
"0.0.3",
|
|
460
|
+
"0.0.4",
|
|
461
|
+
"0.0.5",
|
|
462
|
+
"0.0.6",
|
|
463
|
+
"0.1.0",
|
|
464
|
+
])
|
|
465
|
+
);
|
|
457
466
|
});
|
|
458
467
|
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/*!========================================================================
|
|
2
|
-
* File: bootstrap-iconpicker.min.js v1.10.0 by @victor-valencia
|
|
3
|
-
* https://victor-valencia.github.com/bootstrap-iconpicker
|
|
4
|
-
* ========================================================================
|
|
5
|
-
* Copyright 2013-2018 Victor Valencia Rico.
|
|
6
|
-
* Licensed under MIT license.
|
|
7
|
-
* https://github.com/victor-valencia/bootstrap-iconpicker/blob/master/LICENSE
|
|
8
|
-
* ========================================================================
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
!function($){"use strict";var Iconpicker=function(element,options){if("undefined"==typeof $.fn.popover||"undefined"==typeof $.fn.popover.Constructor.VERSION)throw new TypeError("Bootstrap iconpicker require Bootstrap popover");this.$element=$(element),this.options=$.extend({},Iconpicker.DEFAULTS,this.$element.data()),this.options=$.extend({},this.options,options)};Iconpicker.VERSION="1.10.0",Iconpicker.ICONSET_EMPTY={iconClass:"",iconClassFix:"",icons:[]},Iconpicker.ICONSET={_custom:null,elusiveicon:$.iconset_elusiveicon||Iconpicker.ICONSET_EMPTY,flagicon:$.iconset_flagicon||Iconpicker.ICONSET_EMPTY,fontawesome4:$.iconset_fontawesome_4||Iconpicker.ICONSET_EMPTY,fontawesome5:$.iconset_fontawesome_5||Iconpicker.ICONSET_EMPTY,glyphicon:$.iconset_glyphicon||Iconpicker.ICONSET_EMPTY,ionicon:$.iconset_ionicon||Iconpicker.ICONSET_EMPTY,mapicon:$.iconset_mapicon||Iconpicker.ICONSET_EMPTY,materialdesign:$.iconset_materialdesign||Iconpicker.ICONSET_EMPTY,octicon:$.iconset_octicon||Iconpicker.ICONSET_EMPTY,typicon:$.iconset_typicon||Iconpicker.ICONSET_EMPTY,weathericon:$.iconset_weathericon||Iconpicker.ICONSET_EMPTY},Iconpicker.DEFAULTS={align:"center",arrowClass:"btn-primary",arrowNextIconClass:"fas fa-arrow-right",arrowPrevIconClass:"fas fa-arrow-left",cols:4,icon:"",iconset:"fontawesome5",iconsetVersion:"lastest",header:!0,labelHeader:"{0} / {1}",footer:!0,labelFooter:"{0} - {1} of {2}",placement:"bottom",rows:4,search:!0,searchText:"Search icon",selectedClass:"btn-warning",unselectedClass:"btn-secondary"},Iconpicker.prototype.bindEvents=function(){var op=this.options,el=this;op.table.find(".btn-previous, .btn-next").off("click").on("click",function(e){if(e.preventDefault(),!$(this).hasClass("disabled")){var inc=parseInt($(this).val(),10);el.changeList(op.page+inc)}}),op.table.find(".btn-icon").off("click").on("click",function(e){e.preventDefault(),el.select($(this).val()),op.inline===!1?el.$element.popover("3.x"===$.fn.bsVersion()?"destroy":"dispose"):op.table.find("i[class$='"+$(this).val()+"']").parent().addClass(op.selectedClass)}),op.table.find(".search-control").off("keyup").on("keyup",function(){el.changeList(1)})},Iconpicker.prototype.changeList=function(page){this.filterIcons(),this.updateLabels(page),this.updateIcons(page),this.options.page=page,this.bindEvents()},Iconpicker.prototype.filterIcons=function(){var op=this.options,search=op.table.find(".search-control").val(),icons=[];if("lastest"!=op.iconsetVersion&&"undefined"!=typeof Iconpicker.ICONSET[op.iconset].allVersions?$.each(Iconpicker.ICONSET[op.iconset].allVersions,function(i,v){op.iconsetVersion==v.version&&(icons=v.icons)}):icons=Iconpicker.ICONSET[op.iconset].icons,""===search)op.icons=icons;else{var result=[];$.each(icons,function(i,v){v.toLowerCase().indexOf(search)>-1&&result.push(v)}),op.icons=result}},Iconpicker.prototype.removeAddClass=function(target,remove,add){return this.options.table.find(target).removeClass(remove).addClass(add),add},Iconpicker.prototype.reset=function(){this.updatePicker(),this.changeList(1)},Iconpicker.prototype.select=function(icon){var op=this.options,el=this.$element;op.selected=$.inArray(icon.replace(op.iconClassFix,""),op.icons),-1===op.selected&&(op.selected=0,icon=op.iconClassFix+op.icons[op.selected]),""!==icon&&op.selected>=0&&(op.icon=icon,op.inline===!1&&(el.find("input").val(icon),el.find("i").attr("class","").addClass(op.iconClass).addClass(icon)),icon===op.iconClassFix?el.trigger({type:"change",icon:"empty"}):(el.trigger({type:"change",icon:icon}),el.find("input").val(icon)),op.table.find("button."+op.selectedClass).removeClass(op.selectedClass))},Iconpicker.prototype.switchPage=function(icon){var op=this.options;if(op.selected=$.inArray(icon.replace(op.iconClassFix,""),op.icons),op.selected>=0){var page=Math.ceil((op.selected+1)/this.totalIconsPerPage());this.changeList(page)}""===icon?op.table.find("i."+op.iconClassFix).parent().addClass(op.selectedClass):op.table.find("i."+icon).parent().addClass(op.selectedClass)},Iconpicker.prototype.totalPages=function(){return Math.ceil(this.totalIcons()/this.totalIconsPerPage())},Iconpicker.prototype.totalIcons=function(){return this.options.icons.length},Iconpicker.prototype.totalIconsPerPage=function(){return 0===this.options.rows?this.options.icons.length:this.options.cols*this.options.rows},Iconpicker.prototype.updateArrows=function(page){var op=this.options,total_pages=this.totalPages();1===page?op.table.find(".btn-previous").addClass("disabled"):op.table.find(".btn-previous").removeClass("disabled"),page===total_pages||0===total_pages?op.table.find(".btn-next").addClass("disabled"):op.table.find(".btn-next").removeClass("disabled")},Iconpicker.prototype.updateIcons=function(page){var op=this.options,tbody=op.table.find("tbody").empty(),offset=(page-1)*this.totalIconsPerPage(),length=op.rows;0===op.rows&&(length=op.icons.length);for(var i=0;length>i;i++){for(var tr=$("<tr></tr>"),j=0;j<op.cols;j++){var pos=offset+i*op.cols+j,btn=$('<button class="btn '+op.unselectedClass+' btn-icon"></button>').hide();if(pos<op.icons.length){var v=op.iconClassFix+op.icons[pos];btn.val(v).attr("title",v).append('<i class="'+op.iconClass+" "+v+'"></i>').show(),op.icon===v&&btn.addClass(op.selectedClass).addClass("btn-icon-selected")}tr.append($("<td></td>").append(btn))}tbody.append(tr)}},Iconpicker.prototype.updateIconsCount=function(){var op=this.options;if(op.footer===!0){var icons_count=["<tr>",' <td colspan="'+op.cols+'" class="text-center">',' <span class="icons-count"></span>'," </td>","</tr>"];op.table.find("tfoot").empty().append(icons_count.join(""))}},Iconpicker.prototype.updateLabels=function(page){var op=this.options,total_icons=this.totalIcons(),total_pages=this.totalPages();op.table.find(".page-count").html(op.labelHeader.replace("{0}",0===total_pages?0:page).replace("{1}",total_pages));var offset=(page-1)*this.totalIconsPerPage(),total=page*this.totalIconsPerPage();op.table.find(".icons-count").html(op.labelFooter.replace("{0}",total_icons?offset+1:0).replace("{1}",total_icons>total?total:total_icons).replace("{2}",total_icons)),this.updateArrows(page)},Iconpicker.prototype.updatePagesCount=function(){var op=this.options;if(op.header===!0){for(var tr=$("<tr></tr>"),i=0;i<op.cols;i++){var td=$('<td class="text-center"></td>');if(0===i||i===op.cols-1){var arrow=['<button class="btn btn-arrow '+(0===i?"btn-previous":"btn-next")+" "+op.arrowClass+'" value="'+(0===i?-1:1)+'">','<span class="'+(0===i?op.arrowPrevIconClass:op.arrowNextIconClass)+'"></span>',"</button>"];td.append(arrow.join("")),tr.append(td)}else 0===tr.find(".page-count").length&&(td.attr("colspan",op.cols-2).append('<span class="page-count"></span>'),tr.append(td))}op.table.find("thead").empty().append(tr)}},Iconpicker.prototype.updatePicker=function(){var op=this.options;if(op.cols<4)throw"Iconpicker => The number of columns must be greater than or equal to 4. [option.cols = "+op.cols+"]";if(op.rows<0)throw"Iconpicker => The number of rows must be greater than or equal to 0. [option.rows = "+op.rows+"]";this.updatePagesCount(),this.updateSearch(),this.updateIconsCount()},Iconpicker.prototype.updateSearch=function(){var op=this.options,search=["<tr>",' <td colspan="'+op.cols+'">',' <input type="text" class="form-control search-control" style="width: '+op.cols*("3.x"===$.fn.bsVersion()?39:41)+'px;" placeholder="'+op.searchText+'">'," </td>","</tr>"];search=$(search.join("")),op.search===!0?search.show():search.hide(),op.table.find("thead").append(search)},Iconpicker.prototype.setAlign=function(value){this.$element.removeClass(this.options.align).addClass(value),this.options.align=value},Iconpicker.prototype.setArrowClass=function(value){this.options.arrowClass=this.removeAddClass(".btn-arrow",this.options.arrowClass,value)},Iconpicker.prototype.setArrowNextIconClass=function(value){this.options.arrowNextIconClass=this.removeAddClass(".btn-next > span",this.options.arrowNextIconClass,value)},Iconpicker.prototype.setArrowPrevIconClass=function(value){this.options.arrowPrevIconClass=this.removeAddClass(".btn-previous > span",this.options.arrowPrevIconClass,value)},Iconpicker.prototype.setCols=function(value){this.options.cols=value,this.reset()},Iconpicker.prototype.setFooter=function(value){var footer=this.options.table.find("tfoot");value===!0?footer.show():footer.hide(),this.options.footer=value},Iconpicker.prototype.setHeader=function(value){var header=this.options.table.find("thead");value===!0?header.show():header.hide(),this.options.header=value},Iconpicker.prototype.setIcon=function(value){this.select(value)},Iconpicker.prototype.setIconset=function(value){var op=this.options;$.isPlainObject(value)?(Iconpicker.ICONSET._custom=$.extend(Iconpicker.ICONSET_EMPTY,value),op.iconset="_custom"):Iconpicker.ICONSET.hasOwnProperty(value)?op.iconset=value:op.iconset=Iconpicker.DEFAULTS.iconset,op=$.extend(op,Iconpicker.ICONSET[op.iconset]),this.reset(),this.select(op.icon)},Iconpicker.prototype.setLabelHeader=function(value){this.options.labelHeader=value,this.updateLabels(this.options.page)},Iconpicker.prototype.setLabelFooter=function(value){this.options.labelFooter=value,this.updateLabels(this.options.page)},Iconpicker.prototype.setPlacement=function(value){this.options.placement=value},Iconpicker.prototype.setRows=function(value){this.options.rows=value,this.reset()},Iconpicker.prototype.setSearch=function(value){var search=this.options.table.find(".search-control");value===!0?search.show():search.hide(),search.val(""),this.changeList(1),this.options.search=value},Iconpicker.prototype.setSearchText=function(value){this.options.table.find(".search-control").attr("placeholder",value),this.options.searchText=value},Iconpicker.prototype.setSelectedClass=function(value){this.options.selectedClass=this.removeAddClass(".btn-icon-selected",this.options.selectedClass,value)},Iconpicker.prototype.setUnselectedClass=function(value){this.options.unselectedClass=this.removeAddClass(".btn-icon",this.options.unselectedClass,value)};var old=$.fn.iconpicker;$.fn.iconpicker=function(option,params){return this.each(function(){var $this=$(this),data=$this.data("bs.iconpicker"),options="object"==typeof option&&option;if(data||$this.data("bs.iconpicker",data=new Iconpicker(this,options)),"string"==typeof option){if("undefined"==typeof data[option])throw'Iconpicker => The "'+option+'" method does not exists.';data[option](params)}else{var op=data.options;op=$.extend(op,{inline:!1,page:1,selected:-1,table:$('<table class="table-icons"><thead></thead><tbody></tbody><tfoot></tfoot></table>')});var name="undefined"!=typeof $this.attr("name")?'name="'+$this.attr("name")+'"':"";"BUTTON"===$this.prop("tagName")?($this.empty().append("<i></i>").append('<input type="hidden" '+name+"></input>").append('<span class="caret"></span>').addClass("iconpicker "+("3.x"===$.fn.bsVersion()?"":"dropdown-toggle")),data.setIconset(op.iconset),$this.on("click",function(e){e.preventDefault(),$this.popover({animation:!1,trigger:"manual",html:!0,content:op.table,container:"body",placement:op.placement}).on("inserted.bs.popover",function(){var el=$this.data("bs.popover"),tip="3.x"===$.fn.bsVersion()?el.tip():$(el.getTipElement());tip.addClass("iconpicker-popover")}).on("shown.bs.popover",function(){data.switchPage(op.icon),data.bindEvents()}),$this.popover("show")})):(op.inline=!0,data.setIconset(op.iconset),$this.empty().append('<input type="hidden" '+name+"></input>").append(op.table).addClass("iconpicker").addClass(op.align),data.switchPage(op.icon),data.bindEvents())}})},$.fn.iconpicker.Constructor=Iconpicker,$.fn.iconpicker.noConflict=function(){return $.fn.iconpicker=old,this},$.fn.bsVersion=function(){return $.fn.popover.Constructor.VERSION.substr(0,2)+"x"},$(document).on("click","body",function(e){$(".iconpicker").each(function(){$(this).is(e.target)||0!==$(this).has(e.target).length||0!==$(".popover").has(e.target).length||$(this).popover("3.x"===$.fn.bsVersion()?"destroy":"dispose")})}),$('button[role="iconpicker"],div[role="iconpicker"]').iconpicker()}(jQuery);
|