@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 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 [plugin, routes] of Object.entries(state.plugin_routes)) {
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 [plugin, routes] of Object.entries(pluginRoutes)) {
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: false,
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
- "new_user_form",
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
- //"cookie_sessions",
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({}, req, res);
310
- if (login_form.default_render_page) {
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 (signup_form.default_render_page) {
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 &raquo; Menu</a>": "View %s added to menu. Adjust access permissions in <a href=\"/menu\">Settings &raquo; Menu</a>",
1482
+ "Page %s added to menu. Adjust access permissions in <a href=\"/menu\">Settings &raquo; Menu</a>": "Page %s added to menu. Adjust access permissions in <a href=\"/menu\">Settings &raquo; 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 &raquo; Menu</a>": "Widok %s został dodany do menu. Dostosuj uprawnienia dostępu w <a href=\"/menu\">Ustawienia &raquo; Menu</a>",
1483
+ "Page %s added to menu. Adjust access permissions in <a href=\"/menu\">Settings &raquo; Menu</a>": "Strona %s została dodana do menu. Dostosuj uprawnienia dostępu w <a href=\"/menu\">Ustawienia &raquo; 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": "Фиксированное состояние для вида %s ",
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": "Auto public login",
1111
+ "Auto public login": "Авто логин для анонима (public)",
1112
1112
  "Table Synchronization": "Синхронизация таблиц",
1113
1113
  "unsynched": "не синхронно",
1114
1114
  "synched": "синхронно",
1115
- "exclude": "exclude",
1116
- "include": "include",
1117
- "Cordova builder": "Cordova builder",
1118
- "not available": "не доступно",
1115
+ "exclude": "исключить(я)",
1116
+ "include": "включить(я)",
1117
+ "Cordova builder": "Конструктор Cordova",
1118
+ "not available": "не доступно(ен)",
1119
1119
  "pull": "получить",
1120
1120
  "refresh": "обновить",
1121
- "Missing screen info": "Пропущенный(е) 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",
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 screen infos",
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', User-Agent отображается на устройство с следующими значениями.",
1127
- "Add screen info": "Добавить 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": "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
@@ -420,6 +420,7 @@ const config_fields_form = async ({
420
420
  fields.push({
421
421
  input_type: "section_header",
422
422
  label: req.__(name0.section_header),
423
+ sublabel: name0.sublabel ? req.__(name0.sublabel) : undefined,
423
424
  });
424
425
  continue;
425
426
  }
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "1.0.0-rc.9",
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.0.0-rc.9",
11
- "@saltcorn/builder": "1.0.0-rc.9",
12
- "@saltcorn/data": "1.0.0-rc.9",
13
- "@saltcorn/admin-models": "1.0.0-rc.9",
14
- "@saltcorn/filemanager": "1.0.0-rc.9",
15
- "@saltcorn/markup": "1.0.0-rc.9",
16
- "@saltcorn/plugins-loader": "1.0.0-rc.9",
17
- "@saltcorn/sbadmin2": "1.0.0-rc.9",
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: 999; ${
1355
+ style="min-width: 350px; max-width: 50vw; width: auto; z-index: 9999; ${
1356
1356
  !isNode ? "transform: translateX(-50%);" : ""
1357
1357
  }"
1358
1358
  >
@@ -607,3 +607,7 @@ button.monospace-copy-btn {
607
607
  .mobile-thumbnail-container img {
608
608
  display: block;
609
609
  }
610
+
611
+ i[class^="unicode-"], i[class*=" unicode-"] {
612
+ font-style: normal;
613
+ }
package/routes/actions.js CHANGED
@@ -564,10 +564,17 @@ router.get(
564
564
 
565
565
  const subtitle =
566
566
  span(
567
- { class: "ms-3" },
567
+ { class: "ms-2" },
568
568
  trigger.action,
569
+ "&nbsp;",
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
+ '&nbsp;<i class="fas fa-edit"></i>'
577
+ ) +
571
578
  a(
572
579
  { href: `/actions/testrun/${id}`, class: "ms-2" },
573
580
  req.__("Test run") + "&nbsp;&raquo;"
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
- { href: `/table/${table.id || table.name}`, text: table.name },
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 { script, domReady, div, ul, i } = require("@saltcorn/markup/tags");
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: static_pre + "/iconset-fontawesome5-3-1.min.js",
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
+ );
@@ -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
@@ -200,6 +200,7 @@ router.post(
200
200
  });
201
201
  res.json({ success: "ok", ...(result || {}) });
202
202
  } catch (e) {
203
+ getState().log(2, e?.stack)
203
204
  res.status(400).json({ error: e.message || e });
204
205
  }
205
206
  } else res.status(404).json({ error: "Action not found" });
@@ -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 &raquo; Menu",
681
+ "Page %s added to menu. Adjust access permissions in <a href=\"/menu\">Settings &raquo; Menu</a>",
682
682
  group.name
683
683
  )
684
684
  );
@@ -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
- req.__(`%s configuration`, page.name),
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
- "Page %s added to menu. Adjust access permissions in Settings &raquo; Menu",
788
+ 'Page %s added to menu. Adjust access permissions in <a href="/menu">Settings &raquo; 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
  );
@@ -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&nbsp;",
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&nbsp;",
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 trpack = await trigger_pack(
155
- all_triggers.find((t) => t.name === ename)
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&nbsp;",
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&nbsp;",
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
- ename && etype
248
- ? `Registry editor: ${ename} ${etype}`
249
- : "Registry editor",
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>&nbsp;' + 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>&nbsp;' +
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>&nbsp;' +
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*/ if (db.isSQLite) {
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: "strict" }, // 30 days
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
  };
@@ -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({ href: `/table/` + table.name }, table.name)}`
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
- "View %s added to menu. Adjust access permissions in Settings &raquo; Menu",
767
+ 'View %s added to menu. Adjust access permissions in <a href="/menu">Settings &raquo; 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 User = require("@saltcorn/data/models/user");
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
+ });
@@ -372,7 +372,7 @@ describe("User fields", () => {
372
372
  ],
373
373
  },
374
374
  },
375
- min_role: 1,
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: 1,
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: 1,
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.6");
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.6");
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.6");
279
+ expect(newPlugin.version).toBe("0.1.0");
280
280
  });
281
281
 
282
282
  it("upgrades to fixed version", async () => {
@@ -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 l in JSON at position 0"));
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.6");
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.6");
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(toInclude(["0.0.1", "0.0.2", "0.0.3", "0.0.4", "0.0.5", "0.0.6"]))
456
- .expect(toNotInclude("0.1.0"));
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);