@saltcorn/mobile-builder 1.6.0-rc.2 → 1.6.0-rc.4

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.
@@ -1,160 +0,0 @@
1
- /*
2
- * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
3
- * This devtool is neither made for production nor for readable output files.
4
- * It uses "eval()" calls to create a separate source file in the browser devtools.
5
- * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
6
- * or disable the default devtool with "devtool: false".
7
- * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
8
- */
9
- (function webpackUniversalModuleDefinition(root, factory) {
10
- if(typeof exports === 'object' && typeof module === 'object')
11
- module.exports = factory();
12
- else if(typeof define === 'function' && define.amd)
13
- define([], factory);
14
- else if(typeof exports === 'object')
15
- exports["saltcorn"] = factory();
16
- else
17
- root["saltcorn"] = root["saltcorn"] || {}, root["saltcorn"]["base_plugin"] = factory();
18
- })(self, () => {
19
- return (self["webpackChunksaltcorn"] = self["webpackChunksaltcorn"] || []).push([["base_plugin"],{
20
-
21
- /***/ "../saltcorn-base-plugin/index.js"
22
- /*!****************************************!*\
23
- !*** ../saltcorn-base-plugin/index.js ***!
24
- \****************************************/
25
- (module, __unused_webpack_exports, __webpack_require__) {
26
-
27
- eval("{/**\n * @category saltcorn-base-plugin\n * @module saltcorn-base-plugin/index\n */\n\n/** \n * @type {base-plugin/index}\n */\nconst plugin = __webpack_require__(/*! @saltcorn/data/base-plugin */ \"../saltcorn-data/dist/base-plugin/index.js\");\nmodule.exports = plugin;\n\n\n//# sourceURL=webpack://saltcorn/../saltcorn-base-plugin/index.js?\n}");
28
-
29
- /***/ },
30
-
31
- /***/ "../saltcorn-data/dist/base-plugin/actions.js"
32
- /*!****************************************************!*\
33
- !*** ../saltcorn-data/dist/base-plugin/actions.js ***!
34
- \****************************************************/
35
- (module, __unused_webpack_exports, __webpack_require__) {
36
-
37
- "use strict";
38
- eval("{/* provided dependency */ var Buffer = __webpack_require__(/*! buffer */ \"../../node_modules/buffer/index.js\")[\"Buffer\"];\n\n/**\n * Action description\n * @category saltcorn-data\n * @module base-plugin/actions\n * @subcategory base-plugin\n */\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nconst fetch = __webpack_require__(/*! node-fetch */ \"../../node_modules/node-fetch/browser.js\");\nconst { VM } = __webpack_require__(/*! vm2 */ \"../saltcorn-data/dist/mobile-mocks/npm/vm2.js\");\nconst oldVm = __webpack_require__(/*! vm */ \"../saltcorn-data/dist/mobile-mocks/node/vm.js\");\nconst table_1 = __importDefault(__webpack_require__(/*! ../models/table */ \"../saltcorn-data/dist/models/table.js\"));\nconst eventlog_1 = __importDefault(__webpack_require__(/*! ../models/eventlog */ \"../saltcorn-data/dist/models/eventlog.js\"));\nconst view_1 = __importDefault(__webpack_require__(/*! ../models/view */ \"../saltcorn-data/dist/models/view.js\"));\nconst model_1 = __importDefault(__webpack_require__(/*! ../models/model */ \"../saltcorn-data/dist/models/model.js\"));\nconst page_1 = __importDefault(__webpack_require__(/*! ../models/page */ \"../saltcorn-data/dist/models/page.js\"));\nconst field_1 = __importDefault(__webpack_require__(/*! ../models/field */ \"../saltcorn-data/dist/models/field.js\"));\nconst user_1 = __importDefault(__webpack_require__(/*! ../models/user */ \"../saltcorn-data/dist/models/user.js\"));\nconst trigger_1 = __importDefault(__webpack_require__(/*! ../models/trigger */ \"../saltcorn-data/dist/models/trigger.js\"));\nconst workflow_run_1 = __importDefault(__webpack_require__(/*! ../models/workflow_run */ \"../saltcorn-data/dist/models/workflow_run.js\"));\nconst notification_1 = __importDefault(__webpack_require__(/*! ../models/notification */ \"../saltcorn-data/dist/models/notification.js\"));\nconst file_1 = __importDefault(__webpack_require__(/*! ../models/file */ \"../saltcorn-data/dist/models/file.js\"));\nconst state_1 = __importDefault(__webpack_require__(/*! ../db/state */ \"../saltcorn-data/dist/db/state.js\"));\nconst { getState } = state_1.default;\nconst { getMailTransport, viewToEmailHtml, loadAttachments, getFileAggregations, mjml2html, } = __webpack_require__(/*! ../models/email */ \"../saltcorn-data/dist/mobile-mocks/models/email.js\");\nconst { get_async_expression_function, recalculate_for_stored, eval_expression, freeVariablesInInterpolation, add_free_variables_to_joinfields, freeVariables, } = __webpack_require__(/*! ../models/expression */ \"../saltcorn-data/dist/models/expression.js\");\nconst { div, code, a, span } = __webpack_require__(/*! @saltcorn/markup/tags */ \"../saltcorn-markup/dist/tags.js\");\nconst { sleep, getSessionId, urlStringToObject, dollarizeObject, objectToQueryString, interpolate, comparingCaseInsensitive, mergeActionResults, } = __webpack_require__(/*! ../utils */ \"../saltcorn-data/dist/utils.js\");\nconst db = __webpack_require__(/*! ../db */ \"../saltcorn-data/dist/db/index.js\");\nconst { isNode, isWeb, ppVal, getFetchProxyOptions } = __webpack_require__(/*! ../utils */ \"../saltcorn-data/dist/utils.js\");\nconst { available_languages } = __webpack_require__(/*! ../models/config */ \"../saltcorn-data/dist/models/config.js\");\nconst MetaData = __webpack_require__(/*! ../models/metadata */ \"../saltcorn-data/dist/models/metadata.js\");\nconst assert_1 = __webpack_require__(/*! assert */ \"../saltcorn-data/dist/mobile-mocks/node/assert.js\");\n//action use cases: field modify, like/rate (insert join), notify, send row to webhook\n// todo add translation\nconst consoleInterceptor = (state) => {\n const handle = (printer, level, message, optionalParams) => {\n printer(message, ...optionalParams);\n if (state.hasJoinedLogSockets && state.logLevel >= level) {\n const s = ppVal(message);\n state.emitLog(state.tenant || \"public\", level, optionalParams.length === 0\n ? s\n : `${s} ${optionalParams.map((val) => ppVal(val)).join(\" \")}`);\n }\n };\n return {\n log: (message, ...optionalParams) => handle(console.log, 5, message, optionalParams),\n info: (message, ...optionalParams) => handle(console.info, 5, message, optionalParams),\n debug: (message, ...optionalParams) => handle(console.debug, 5, message, optionalParams),\n warn: (message, ...optionalParams) => handle(console.warn, 2, message, optionalParams),\n error: (message, ...optionalParams) => handle(console.error, 1, message, optionalParams),\n };\n};\nconst emit_to_client = (user) => (data, userIds) => {\n const state = getState();\n const enabled = getState().getConfig(\"enable_dynamic_updates\", true);\n if (!enabled) {\n state.log(5, \"emit_to_client called, but dynamic updates are disabled\");\n return;\n }\n const safeIds = Array.isArray(userIds)\n ? userIds\n : userIds === null\n ? null\n : userIds\n ? [userIds]\n : user?.id\n ? [user.id]\n : [];\n state.emitDynamicUpdate(db.getTenantSchema(), data, safeIds);\n};\n/**\n * @param opts\n * @param opts.row\n * @param opts.table\n * @param opts.channel\n * @param opts.configuration\n * @param opts.user\n * @param opts.rest\n * @returns\n */\nconst run_code = async ({ row, table, channel, configuration, user, ...rest }) => {\n let stripTypes = (s) => s;\n let code;\n try {\n const { stripTypeScriptTypes } = __webpack_require__(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module 'module'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()));\n if (stripTypeScriptTypes)\n stripTypes = stripTypeScriptTypes;\n code = stripTypes(`async () =>{${configuration.code}\n}`)\n .replace(\"async () =>{\", \"\")\n .slice(0, -2);\n }\n catch (e) {\n //console.error(\"strip error\", e);\n code = configuration.code;\n }\n const run_where = configuration.run_where;\n if (run_where === \"Client page\")\n return {\n eval_js: code,\n row,\n field_names: table ? table.fields.map((f) => f.name) : undefined,\n };\n if (!isNode()) {\n const { isOfflineMode } = getState().mobileConfig || {};\n if (!isOfflineMode && run_where === \"Server\") {\n // stop on the app and run the action server side\n return { server_eval: true };\n }\n }\n const Actions = {};\n Object.entries(getState().actions).forEach(([k, v]) => {\n Actions[k] = (args = {}) => {\n return v.run({ row, table, user, configuration: args, ...rest, ...args });\n };\n });\n const trigger_actions = await trigger_1.default.find({\n when_trigger: { or: [\"API call\", \"Never\"] },\n });\n for (const trigger of trigger_actions) {\n Actions[trigger.name] = (args = {}) => {\n if (trigger.action === \"Workflow\") {\n return trigger.runWithoutRow({\n row,\n table: trigger.table_id\n ? table_1.default.findOne({ id: trigger.table_id }) || table\n : table,\n user,\n trigger_id: trigger.id || undefined,\n interactive: false,\n ...rest,\n ...args,\n });\n }\n else {\n const state_action = getState().actions[trigger.action];\n return state_action.run({\n row,\n table: trigger.table_id\n ? table_1.default.findOne({ id: trigger.table_id }) || table\n : table,\n configuration: trigger.configuration,\n trigger_id: trigger.id || undefined,\n user,\n ...rest,\n ...args,\n });\n }\n };\n }\n const run_js_code = async ({ code, ...restArgs }) => {\n return await run_code({\n row,\n table,\n channel,\n configuration: { code, run_where },\n user,\n ...rest,\n ...restArgs,\n });\n };\n const emitEvent = (eventType, channel, payload) => trigger_1.default.emitEvent(eventType, channel, user, payload);\n const fetchJSON = async (...args) => await (await fetch(...args)).json();\n const sysState = getState();\n const refreshSystemCache = async (which) => {\n //this worker\n if (which)\n await sysState[`refresh_${which}`](true);\n else\n await sysState.refresh(true);\n //other workers\n db.whenTransactionisFree(async () => {\n if (which) {\n const state = getState();\n await state[`refresh_${which}`]();\n }\n else\n await getState().refresh(false);\n });\n };\n const sandbox = {\n Table: table_1.default,\n table,\n row,\n context: row,\n user,\n console: consoleInterceptor(getState()),\n Actions,\n MetaData,\n emitEvent,\n sleep,\n fetchJSON,\n fetch,\n emit_to_client: emit_to_client(user),\n run_js_code,\n ...(isNode() ? { assert: assert_1.strict } : {}),\n tryCatchInTransaction: db.tryCatchInTransaction,\n commitAndBeginNewTransaction: db.commitAndBeginNewTransaction,\n commitBeginNewTransactionAndRefreshCache: async () => {\n await db.commitAndBeginNewTransaction();\n await sysState.refresh();\n },\n URL,\n File: file_1.default,\n User: user_1.default,\n View: view_1.default,\n Page: page_1.default,\n Field: field_1.default,\n EventLog: eventlog_1.default,\n Buffer: isNode() ? Buffer : __webpack_require__(/*! buffer */ \"../../node_modules/buffer/index.js\"),\n Trigger: trigger_1.default,\n Notification: notification_1.default,\n WorkflowRun: workflow_run_1.default,\n setTimeout,\n interpolate,\n require: (nm) => sysState.codeNPMmodules[nm],\n refreshSystemCache,\n setConfig: (k, v) => sysState.isFixedConfig(k) ? undefined : sysState.setConfig(k, v),\n getConfig: (k) => sysState.isFixedConfig(k) ? undefined : sysState.getConfig(k),\n channel: table ? table.name : channel,\n session_id: rest.req && getSessionId(rest.req),\n request_headers: rest?.req?.headers,\n page_load_tag: rest?.req?.headers?.[\"page-load-tag\"],\n request_ip: rest?.req?.ip,\n ...(row || {}),\n ...getState().eval_context,\n ...rest,\n };\n if (isNode()) {\n const vm2 = new VM({ sandbox, eval: false, wasm: false });\n const result = await vm2.run(`(async () => {${code}\\n})()`);\n if (result === null || result === undefined || typeof result !== \"object\")\n return result;\n try {\n return JSON.parse(JSON.stringify(result));\n }\n catch {\n return result;\n }\n }\n else {\n // on mobile, vm2 is not available\n const f = oldVm.runInNewContext(`async () => {${code}\\n}`, sandbox);\n return await f();\n }\n};\nmodule.exports = {\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n blocks: {\n disableInBuilder: true,\n disableInList: true,\n disableInWorkflow: true,\n description: \"Build action with drag and drop steps similar to Scratch\",\n configFields: [\n {\n name: \"workspace\",\n input_type: \"hidden\",\n },\n {\n name: \"code\",\n input_type: \"hidden\",\n },\n ],\n /**\n * @type {base-plugin/actions~run_code}\n * @see base-plugin/actions~run_code\n */\n run: run_code,\n namespace: \"Code\",\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n emit_event: {\n /**\n * @returns {object[]}\n */\n description: \"Emit an event\",\n configFields: async ({ table }) => {\n const evTypes = trigger_1.default.when_options;\n const hasChannel = [];\n const hasTable = [];\n evTypes.forEach((ty) => {\n if (eventlog_1.default.hasChannel(ty))\n hasChannel.push(ty);\n if (eventlog_1.default.hasTable(ty))\n hasTable.push(ty);\n });\n return [\n {\n name: \"eventType\",\n label: \"Event type\",\n required: true,\n input_type: \"select\",\n options: evTypes,\n },\n {\n name: \"channel\",\n label: \"Channel\",\n type: \"String\",\n showIf: { eventType: hasChannel },\n help: {\n topic: \"Event channel and payload\",\n },\n },\n {\n name: \"channel\",\n label: \"Table\",\n type: \"String\",\n showIf: { eventType: hasTable },\n required: true,\n attributes: { options: table_1.default.allTableNames },\n help: {\n topic: \"Event channel and payload\",\n },\n },\n {\n name: \"payload\",\n label: \"Payload JSON\",\n sublabel: `Leave blank to use row from table. <code>user</code> ${table ? `and field variables ` : \"\"}in scope`,\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n compact: true,\n expression_type: \"row\",\n table: table?.name,\n nojoins: true,\n user: true,\n },\n help: {\n topic: \"Event channel and payload\",\n },\n },\n ];\n },\n /**\n * @param {object} opts\n * @param {object} opts.row\n * @param {object} opts.configuration\n * @param {object} opts.user\n * @returns {Promise<void>}\n */\n run: async ({ row, configuration: { eventType, channel, payload }, user, }) => {\n return await trigger_1.default.emitEvent(eventType, channel, user, payload ? eval_expression(payload, row || {}, user) : row);\n },\n namespace: \"Control\",\n },\n loop_rows: {\n /**\n * @returns {object[]}\n */\n description: \"Repeat an action over some or all rows in a table\",\n configFields: async () => {\n const tables = await table_1.default.find({}, { cached: true });\n const trigger_actions0 = await trigger_1.default.find({});\n const trigger_actions = trigger_actions0.sort(comparingCaseInsensitive(\"name\"));\n const order_options = {};\n for (const table of tables) {\n order_options[table.name] = table.fields.map((f) => f.name);\n order_options[table.name].push(\"RANDOM()\");\n }\n return [\n {\n name: \"table_name\",\n label: \"Table\",\n sublabel: \"Source for rows to loop over\",\n input_type: \"select\",\n options: tables.map((t) => t.name),\n },\n {\n name: \"where\",\n label: \"Where\",\n sublabel: `Where-expression for subset of rows to loop over. For example: <code>{status: \"Active\"}</code>`,\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n expression_type: \"query\",\n },\n },\n {\n name: \"limit\",\n label: \"Limit\",\n type: \"Integer\",\n sublabel: \"Optional. Max number of rows to loop over\",\n },\n {\n name: \"orderBy\",\n label: \"Order by\",\n type: \"String\",\n attributes: {\n calcOptions: [\"table_name\", order_options],\n },\n },\n {\n name: \"orderDesc\",\n label: \"Descending?\",\n type: \"Bool\",\n },\n {\n name: \"interval\",\n label: \"Interval (s)\",\n sublabel: \"Delay in seconds between action invocations\",\n type: \"Float\",\n attributes: { min: 0 },\n },\n {\n name: \"trigger_id\",\n label: \"Trigger\",\n sublabel: \"The trigger to run for each row. \" +\n a({\n \"data-dyn-href\": `\\`/actions/configure/\\${trigger_id}\\``,\n target: \"_blank\",\n }, \"Configure\"),\n input_type: \"select\",\n options: trigger_actions.map((t) => ({ label: t.name, value: t.id })),\n },\n ];\n },\n /**\n * @param {object} opts\n * @param {object} opts.row\n * @param {object} opts.configuration\n * @param {object} opts.user\n * @returns {Promise<void>}\n */\n run: async ({ row, configuration: { table_name, where, limit, orderBy, orderDesc, trigger_id, interval, }, user, ...rest }) => {\n const table = table_1.default.findOne({ name: table_name });\n const wh = where ? eval_expression(where, row, user) : {};\n const selOpts = { orderDesc, orderBy };\n if (limit)\n selOpts.limit = limit;\n const rows = await table.getRows(wh, selOpts);\n const trigger = trigger_1.default.findOne({ id: trigger_id });\n let result = {};\n let first = true;\n for (const row_i of rows) {\n if (!first && interval)\n await sleep(interval * 1000);\n const stepres = await trigger.runWithoutRow({\n ...rest,\n table,\n row: row_i,\n user,\n });\n try {\n mergeActionResults(result, stepres);\n }\n catch (error) {\n console.error(error);\n }\n first = false;\n }\n return result;\n },\n namespace: \"Control\",\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n webhook: {\n description: \"Make an outbound HTTP/HTTPS request\",\n configFields: async ({ table, mode }) => {\n let field_opts = [];\n if (table) {\n field_opts = table.fields\n .filter((f) => f.type &&\n [\"String\", \"HTML\", \"JSON\"].includes(typeof f.type === \"string\" ? f.type : f.type?.name))\n .map((f) => f.name);\n }\n return [\n {\n name: \"url\",\n label: \"URL\",\n type: \"String\",\n sublabel: \"Trigger will call specified URL. Interpolations <code>{{ }}</code> can be used\",\n },\n {\n name: \"method\",\n label: \"HTTP Method\",\n type: \"String\",\n required: true,\n attributes: { options: [\"POST\", \"GET\", \"PUT\", \"DELETE\", \"PATCH\"] },\n },\n {\n name: \"body\",\n label: \"JSON body\",\n showIf: { method: [\"POST\", \"PUT\", \"DELETE\", \"PATCH\"] },\n sublabel: `Leave blank to use row from table.${table ? ` <code>user</code> and field variables in scope` : \"\"}`,\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n compact: true,\n expression_type: \"row\",\n table: table?.name,\n nojoins: true,\n user: true,\n },\n },\n {\n name: \"authorization\",\n label: \"Authorization header\",\n type: \"String\",\n sublabel: \"For example <code>Bearer xxxx</code>. Interpolations <code>{{ }}</code> can be used\",\n },\n ...(field_opts.length\n ? [\n {\n name: \"response_field\",\n label: \"Response into field\",\n type: \"String\",\n attributes: { options: field_opts },\n },\n ]\n : []),\n ...(mode === \"workflow\"\n ? [\n {\n name: \"response_var\",\n label: \"Response variable\",\n sublabel: \"Variable in the context to fill with the response\",\n type: \"String\",\n },\n ]\n : []),\n ];\n },\n /**\n * @param {object} opts\n * @param {string} opts.url\n * @param {object} opts.body\n * @returns {Promise<object>}\n */\n run: async ({ row, user, table, configuration: { url, body, authorization, response_field, response_var, method, }, }) => {\n let url1 = interpolate(url, row || {}, user, \"Webhook URL\");\n const fetchOpts = {\n method: (method || \"post\").toLowerCase(),\n headers: { \"Content-Type\": \"application/json\" },\n ...getFetchProxyOptions(),\n };\n if (method !== \"GET\") {\n let postBody;\n if (body && row) {\n const f = get_async_expression_function(body, table?.fields || Object.keys(row).map((k) => ({ name: k })), {\n row: row || {},\n user,\n });\n postBody = JSON.stringify(await f(row, user));\n }\n else if (body)\n postBody = body;\n else\n postBody = JSON.stringify(row);\n fetchOpts.body = postBody;\n }\n if (authorization)\n fetchOpts.headers.Authorization = interpolate(authorization, row, user, \"Webhook authorization\");\n const response = await fetch(url1, fetchOpts);\n const contentType = response.headers.get(\"content-type\");\n const isJSON = contentType && contentType.indexOf(\"application/json\") !== -1;\n if (response_var) {\n const parsedResponse = isJSON\n ? await response.json()\n : await response.text();\n return { [response_var]: parsedResponse };\n }\n else if (table && row && response_field) {\n const field = table.getField(response_field);\n const parsedResponse = isJSON\n ? await response.json()\n : await response.text();\n const saveResponse = isJSON &&\n typeof field?.type !== \"string\" &&\n (field?.type?.name === \"String\" || field?.type?.sql_name === \"text\")\n ? JSON.stringify(parsedResponse)\n : parsedResponse;\n await table.updateRow({ [response_field]: saveResponse }, row[table.pk_name]);\n }\n else\n return;\n },\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n find_or_create_dm_room: {\n /**\n * @returns {Promise<object[]>}\n */\n description: \"Find or create a direct message room for the user, redirect the page to this room\",\n configFields: async () => {\n const views = await view_1.default.find_all_views_where(({ viewrow }) => viewrow.viewtemplate === \"Room\");\n const view_opts = views.map((v) => v.name);\n return [\n {\n name: \"viewname\",\n label: \"Room view\",\n sublabel: \"Select a view with the Room viewtemplate\",\n input_type: \"select\",\n options: view_opts,\n },\n ];\n },\n /**\n * @param {object} opts\n * @param {object} opts.row\n * @param {*} opts.table\n * @param {object} opts.configuration\n * @param {object} opts.user\n * @returns {Promise<object>}\n */\n run: async ({ row, table, configuration: { viewname }, user, }) => {\n const view = view_1.default.findOne({ name: viewname });\n if (!view)\n throw new Error(`In find_or_create_dm_room action, Room view ${viewname} does not exist`);\n const { participant_field } = view.configuration;\n const [part_table_name, part_key_to_room, part_user_field] = participant_field.split(\".\");\n const roomtable = table_1.default.findOne({ id: view.table_id });\n const parttable = table_1.default.findOne({ name: part_table_name });\n //find a room that has both participants\n //select id from rooms r where uid1 in (select id from participants where...) and\n const { rows } = await db.query(`with my_rooms as (select \"${part_key_to_room}\" from \"${db.getTenantSchema()}\".\"${db.sqlsanitize(part_table_name)}\" where \"${part_user_field}\" = $1)\n select * from \"${db.getTenantSchema()}\".\"${db.sqlsanitize(roomtable.name)}\" r where r.id in (select \"${part_key_to_room}\" from my_rooms) \n and $2 in (select \"${part_user_field}\" from \"${db.getTenantSchema()}\".\"${db.sqlsanitize(part_table_name)}\" where \"${part_key_to_room}\" = r.id)`, [user.id, row.id]);\n if (rows.length > 0) {\n return { goto: `/view/${viewname}?id=${rows[0].id}` };\n }\n else {\n //create room\n const room_id = await roomtable.insertRow({});\n await parttable.insertRow({\n [part_user_field]: user.id,\n [part_key_to_room]: room_id,\n });\n await parttable.insertRow({\n [part_user_field]: row.id,\n [part_key_to_room]: room_id,\n });\n return { goto: `/view/${viewname}?id=${room_id}` };\n }\n },\n namespace: \"Communication\",\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n send_email: {\n /**\n * @param {object} opts\n * @param {object} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"Send an email, based on a chosen view for this table\",\n configFields: async ({ table, mode }) => {\n if (mode === \"workflow\") {\n return [\n {\n name: \"to_email\",\n label: \"To\",\n sublabel: \"To addresses, comma separated, <code>{{ }}</code> interpolations usable\",\n type: \"String\",\n },\n {\n name: \"cc_email\",\n label: \"cc\",\n sublabel: \"cc addresses, comma separated, <code>{{ }}</code> interpolations usable\",\n type: \"String\",\n },\n {\n name: \"bcc_email\",\n label: \"bcc\",\n sublabel: \"bcc addresses, comma separated, <code>{{ }}</code> interpolations usable\",\n type: \"String\",\n },\n {\n name: \"subject\",\n label: \"Subject\",\n sublabel: \"Subject of email, <code>{{ }}</code> interpolations usable\",\n type: \"String\",\n required: true,\n },\n {\n name: \"body\",\n label: \"Body\",\n type: \"String\",\n fieldview: \"textarea\",\n required: true,\n },\n {\n name: \"locale\",\n label: \"Locale\",\n sublabel: \"Language override. Two-letter code (<code>en</code>, <code>fr</code> etc) or language name. <code>{{ }}</code> interpolations usable\",\n type: \"String\",\n },\n /* {\n name: \"attachment_paths\",\n label: \"Attachments\",\n sublabel:\n \"Comma-separated list of files to attach. <code>{{ }}</code> interpolations usable\",\n type: \"String\",\n },*/\n {\n name: \"confirm_field\",\n label: \"Send confirmation variable\",\n type: \"String\",\n sublabel: \"Bool variable set in context indicate successful sending of email message\",\n },\n ];\n }\n if (!table)\n return [];\n const views = await view_1.default.find_table_views_where(table, ({ state_fields, viewtemplate, viewrow }) => !!(viewtemplate?.runMany || viewtemplate?.renderRows));\n const view_opts = views.map((v) => v.name);\n const fields = table.getFields();\n const field_opts = fields\n .filter((f) => f.type_name === \"String\" || f.reftable_name === \"users\")\n .map((f) => f.name);\n const body_field_opts = fields\n .filter((f) => f.type_name === \"HTML\" || f.type_name === \"String\")\n .map((f) => f.name);\n const confirm_field_opts = fields\n .filter((f) => f.type_name === \"Bool\" || f.type_name === \"Date\")\n .map((f) => f.name);\n const attachment_opts = [\"\"];\n for (const field of fields) {\n if (field.type === \"File\")\n attachment_opts.push(field.name);\n }\n for (const relationPath of await getFileAggregations(table)) {\n attachment_opts.push(relationPath);\n }\n return [\n {\n name: \"body_type\",\n label: \"Body type\",\n type: \"String\",\n required: true,\n attributes: {\n options: [\"View\", \"Text field\", \"HTML field\", \"MJML field\"],\n },\n },\n {\n name: \"body_field\",\n label: \"Body field\",\n type: \"String\",\n required: true,\n attributes: {\n options: body_field_opts,\n },\n showIf: { body_type: [\"Text field\", \"HTML field\", \"MJML field\"] },\n },\n {\n name: \"viewname\",\n label: \"View to send\",\n sublabel: \"Select a view that can render a single record - for instance, of the Show template.\",\n input_type: \"select\",\n options: view_opts,\n showIf: { body_type: \"View\" },\n },\n {\n name: \"to_email\",\n label: \"Recipient email address\",\n sublabel: \"Select email addresses for send email. Choose option to get more information\",\n input_type: \"select\",\n required: true,\n options: [\"Fixed\", \"User\", \"Field\"],\n },\n {\n name: \"to_email_field\",\n label: \"Field with email address\",\n sublabel: \"Field with email address a String, or Key to user who will receive email\",\n input_type: \"select\",\n options: field_opts,\n showIf: { to_email: \"Field\" },\n },\n {\n name: \"to_email_fixed\",\n label: \"Fixed address\",\n sublabel: \"To addresses, comma separated, <code>{{ }}</code> interpolations usable\",\n type: \"String\",\n showIf: { to_email: \"Fixed\" },\n },\n {\n name: \"cc_email\",\n label: \"cc address\",\n sublabel: \"cc addresses, comma separated, <code>{{ }}</code> interpolations usable\",\n type: \"String\",\n },\n {\n name: \"bcc_email\",\n label: \"bcc address\",\n sublabel: \"bcc addresses, comma separated, <code>{{ }}</code> interpolations usable\",\n type: \"String\",\n },\n {\n name: \"subject\",\n label: \"Subject\",\n sublabel: \"Subject of email\",\n type: \"String\",\n class: \"validate-expression validate-expression-conditional\",\n required: true,\n },\n {\n name: \"subject_formula\",\n label: \"Subject is a formula?\",\n type: \"Bool\",\n required: false,\n },\n {\n name: \"attachment_path\",\n label: \"Attachment\",\n sublabel: \"Select a field pointing to a file. \" +\n \"Direct fields produce a single attachment, relations allow multiple attachments.\",\n input_type: \"select\",\n options: attachment_opts,\n type: \"String\",\n default: \"\",\n },\n {\n name: \"only_if\",\n label: \"Only if\",\n sublabel: \"Only send email if this formula evaluates to true. Leave blank to always send email\",\n type: \"String\",\n },\n {\n name: \"locale\",\n label: \"Locale\",\n sublabel: \"Language override. Two-letter code (<code>en</code>, <code>fr</code> etc) or language name. <code>{{ }}</code> interpolations usable\",\n type: \"String\",\n },\n { name: \"disable_notify\", label: \"Disable notification\", type: \"Bool\" },\n {\n name: \"confirm_field\",\n label: \"Send confirmation field\",\n type: \"String\",\n sublabel: \"Bool or Date field to indicate successful sending of email message\",\n attributes: {\n options: confirm_field_opts,\n },\n },\n ];\n },\n requireRow: true,\n /**\n * @param {object} opts\n * @param {object} opts.row\n * @param {object} opts.table\n * @param {object} opts.configuration\n * @param {object} opts.user\n * @returns {Promise<object>}\n */\n run: async ({ row, table, configuration: { body_type, body_field, viewname, subject, subject_formula, to_email, to_email_field, to_email_fixed, cc_email, bcc_email, only_if, attachment_path, disable_notify, confirm_field, body, locale, }, user, mode, }) => {\n const from = getState().getConfig(\"email_from\");\n if (mode === \"workflow\") {\n const email = {\n from,\n to: interpolate(to_email, row, user, \"send_email to address\"),\n cc: interpolate(cc_email, row, user, \"send_email cc address\"),\n bcc: interpolate(bcc_email, row, user, \"send_email bcc address\"),\n subject: interpolate(subject, row, user, \"send_email subject\"),\n html: interpolate(body, row, user, \"send_email html body\"),\n // attachments,\n };\n const sendres = await (await getMailTransport()).sendMail(email);\n getState().log(5, `send_email result: ${JSON.stringify(sendres)}`);\n if (confirm_field)\n return { [confirm_field]: sendres.accepted.length > 0 };\n else\n return;\n }\n let to_addr;\n let useRow = row;\n const fvs = [\n ...freeVariablesInInterpolation(to_email_fixed),\n ...freeVariablesInInterpolation(cc_email),\n ...freeVariablesInInterpolation(bcc_email),\n ...freeVariablesInInterpolation(locale),\n ...(subject_formula ? freeVariables(subject) : []),\n ...freeVariables(only_if),\n ];\n if (fvs.length > 0) {\n const joinFields = {};\n const fields = table.getFields();\n add_free_variables_to_joinfields(new Set(fvs), joinFields, fields);\n useRow = await table.getJoinedRow({\n where: { [table.pk_name]: row[table.pk_name] },\n joinFields,\n forUser: user,\n });\n }\n if (only_if) {\n const bres = eval_expression(only_if, useRow, user, \"send_email only if formula\");\n if (!bres)\n return;\n }\n switch (to_email) {\n case \"Fixed\":\n to_addr = interpolate(to_email_fixed, useRow, user, \"send_email to address\");\n break;\n case \"User\":\n to_addr = user.email;\n break;\n case \"Field\":\n const fields = table.getFields();\n const field = fields.find((f) => f.name === to_email_field);\n if (field && field.type.name === \"String\")\n to_addr = row[to_email_field];\n else if (field && field.reftable_name === \"users\") {\n const refuser = await user_1.default.findOne({\n id: row[to_email_field],\n });\n to_addr = refuser.email;\n }\n break;\n }\n if (!to_addr) {\n getState().log(2, `send_email action: Not sending as address ${to_email} is missing`);\n return;\n }\n const setBody = {};\n if (body_type === \"Text field\" && body_field) {\n setBody.text = row[body_field];\n }\n else if (body_type === \"HTML field\" && body_field) {\n setBody.html = row[body_field];\n }\n else if (body_type === \"MJML field\" && body_field) {\n const mjml = row[body_field];\n const html = mjml2html(mjml, { minify: true });\n setBody.html = html.html;\n }\n else {\n const opts = {};\n if (locale) {\n opts.locale = interpolate(locale, useRow, user, \"send_email locale\");\n const cfgLangs = getState().getConfig(\"localizer_languages\");\n if (Object.values(cfgLangs || {})\n .map((r) => r.name)\n .includes(opts.locale)) {\n opts.locale = Object.values(cfgLangs).find((r) => r.name).locale;\n }\n }\n const view = await view_1.default.findOne({ name: viewname });\n setBody.html = await viewToEmailHtml(view, {\n [table.pk_name]: row[table.pk_name],\n }, opts);\n }\n // if user not supplied, default to admin rights in line with convention for insertRow/updateRow\n const attachments = await loadAttachments(attachment_path, row, user ? user : { role_id: 1 });\n const the_subject = subject_formula\n ? eval_expression(subject, useRow, user, \"send_email subject formula\")\n : subject;\n getState().log(3, `Sending email from ${from} to ${to_addr} with subject ${the_subject}`);\n const cc = cc_email\n ? interpolate(cc_email, useRow, user, \"send_email cc address\")\n : undefined;\n const bcc = bcc_email\n ? interpolate(bcc_email, useRow, user, \"send_email bcc address\")\n : undefined;\n const email = {\n from,\n to: to_addr,\n cc,\n bcc,\n subject: the_subject,\n ...setBody,\n attachments,\n };\n try {\n const sendres = await (await getMailTransport()).sendMail(email);\n getState().log(5, `send_email result: ${JSON.stringify(sendres)}`);\n if (confirm_field) {\n const confirm_fld = table.getField(confirm_field);\n if (sendres.accepted.length > 0) {\n if (confirm_fld && confirm_fld.type_name === \"Date\")\n await table.updateRow({ [confirm_field]: new Date() }, row[table.pk_name]);\n else if (confirm_fld && confirm_fld.type_name === \"Bool\")\n await table.updateRow({ [confirm_field]: true }, row[table.pk_name]);\n }\n else if (confirm_fld && confirm_fld.type_name === \"Bool\") {\n await table.updateRow({ [confirm_field]: false }, row[table.pk_name]);\n }\n }\n if (disable_notify)\n return;\n else\n return { notify: `E-mail sent to ${to_addr}` };\n }\n catch (e) {\n if (confirm_field) {\n const confirm_fld = table.getField(confirm_field);\n if (confirm_fld &&\n (typeof confirm_fld.type === \"string\"\n ? confirm_fld.type === \"Bool\"\n : confirm_fld.type?.name === \"Bool\"))\n await table.updateRow({ [confirm_field]: false }, row[table.pk_name]);\n throw e;\n }\n }\n },\n namespace: \"Communication\",\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n insert_joined_row: {\n /**\n * @param {object} opts\n * @param {object} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"Insert a row in a related table\",\n configFields: async ({ table }) => {\n if (!table)\n return [];\n const { child_field_list } = await table.get_child_relations();\n return [\n {\n name: \"joined_table\",\n label: \"Relation\",\n sublabel: \"Relation\", // todo more detailed explanation\n input_type: \"select\",\n options: child_field_list,\n },\n ];\n },\n disableInWorkflow: true,\n requireRow: true,\n /**\n * @param {object} opts\n * @param {object} opts.row\n * @param {object} opts.table\n * @param {object} opts.configuration\n * @param {object} opts.user\n * @returns {Promise<object>}\n */\n run: async ({ row, table, configuration: { joined_table }, user, }) => {\n if (!joined_table)\n throw new Error(`Relation not specified in insert_joined_row action`);\n const [join_table_name, join_field] = joined_table.split(\".\");\n const joinTable = table_1.default.findOne({ name: join_table_name });\n if (!joinTable)\n throw new Error(`Table ${join_table_name} not found in insert_joined_row action`);\n const fields = joinTable.getFields();\n const newRow = { [join_field]: row.id };\n for (const field of fields) {\n if (field.type === \"Key\" &&\n field.reftable_name === \"users\" &&\n user &&\n user.id)\n newRow[field.name] = user.id;\n }\n return await joinTable.insertRow(newRow, user);\n },\n namespace: \"Database\",\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n duplicate_row: {\n /**\n * @returns {Promise<object[]>}\n */\n description: \"Duplicate the current row\",\n configFields: () => [],\n disableInWorkflow: true,\n requireRow: true,\n /**\n * @param {object} opts\n * @param {object} opts.row\n * @param {object} opts.table\n * @param {*} opts.user\n * @returns {Promise<object>}\n */\n run: async ({ row, table, user, }) => {\n const newRow = { ...row };\n table.getFields();\n delete newRow[table.pk_name];\n for (const field of table.fields)\n if (field.is_fkey &&\n typeof newRow[field.name] === \"object\" &&\n newRow[field.name]?.id)\n newRow[field.name] = newRow[field.name].id; //TODO non-id pks\n await table.insertRow(newRow, user);\n },\n namespace: \"Database\",\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n recalculate_stored_fields: {\n /**\n * @param {object} opts\n * @param {object} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"Re-calculate the stored calculated fields for a table, optionally only for the triggering row\",\n configFields: async ({ table }) => {\n return [\n {\n name: \"table\",\n label: \"Table\",\n sublabel: \"Table on which to recalculate stored calculated fields\",\n input_type: \"select\",\n options: table_1.default.allTableNames,\n },\n {\n name: \"only_triggering_row\",\n label: \"Only triggering row\",\n type: \"Bool\",\n showIf: table ? { table: table.name } : {},\n },\n {\n name: \"where\",\n label: \"Recalculate where\",\n sublabel: \"Where-expression for subset of rows to recalculate. Example: <code>{manager: id}</code>\",\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n expression_type: \"query\",\n ...(table ? { table: table.name } : {}),\n },\n },\n ];\n },\n /**\n * @param {object} opts\n * @param {object} opts.configuration\n * @returns {Promise<void>}\n */\n run: async ({ table, row, configuration, user, }) => {\n const table_for_recalc = table_1.default.findOne({\n name: configuration.table,\n });\n //intentionally omit await\n if (configuration.only_triggering_row &&\n table &&\n table.name === table_for_recalc?.name &&\n row &&\n row[table.pk_name]) {\n await table.updateRow({}, row[table.pk_name], undefined, true);\n }\n else if (configuration.where) {\n const where = eval_expression(configuration.where, row || {}, user, \"recalculate_stored_fields where\");\n await recalculate_for_stored(table_for_recalc, where);\n }\n else if (table_for_recalc)\n recalculate_for_stored(table_for_recalc);\n else\n return { error: \"recalculate_stored_fields: table not found\" };\n },\n namespace: \"Database\",\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n insert_any_row: {\n /**\n * @param {object} opts\n * @param {*} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"insert a row into any table, using a formula expression\",\n configFields: async ({ mode, table }) => {\n return [\n {\n name: \"table\",\n label: \"Table\",\n sublabel: \"Table to insert rows in\",\n input_type: \"select\",\n options: table_1.default.allTableNames,\n },\n {\n name: \"row_expr\",\n label: \"Row expression\",\n sublabel: \"Expression for JavaScript object or array of objects. Example: <code>{first_name: name.split(' ')[0]}</code>\",\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n compact: true,\n expression_type: \"row\",\n table: table?.name,\n nojoins: true,\n user: true,\n },\n },\n ...(mode === \"workflow\"\n ? [\n {\n name: \"id_variable\",\n label: \"ID variable\",\n sublabel: \"Variable in the context to fill with the created ID value\",\n type: \"String\",\n },\n ]\n : []),\n ];\n },\n /**\n * @param {object} opts\n * @param {object} opts.row\n * @param {object} [opts.table]\n * @param {object} opts.configuration\n * @param {object} opts.user\n * @param {...*} [opts.rest]\n * @returns {Promise<object|boolean>}\n */\n run: async ({ row, table, configuration, user, referrer, ...rest }) => {\n const state = urlStringToObject(referrer);\n const f = get_async_expression_function(configuration.row_expr, table?.fields || Object.keys(row || {}).map((k) => ({ name: k })), {\n user,\n console,\n session_id: rest.req && getSessionId(rest.req),\n ...dollarizeObject(state),\n });\n const calcrow = await f(row || {}, user);\n const table_for_insert = table_1.default.findOne({ name: configuration.table });\n const all_results = {};\n const ids = [];\n const upsertOne = async (row) => {\n const results = {};\n if (row[table_for_insert.pk_name]) {\n const existing = await table_for_insert.getRow({\n [table_for_insert.pk_name]: row[table_for_insert.pk_name],\n });\n if (existing) {\n await table_for_insert.updateRow(row, row[table_for_insert.pk_name], user, { resultCollector: results });\n ids.push(row[table_for_insert.pk_name]);\n }\n else\n ids.push(await table_for_insert.insertRow(row, user, results));\n }\n else\n ids.push(await table_for_insert.insertRow(row, user, results));\n mergeActionResults(all_results, results);\n };\n if (Array.isArray(calcrow)) {\n for (const insrow of calcrow)\n await upsertOne(insrow);\n if (configuration.id_variable)\n return { [configuration.id_variable]: ids, ...all_results };\n else\n return all_results;\n }\n else {\n await upsertOne(calcrow);\n if (configuration.id_variable)\n return { [configuration.id_variable]: ids[0], ...all_results };\n else\n return all_results;\n }\n },\n namespace: \"Database\",\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n modify_row: {\n description: \"Modify the triggering row\",\n configFields: async ({ mode, when_trigger, table, }) => {\n return [\n {\n name: \"row_expr\",\n label: \"Row expression\",\n sublabel: \"Expression for JavaScript object. For example, <code>{points: 34}</code>\",\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n compact: true,\n expression_type: \"row\",\n table: table?.name,\n nojoins: true,\n user: true,\n },\n },\n ...(mode === \"edit\" ||\n mode === \"filter\" ||\n when_trigger === \"Validate\" ||\n mode === \"workflow\"\n ? [\n {\n name: \"where\",\n label: \"Modify where\",\n type: \"String\",\n required: true,\n attributes: {\n options: when_trigger === \"Validate\"\n ? [\"Row\"]\n : mode === \"filter\"\n ? [\"Filter state\"]\n : mode === \"workflow\"\n ? [\"Database\", \"Active edit view\"]\n : [\"Form\", \"Database\"],\n },\n },\n ]\n : []),\n ...(mode === \"workflow\"\n ? [\n {\n name: \"select_table\",\n label: \"Table\",\n type: \"String\",\n required: true,\n attributes: {\n options: table_1.default.allTableNames,\n showIf: { where: \"Database\" },\n },\n },\n {\n name: \"query\",\n label: \"Query object\",\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n expression_type: \"query\",\n },\n required: true,\n showIf: { where: \"Database\" },\n },\n ]\n : []),\n ];\n },\n requireRow: true,\n run: async ({ row, table, configuration: { row_expr, where, select_table, query }, user, ...rest }) => {\n const f = get_async_expression_function(row_expr, table?.fields || Object.keys(row || {}).map((k) => ({ name: k })), {\n row: row || {},\n user,\n });\n const calcrow = await f(row, user);\n if (where === \"Form\" ||\n where === \"Filter state\" ||\n where === \"Row\" ||\n where === \"Active edit view\")\n return { set_fields: calcrow };\n if (select_table && query) {\n //get table\n const table = table_1.default.findOne(select_table);\n // evaluate query\n const q = eval_expression(query, row, user, \"Query expression in modify_row step\");\n const rows = await table.getRows(q);\n for (const row of rows) {\n await table.updateRow(calcrow, row[table.pk_name]);\n }\n return;\n }\n const res = await table.tryUpdateRow(calcrow, row[table.pk_name], user);\n if (res.error)\n return res;\n else\n return;\n },\n namespace: \"Database\",\n },\n delete_rows: {\n /**\n * @param {object} opts\n * @param {*} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"Modify the triggering row\",\n configFields: async ({ mode, table, when_trigger }) => {\n const tableNames = table_1.default.allTableNames;\n return [\n ...(mode === \"workflow\"\n ? []\n : [\n {\n name: \"delete_triggering_row\",\n label: \"Delete triggering row\",\n type: \"Bool\",\n },\n ]),\n {\n name: \"table_name\",\n label: \"Table\",\n sublabel: \"Table on which to delete rows\",\n input_type: \"select\",\n showIf: mode === \"workflow\" ? undefined : { delete_triggering_row: false },\n options: tableNames,\n },\n {\n name: \"delete_where\",\n label: \"Delete where\",\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n expression_type: \"query\",\n ...(table ? { table: table.name } : {}),\n },\n sublabel: \"Where expression, ex. <code>{manager: id}</code>\",\n required: true,\n showIf: mode === \"workflow\" ? undefined : { delete_triggering_row: false },\n },\n ];\n },\n run: async ({ row, table, configuration: { delete_triggering_row, delete_where, table_name }, user, ...rest }) => {\n const resultCollector = {};\n if (delete_triggering_row) {\n if (!table || !row?.[table.pk_name])\n throw new Error(\"delete_rows cannot find triggering row\");\n await table.deleteRows({ [table.pk_name]: row[table.pk_name] }, user, false, resultCollector);\n return resultCollector;\n }\n const where = eval_expression(delete_where, row || {}, user, \"delete_rows where\");\n const tbl = table_1.default.findOne({ name: table_name });\n await tbl.deleteRows(where, user, false, resultCollector);\n return resultCollector;\n },\n namespace: \"Database\",\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n navigate: {\n /**\n * @param {object} opts\n * @param {*} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"Navigation action\",\n configFields: async ({ table }) => {\n const pages = await page_1.default.find({}, { cached: true });\n const views = await view_1.default.find({}, { cached: true });\n return [\n {\n name: \"nav_action\",\n label: \"Nav Action\",\n type: \"String\",\n required: true,\n attributes: {\n options: [\n \"Go to URL\",\n \"Go to View\",\n \"Go to Page\",\n \"Popup modal\",\n \"Back\",\n \"Reload page\",\n \"Close modal\",\n \"Close tab\",\n ],\n },\n },\n {\n name: \"url\",\n label: \"URL\",\n type: \"String\",\n required: true,\n showIf: { nav_action: [\"Go to URL\", \"Popup modal\"] },\n sublabel: \"Use interpolations <code>{{ }}</code> to access row variables\",\n },\n {\n name: \"page\",\n label: \"Page\",\n input_type: \"select\",\n options: pages.map((p) => p.name),\n showIf: { nav_action: \"Go to Page\" },\n },\n {\n name: \"view\",\n label: \"View\",\n input_type: \"select\",\n options: views.map((p) => p.name),\n showIf: { nav_action: \"Go to View\" },\n },\n {\n name: \"state_formula\",\n label: \"State\",\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n expression_type: \"query\",\n ...(table ? { table: table.name } : {}),\n },\n showIf: { nav_action: [\"Go to Page\", \"Go to View\"] },\n sublabel: `Additional state passed to the page. Example: <code>{id: 5}</code>`,\n help: {\n topic: \"Extra state formula\",\n context: table ? { srcTable: table.name } : {},\n dynContext: [\"view\"],\n },\n },\n {\n name: \"new_tab\",\n label: \"Open in new tab\",\n type: \"Bool\",\n showIf: { nav_action: [\"Go to URL\", \"Go to Page\", \"Go to View\"] },\n },\n {\n name: \"in_popup\",\n label: \"Open in popup modal\",\n type: \"Bool\",\n showIf: { nav_action: [\"Go to URL\", \"Go to Page\", \"Go to View\"] },\n },\n ];\n },\n run: async ({ row, user, configuration: { nav_action, url, state_formula, new_tab, view, page, in_popup, }, req, }) => {\n let qs = \"\";\n if ([\"Go to Page\", \"Go to View\"].includes(nav_action) && state_formula) {\n const new_state = eval_expression(state_formula, row || {}, user, \"navigate state formula\");\n qs = \"?\" + objectToQueryString(new_state);\n }\n const kgoto = in_popup ? \"popup\" : \"goto\";\n switch (nav_action) {\n case \"Go to URL\":\n return {\n [kgoto]: interpolate(url, row, user, \"navigate URL\"),\n ...(new_tab ? { target: \"_blank\" } : {}),\n };\n case \"Go to Page\":\n return {\n [kgoto]: `/page/${page}${qs}`,\n ...(new_tab ? { target: \"_blank\" } : {}),\n };\n case \"Go to View\":\n return {\n [kgoto]: `/view/${view}${qs}`,\n ...(new_tab ? { target: \"_blank\" } : {}),\n };\n case \"Popup modal\":\n return { popup: interpolate(url, row, user, \"navigate URL\") };\n case \"Back\":\n return {\n eval_js: isWeb(req)\n ? \"history.back()\"\n : \"parent.saltcorn.mobileApp.navigation.goBack()\",\n };\n case \"Close tab\":\n return { eval_js: \"window.close()\" };\n case \"Close modal\":\n return { eval_js: \"close_saltcorn_modal()\" };\n case \"Reload page\":\n return { reload_page: true };\n default:\n break;\n }\n },\n namespace: \"User interface\",\n },\n step_control_flow: {\n /**\n * @param {object} opts\n * @param {*} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"Step control flow\",\n disableInWorkflow: true,\n configFields: [\n {\n name: \"control_action\",\n label: \"Control action\",\n type: \"String\",\n required: true,\n attributes: {\n options: [\"Halt steps\", \"Goto step\", \"Clear return values\"],\n },\n },\n {\n name: \"step\",\n label: \"Step\",\n type: \"Integer\",\n required: true,\n showIf: { control_action: [\"Goto step\"] },\n },\n ],\n run: async ({ row, user, configuration: { control_action, step }, }) => {\n switch (control_action) {\n case \"Halt steps\":\n return { halt_steps: true };\n case \"Goto step\":\n return { goto_step: step };\n case \"Clear return values\":\n return { clear_return_values: true };\n default:\n break;\n }\n },\n namespace: \"Control\",\n },\n form_action: {\n /**\n * @param {object} opts\n * @param {*} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"Action on form in Edit view\",\n requireRow: true,\n configFields: [\n {\n name: \"form_action\",\n label: \"Form Action\",\n input_type: \"select\",\n required: true,\n attributes: {\n explainers: {\n RequestSubmit: \"Validate the form, and submit if no errors, or display errors\",\n Submit: \"Submit the form, skipping validation\",\n Save: \"Save the form contents on the server, stay on the form page\",\n Reset: \"Reset the form to the state it was in at page load time\",\n \"Submit with Ajax\": \"Submit the form with Ajax and follow the set destination if there are no errors\",\n \"Ajax Save Form Data\": \"Submit the form with Ajax and close the open popup \",\n },\n },\n options: [\n \"RequestSubmit\",\n \"Submit\",\n \"Save\",\n \"Reset\",\n \"Submit with Ajax\",\n \"Ajax Save Form Data\",\n ],\n },\n ],\n run: async ({ row, table, user, req, configuration: { form_action }, ...rest }) => {\n const jqGet = `$('form[data-viewname=\"'+viewname+'\"]')`;\n switch (form_action) {\n case \"Submit\":\n return { eval_js: jqGet + \".submit()\" };\n case \"RequestSubmit\":\n return { eval_js: jqGet + \"[0].requestSubmit()\" };\n case \"Save\":\n const applyUploadedFiles = async () => {\n if (!req?.files)\n return false;\n const viewColumns = rest?.columns;\n let processed = false;\n for (const field of table.fields) {\n if (field.type === \"File\" && req.files[field.name]) {\n let folder = field?.attributes?.folder;\n if (Array.isArray(viewColumns)) {\n const col = viewColumns.find((c) => c?.type === \"Field\" && c.field_name === field.name);\n let cfgFolder = col?.configuration?.folder;\n if (typeof cfgFolder === \"string\" && cfgFolder.length) {\n // allow simple interpolations like {{user.id}} if present\n folder = cfgFolder.includes(\"{{\")\n ? interpolate(cfgFolder, row, user, \"Upload folder\")\n : cfgFolder;\n }\n }\n const file = await file_1.default.from_req_files(req.files[field.name], user?.id, (field.attributes && +field.attributes.min_role_read) || 1, folder);\n row[field.name] = file.path_to_serve;\n processed = true;\n }\n }\n return processed;\n };\n if (!row[table.pk_name]) {\n // insert: process uploaded file fields server-side, then insert\n await applyUploadedFiles();\n const result = await table.tryInsertRow(row, user);\n if (result.success)\n return {\n notify_success: req ? req.__(\"Saved\") : \"Saved\",\n set_fields: { [table.pk_name]: result.success },\n };\n else {\n getState().log(3, `form_actions Save failed server side, result: ${JSON.stringify(result)} row ${JSON.stringify(row)}`);\n return { eval_js: `return saveAndContinueAsync(${jqGet})` };\n }\n }\n else {\n // update: if any file uploads present, process server-side and update row\n const hasUploads = await applyUploadedFiles();\n if (hasUploads) {\n const updateres = await table.tryUpdateRow(row, row[table.pk_name], user);\n if (!updateres?.error)\n return { notify_success: req ? req.__(\"Saved\") : \"Saved\" };\n else {\n getState().log(3, `form_actions Save update failed server side, result: ${JSON.stringify(updateres)} row ${JSON.stringify(row)}`);\n return { eval_js: `return saveAndContinueAsync(${jqGet})` };\n }\n }\n return { eval_js: `return saveAndContinueAsync(${jqGet})` };\n }\n case \"Reset\":\n return { eval_js: jqGet + \".trigger('reset')\" };\n case \"Submit with Ajax\":\n return { eval_js: `submitWithAjax(${jqGet})` };\n case \"Ajax Save Form Data\":\n return { eval_js: `ajaxSubmitForm(${jqGet}, true)` };\n default:\n return { eval_js: jqGet + \"[0].requestSubmit()\" };\n }\n },\n namespace: \"User interface\",\n },\n copy_to_clipboard: {\n /**\n * @param {object} opts\n * @param {*} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"Copy text based on the current row to clipboard\",\n requireRow: true,\n configFields: [\n {\n name: \"text_template\",\n label: \"Text template\",\n sublabel: \"Use interpolations <code>{{ }}</code> to access row variables\",\n type: \"String\",\n required: true,\n },\n ],\n run: async ({ row, table, user, req, configuration: { text_template }, ...rest }) => {\n return {\n eval_js: `navigator.clipboard.writeText(${JSON.stringify(interpolate(text_template || \"\", row || {}, user))})`,\n };\n },\n namespace: \"User interface\",\n },\n toast: {\n /**\n * @param {object} opts\n * @param {*} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"Notify the user with a toast\",\n configFields: [\n {\n name: \"notify_type\",\n label: \"Type\",\n type: \"String\",\n required: true,\n attributes: {\n options: [\"Notify\", \"Error\", \"Success\", \"Warning\"],\n },\n },\n {\n name: \"text\",\n label: \"Text\",\n type: \"String\",\n required: true,\n sublabel: \"Interpolations <code>{{ }}</code> can be used\",\n },\n {\n name: \"title\",\n label: \"Title\",\n type: \"String\",\n sublabel: \"Optional. Interpolations <code>{{ }}</code> can be used\",\n },\n {\n name: \"remove_delay\",\n label: \"Remove after (s)\",\n type: \"Float\",\n },\n ],\n run: async ({ row, user, configuration: { type, notify_type, text, title, remove_delay }, }) => {\n //type is legacy. this name gave react problems\n let text1 = interpolate(text, row, user, \"Toast text\");\n let toast_title = title\n ? { toast_title: interpolate(title, row, user, \"Toast title\") }\n : {};\n if (remove_delay)\n toast_title.remove_delay = remove_delay;\n switch (notify_type || type) {\n case \"Error\":\n return { error: text1, ...toast_title };\n case \"Success\":\n return { notify_success: text1, ...toast_title };\n case \"Warning\":\n return { notify: text1, notify_type: \"warning\", ...toast_title };\n default:\n return { notify: text1, ...toast_title };\n }\n },\n namespace: \"User interface\",\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n run_js_code: {\n /**\n * @param {object} opts\n * @param {object} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"Run arbitrary JavaScript code\",\n configuration_summary: (cfg = {}) => {\n const where = cfg.run_where || \"Server\";\n return `Run arbitrary JavaScript code on ${where}`;\n },\n configFormOptions: {\n formStyle: \"vert\",\n },\n configFields: async ({ table, when_trigger, mode }) => {\n const fields = table ? table.getFields().map((f) => f.name) : [];\n const vars = [\n ...(table ? [\"row\"] : []),\n \"user\",\n \"console\",\n \"Actions\",\n a({\n href: \"/admin/jsdoc/classes/_saltcorn_data.models_table.Table.html\",\n target: \"_blank\",\n }, \"Table\"),\n a({\n href: \"/admin/jsdoc/classes/_saltcorn_data.models_file.File.html\",\n target: \"_blank\",\n }, \"File\"),\n a({\n href: \"/admin/jsdoc/classes/_saltcorn_data.models_user.User.html\",\n target: \"_blank\",\n }, \"User\"),\n ...(table ? [\"table\"] : []),\n ...fields,\n ]\n .map((f) => code(f))\n .join(\", \");\n const clientvars = [...fields].map((f) => code(f)).join(\", \");\n const has_user = [\n \"Hourly\",\n \"Weekly\",\n \"Daily\",\n \"Often\",\n \"Startup\",\n ].includes(when_trigger)\n ? undefined\n : \"maybe\";\n return [\n {\n name: \"code\",\n label: \"Code\",\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n table: table?.name || undefined,\n user: has_user,\n workflow: mode === \"workflow\",\n nojoins: true,\n },\n class: `validate-statements strip-types ${mode !== \"workflow\" ? \"enlarge-in-card\" : \"\"}`,\n validator(s) {\n let stripTypes = (s) => s;\n try {\n const { stripTypeScriptTypes } = __webpack_require__(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module 'module'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()));\n if (stripTypeScriptTypes)\n stripTypes = stripTypeScriptTypes;\n }\n catch (e) {\n //ignore\n }\n try {\n let AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;\n AsyncFunction(stripTypes(`async () =>{${s}\n}`));\n return true;\n }\n catch (e) {\n return e.message;\n }\n },\n },\n {\n input_type: \"section_header\",\n label: \" \",\n sublabel: span(\"Variables in scope: \", vars),\n help: {\n topic: \"JavaScript action code\",\n },\n showIf: { run_where: \"Server\" },\n attributes: { secondColHoriz: true },\n },\n {\n input_type: \"section_header\",\n label: \" \",\n sublabel: span(\"Variables in scope: \", clientvars),\n help: {\n topic: \"JavaScript action code\",\n },\n showIf: { run_where: \"Client page\" },\n attributes: { secondColHoriz: true },\n },\n {\n name: \"run_where\",\n label: \"Run where\",\n input_type: \"select\",\n options: [\"Server\", \"Client page\"],\n },\n ];\n },\n /**\n * @type {base-plugin/actions~run_code}\n * @see base-plugin/actions~run_code\n **/\n run: run_code,\n namespace: \"Code\",\n },\n run_js_code_in_field: {\n /**\n * @param {object} opts\n * @param {object} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"Run arbitrary JavaScript code from a String field\",\n configuration_summary: (cfg = {}) => {\n const where = cfg.run_where || \"Server\";\n return `Run JavaScript from context field on ${where}`;\n },\n configFields: async ({ table, mode }) => {\n if (mode === \"workflow\")\n return [\n {\n name: \"code_field\",\n label: \"Code field\",\n sublabel: \"String variable in context contains the JavaScript code to run\",\n type: \"String\",\n required: true,\n },\n {\n name: \"run_where\",\n label: \"Run where\",\n input_type: \"select\",\n options: [\"Server\", \"Client page\"],\n },\n ];\n const field_opts = table.fields\n .filter((f) => f.type_name === \"String\")\n .map((f) => f.name);\n table.fields.forEach((f) => {\n if (f.is_fkey && f.type !== \"File\") {\n const refTable = table_1.default.findOne({ name: f.reftable_name });\n if (!refTable)\n throw new Error(`Unable to find table '${f.reftable_name}`);\n field_opts.push(...refTable.fields\n .filter((jf) => jf.type_name === \"String\")\n .map((jf) => `${f.name}.${jf.name}`));\n }\n });\n return [\n {\n name: \"code_field\",\n label: \"Code field\",\n sublabel: \"String field that contains the JavaScript code to run\",\n type: \"String\",\n required: true,\n attributes: {\n options: field_opts,\n },\n },\n {\n name: \"run_where\",\n label: \"Run where\",\n input_type: \"select\",\n options: [\"Server\", \"Client page\"],\n },\n ];\n },\n requireRow: true,\n /**\n * @type {base-plugin/actions~run_code}\n * @see base-plugin/actions~run_code\n **/\n run: async ({ table, configuration: { code_field, run_where }, row, mode, ...rest }) => {\n let code;\n if (code_field.includes(\".\")) {\n const [ref, target] = code_field.split(\".\");\n if (typeof row[ref] === \"object\")\n code = row[ref][target];\n else if (!row[ref])\n return;\n else {\n const keyfield = table.getField(ref);\n const refTable = table_1.default.findOne({ name: keyfield.reftable_name });\n const refRow = await refTable.getRow({ [table.pk_name]: row[ref] });\n code = refRow[target];\n }\n }\n else\n code = row[code_field];\n code = code || \"\";\n return await run_code({\n ...rest,\n table,\n row,\n configuration: { run_where: run_where || \"Server\", code },\n });\n },\n namespace: \"Code\",\n },\n duplicate_row_prefill_edit: {\n configFields: async ({ table }) => {\n const fields = table ? table.getFields() : [];\n const views = await view_1.default.find_table_views_where(table, ({ viewtemplate, viewrow, state_fields }) => viewrow.viewtemplate === \"Edit\");\n const fldOpts = fields.map((f) => ({\n label: f.name,\n name: `_prefill_${f.name}`,\n default: f.name !== \"id\",\n type: \"Bool\",\n }));\n return [\n {\n name: \"viewname\",\n label: \"View to create\",\n input_type: \"select\",\n options: views.map((v) => v.name),\n },\n ...fldOpts,\n ];\n },\n disableInWorkflow: true,\n requireRow: true,\n run: async ({ row, table, configuration: { viewname, ...flds }, user, }) => {\n const qs = Object.entries(flds)\n .map(([prefill_k, v]) => {\n const k = prefill_k.replace(\"_prefill_\", \"\");\n return v && typeof row[k] !== \"undefined\" && row[k] !== null\n ? `${encodeURIComponent(k)}=${encodeURIComponent(row[k])}`\n : false;\n })\n .filter((s) => s)\n .join(\"&\");\n return { goto: `/view/${viewname}?${qs}` };\n },\n namespace: \"User interface\",\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n set_user_language: {\n description: \"Set the logged-in user's chosen language\",\n configFields: async ({ table }) => [\n {\n name: \"language\",\n type: \"String\",\n required: true,\n attributes: {\n options: Object.entries(available_languages).map(([locale, language]) => ({\n label: language,\n name: locale,\n })),\n },\n },\n ],\n run: async ({ configuration: { language }, user, req, res, }) => {\n if (user?.id) {\n const u = await user_1.default.findForSession({ id: user.id });\n await u.set_language(language);\n req.login(u.session_object, function (err) {\n if (!err) {\n req.flash(\"success\", req.__(\"Language changed to %s\", language));\n return { reload_page: true };\n }\n else {\n req.flash(\"danger\", err);\n }\n });\n }\n else {\n res?.cookie?.(\"lang\", language);\n }\n return { reload_page: true };\n },\n namespace: \"User interface\",\n },\n /**\n * @namespace\n * @category saltcorn-data\n * @subcategory actions\n */\n sync_table_from_external: {\n /**\n * @param {object} opts\n * @param {*} opts.table\n * @returns {Promise<object[]>}\n */\n description: \"Synchronize a database table with an external/provider table by copying rows from the external table\",\n configFields: async ({ table }) => {\n const tables = await table_1.default.find_with_external();\n const pk_options = {};\n for (const table of tables) {\n const fields = table.getFields();\n pk_options[table.name] = fields.map((f) => f.name);\n }\n return [\n {\n name: \"table_src\",\n label: \"Source table\",\n sublabel: \"External table to sync from\",\n input_type: \"select\",\n options: tables\n .filter((t) => t.external || t.provider_name)\n .map((t) => t.name),\n },\n {\n name: \"where\",\n label: \"Query\",\n sublabel: \"Query-expression on source table for subset of rows to synchronize. Example: <code>{ status: 'Open' }</code>\",\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n expression_type: \"query\",\n },\n },\n {\n name: \"table_dest\",\n label: \"Destination table\",\n sublabel: \"Table to sync to\",\n input_type: \"select\",\n options: tables\n .filter((t) => !(t.external || t.provider_name))\n .map((t) => t.name),\n },\n {\n name: \"pk_field\",\n label: \"Primary key field\",\n sublabel: \"Field on destination table to match primary key in source table\",\n type: \"String\",\n required: true,\n attributes: {\n calcOptions: [\"table_dest\", pk_options],\n },\n },\n {\n name: \"delete_rows\",\n label: \"Delete removed rows\",\n sublabel: \"Delete rows that are in the destination table but not in the source table\",\n type: \"Bool\",\n default: true,\n },\n {\n name: \"match_field_names\",\n label: \"Match fields\",\n sublabel: \"Match field names automatically by name or label\",\n type: \"Bool\",\n default: true,\n },\n {\n name: \"row_expr\",\n label: \"Row expression\",\n sublabel: \"Expression for JavaScript object\",\n type: \"String\",\n fieldview: \"textarea\",\n showIf: { match_field_names: false },\n },\n ];\n },\n /**\n * @param {object} opts\n * @param {object} opts.row\n * @param {object} opts.configuration\n * @param {object} opts.user\n * @param {...*} opts.rest\n * @returns {Promise<object|boolean>}\n */\n run: async ({ configuration: { row_expr, table_src, table_dest, pk_field, delete_rows, match_field_names, where, }, user, ...rest }) => {\n // set difference: a - b\n // https://stackoverflow.com/a/36504668/19839414\n const set_diff = (a, b) => new Set([...a].filter((x) => !b.has(x)));\n let set_intersect = (a, b) => new Set([...a].filter((x) => b.has(x)));\n const source_table = table_1.default.findOne({ name: table_src });\n if (!source_table)\n return { error: \"Source table not found\" };\n let q = {};\n if (where)\n q = eval_expression(where, {}, user, \"sync_table_from_external where\");\n const source_rows = await source_table.getRows(q);\n if (!source_rows)\n return { error: \"No data received\" };\n const table_for_insert = table_1.default.findOne({ name: table_dest });\n const dest_rows = await table_for_insert.getRows({});\n const srcPKfield = source_table.fields.find((f) => f.primary_key).name;\n const src_pks = new Set(source_rows.map((r) => r[srcPKfield]));\n const dest_pks = new Set(dest_rows.map((r) => r[pk_field]));\n let match_expr;\n if (match_field_names) {\n const matched_fields = [];\n const dest_fields = table_for_insert.getFields();\n const src_fields = source_table.getFields();\n dest_fields.forEach((df) => {\n const s = src_fields.find((sf) => sf.name === df.name ||\n sf.label === df.label ||\n sf.name === df.label ||\n sf.label === df.name);\n if (s)\n matched_fields.push([df.name, s.name]);\n });\n match_expr = `({${matched_fields\n .map(([d, s]) => `${d}: ${s}`)\n .join(\",\")}})`;\n }\n // new rows\n for (const newPK of Array.from(set_diff(src_pks, dest_pks))) {\n const srcRow = source_rows.find((r) => r[srcPKfield] === newPK);\n const newRow = {\n [pk_field]: newPK,\n ...eval_expression(match_expr || row_expr, srcRow),\n };\n await table_for_insert.insertRow(newRow, user);\n }\n // delete rows\n if (delete_rows)\n await table_for_insert.deleteRows({\n [pk_field]: { in: Array.from(set_diff(dest_pks, src_pks)) },\n }, user);\n //update existing\n for (const existPK of Array.from(set_intersect(src_pks, dest_pks))) {\n const srcRow = source_rows.find((r) => r[srcPKfield] === existPK);\n const newRow = {\n [pk_field]: existPK,\n ...eval_expression(match_expr || row_expr, srcRow),\n };\n const existingRow = dest_rows.find((r) => r[pk_field] === existPK);\n const is_different_for_key = (k) => newRow[k] != existingRow[k];\n if (Object.keys(newRow).some(is_different_for_key)) {\n const upd = {};\n Object.keys(newRow).forEach((k) => {\n if (is_different_for_key(k))\n upd[k] = newRow[k];\n });\n await table_for_insert.updateRow(upd, existingRow[table_for_insert.pk_name], user);\n }\n }\n },\n namespace: \"Database\",\n },\n reload_embedded_view: {\n description: \"Reload an embedded view without full page reload\",\n configFields: async ({ table }) => {\n const views = await view_1.default.find({}, { cached: true });\n return [\n {\n name: \"view\",\n label: \"View to refresh\",\n class: \"selectizable\",\n type: \"String\",\n required: true,\n attributes: { options: views.map((v) => v.select_option) },\n },\n {\n name: \"new_state_fml\",\n label: \"New state formula\",\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n expression_type: \"row\",\n ...(table ? { table: table.name } : {}),\n },\n sublabel: `Optional. Updated view state. Example: <code>{id: 5}</code>. Leave blank to keep existing state`,\n help: {\n topic: \"Extra state formula\",\n context: table ? { srcTable: table.name } : {},\n dynContext: [\"view\"],\n },\n },\n {\n name: \"interval\",\n label: \"Periodic interval (ms)\",\n sublabel: \"Optional. Reload periodically with given interval in milliseconds, if set\",\n type: \"Integer\",\n },\n ];\n },\n run: async ({ row, user, configuration: { view, new_state_fml, interval }, }) => {\n let eval_js = `reload_embedded_view('${view}')`;\n if (new_state_fml) {\n const new_state = eval_expression(new_state_fml, row || {}, user, \"reload_embedded_view new state formula\");\n const newqs = objectToQueryString(new_state);\n eval_js = `reload_embedded_view('${view}', '${newqs}')`;\n }\n if (interval)\n eval_js = `setInterval(()=>{${eval_js}}, ${interval})`;\n return { eval_js };\n },\n namespace: \"User interface\",\n },\n progress_bar: {\n description: \"Display or update progress messages and/or progress bar\",\n configFields: [\n {\n name: \"blocking\",\n label: \"Blocking\",\n sublabel: \"Display progress in a blocking popup modal, If not checked, display as toast\",\n type: \"Bool\",\n },\n {\n name: \"close\",\n label: \"Close\",\n sublabel: \"Close this progress window\",\n type: \"Bool\",\n },\n {\n name: \"id\",\n label: \"Identifier\",\n type: \"String\",\n class: \"validate-identifier\",\n required: true,\n sublabel: \"A valid JavaScript identifier for updating existing progress toasts. Interpolations <code>{{ }}</code> can be used\",\n showIf: { blocking: false },\n },\n {\n name: \"maxHeight\",\n label: \"max-height (px)\",\n type: \"Integer\",\n sublabel: \"If set, progress messages will be added to scrolling container with this maximum height\",\n showIf: { blocking: true, close: false },\n },\n {\n name: \"popupWidth\",\n label: \"Popup width (px)\",\n type: \"Integer\",\n showIf: { blocking: true, close: false },\n },\n {\n name: \"title\",\n label: \"Title\",\n type: \"String\",\n showIf: { close: false },\n sublabel: \"Use interpolations <code>{{ }}</code> to access row variables\",\n },\n {\n name: \"message\",\n label: \"Message\",\n type: \"String\",\n showIf: { close: false },\n sublabel: \"Use interpolations <code>{{ }}</code> to access row variables\",\n },\n {\n name: \"percent\",\n label: \"Percent\",\n type: \"String\",\n sublabel: \"Optional. 0-100. Use interpolations <code>{{ }}</code> to calculate from context\",\n showIf: { close: false },\n },\n ],\n run: async ({ row, user, configuration: { blocking, id, close, title, message, percent, maxHeight, popupWidth, }, req, }) => {\n const msg = interpolate(message, row, user, \"progress_bar message\");\n const title1 = interpolate(title, row, user, \"progress_bar title\");\n const id1 = interpolate(id, row, user, \"progress_bar id\");\n const percent1 = interpolate(percent, row, user, \"progress_bar id\");\n return {\n page_load_tag: req?.headers?.[\"page-load-tag\"],\n progress_bar_update: {\n blocking,\n id: id1,\n close,\n message: msg,\n title: title1,\n percent: +percent1,\n maxHeight,\n popupWidth,\n },\n };\n },\n namespace: \"User interface\",\n },\n sleep: {\n description: \"Delay for a set number of seconds\",\n configFields: [\n {\n name: \"seconds\",\n label: \"Seconds\",\n type: \"Float\",\n required: true,\n },\n {\n name: \"sleep_where\",\n label: \"Sleep where\",\n input_type: \"select\",\n options: [\"Client page\", \"Server\"],\n },\n ],\n run: async ({ configuration: { seconds, sleep_where }, }) => {\n if (sleep_where === \"Server\") {\n await sleep((seconds || 0) * 1000);\n return;\n }\n return {\n eval_js: `return new Promise((resolve) => setTimeout(resolve, ${(seconds || 0) * 1000}));`,\n };\n },\n namespace: \"Control\",\n },\n refresh_user_session: {\n description: \"Refresh the user session with row from the users table\",\n run: async ({ user, req }) => {\n if (!user?.id)\n return;\n const u = await user_1.default.findOne({ id: user.id });\n if (!u)\n return;\n await u.relogin(req);\n },\n namespace: \"Control\",\n },\n notify_user: {\n description: \"Send a notification to a specific user\",\n configFields: ({ table }) => [\n {\n name: \"user_spec\",\n label: \"User where or email\",\n type: \"String\",\n sublabel: `Valid input: <code>*</code> for all, a valid email address, or a where object, e.g. <code>{department: \"Finance\"}</code>${table ? \". Row values are in scope for where object.\" : \"\"}`,\n },\n {\n name: \"title\",\n label: \"Title\",\n required: true,\n type: \"String\",\n sublabel: \"<code>{{ }}</code> interpolations usable\",\n },\n {\n name: \"body\",\n label: \"Body\",\n type: \"String\",\n sublabel: \"<code>{{ }}</code> interpolations usable\",\n },\n {\n name: \"link\",\n label: \"Link\",\n type: \"String\",\n sublabel: \"<code>{{ }}</code> interpolations usable\",\n },\n ],\n /**\n * @param {object} opts\n * @param {object} opts.row\n * @param {object} opts.configuration\n * @param {object} opts.user\n * @returns {Promise<void>}\n */\n run: async ({ row, user, configuration: { title, body, link, user_spec }, }) => {\n const user_where = \n //first two cases are for programmatic use\n typeof user_spec === \"number\"\n ? { id: user_spec }\n : typeof user_spec === \"object\"\n ? user_spec\n : user_1.default.valid_email(user_spec)\n ? { email: user_spec }\n : user_spec === \"*\"\n ? {}\n : eval_expression(user_spec, row || {}, user, \"Notify user user where\");\n const users = await user_1.default.find(user_where);\n for (const user of users) {\n await notification_1.default.create({\n title: interpolate(title, row, user, \"notify_user title\"),\n body: interpolate(body, row, user, \"notify_user body\"),\n link: interpolate(link, row, user, \"notify_user link\"),\n user_id: user.id,\n });\n }\n },\n namespace: \"Communication\",\n },\n convert_session_to_user: {\n description: \"Convert session id fields to user key fields on a table on Login events\",\n configFields: async ({ table }) => {\n const tables = await table_1.default.find({}, { cached: true });\n const sess_options = {};\n const user_options = {};\n for (const table of tables) {\n const fields = table.getFields();\n sess_options[table.name] = fields\n .filter((f) => f.type_name === \"String\")\n .map((f) => f.name);\n user_options[table.name] = fields\n .filter((f) => f.reftable_name === \"users\")\n .map((f) => f.name);\n }\n return [\n {\n name: \"table_name\",\n label: \"Table\",\n sublabel: \"Table with session and user field\",\n input_type: \"select\",\n options: tables.filter((t) => !t.external).map((t) => t.name),\n },\n {\n name: \"session_field\",\n label: \"Session field\",\n sublabel: \"Field containing session IDs\",\n type: \"String\",\n //required: true,\n attributes: {\n calcOptions: [\"table_name\", sess_options],\n },\n },\n {\n name: \"user_field\",\n label: \"User field\",\n sublabel: \"Key to users field which will be filled from matching session ID\",\n type: \"String\",\n //required: true,\n attributes: {\n calcOptions: [\"table_name\", user_options],\n },\n },\n ];\n },\n run: async ({ row, configuration: { table_name, session_field, user_field }, user, }) => {\n if (!row?.old_session_id || !user || !session_field || !user_field)\n return;\n const table = table_1.default.findOne({ name: table_name });\n const rows = await table.getRows({\n [session_field]: row.old_session_id,\n [user_field]: null,\n });\n for (const dbrow of rows) {\n await table.updateRow({ [user_field]: user.id }, dbrow[table.pk_name]);\n }\n },\n namespace: \"Database\",\n },\n train_model_instance: {\n description: \"Train a model instance\",\n disableIf: () => !model_1.default.has_templates,\n configFields: async () => {\n const models = await model_1.default.find({});\n const explainers = {};\n for (const model of models) {\n try {\n const table = table_1.default.findOne({ id: model.table_id });\n if (!model.templateObj)\n continue;\n const hyperparameter_fields = model.templateObj.hyperparameter_fields?.({\n table: table,\n ...model,\n }) || [];\n if (hyperparameter_fields.length)\n explainers[model.id] =\n \"Hyperparamter fields: \" +\n hyperparameter_fields.map((f) => f.name).join(\",\");\n }\n catch {\n //ignore\n }\n }\n return [\n {\n name: \"model_id\",\n label: \"Model\",\n input_type: \"select\",\n required: true,\n options: models.map((model) => ({\n label: `${model.name} [${model.modelpattern} on ${table_1.default.findOne({ id: model.table_id }).name}]`,\n value: model.id,\n })),\n attributes: {\n explainers,\n },\n },\n {\n name: \"instance_name\",\n label: \"Instance name\",\n required: true,\n type: \"String\",\n sublabel: \"Will overwrite instances with exisitng name. Intepolations (<code>{{ }}</code>) available\",\n },\n {\n name: \"where\",\n label: \"Where\",\n fieldview: \"textarea\",\n sublabel: \"Where-expression for subset of rows to train on. Optional\",\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n expression_type: \"query\",\n },\n },\n {\n name: \"hyperparameters\",\n label: \"Hyperparameters\",\n sublabel: \"Optional. JavaScript object with hyperparameter values\",\n type: \"String\",\n fieldview: \"textarea\",\n class: \"validate-expression\",\n },\n ];\n },\n run: async ({ row, configuration: { model_id, instance_name, where, hyperparameters }, user, }) => {\n const use_instance_name = interpolate(instance_name, row || {}, user, \"train_model_instance instance name\");\n const state = where\n ? eval_expression(where, row || {}, user, \"train_model_instance where\")\n : {};\n const hpars = hyperparameters\n ? eval_expression(hyperparameters, row || {}, user, \"train_model_instance hyperparameters\")\n : {};\n const model = await model_1.default.findOne({ id: model_id });\n if (!model)\n throw new Error(\"model not found\");\n const inst = await model.train_instance(use_instance_name, hpars, state);\n if (typeof inst === \"string\")\n throw new Error(inst);\n },\n namespace: \"Database\",\n },\n download_file_to_browser: {\n description: \"Download a file to the user's browser\",\n configFields: async ({ table, mode }) => {\n if (mode === \"workflow\") {\n return [\n {\n name: \"filepath_expr\",\n label: \"File path\",\n class: \"validate-expression\",\n sublabel: 'JavaScript expression, based on the context, for the file path within the file store. If giving a literal filename, enclose in quotes: \"myfile.zip\"',\n type: \"String\",\n },\n ];\n }\n let field_opts = [];\n if (table) {\n field_opts = table.fields\n .filter((f) => f.type === \"File\")\n .map((f) => f.name);\n }\n return [\n {\n name: \"file_field\",\n label: \"File field\",\n type: \"String\",\n required: true,\n attributes: { options: field_opts },\n },\n ];\n },\n run: async ({ row, configuration: { filepath_expr, file_field }, user, mode, }) => {\n let filepath;\n if (mode === \"workflow\") {\n filepath = eval_expression(filepath_expr, row, user, \"download filepath formula\");\n }\n else\n filepath = row[file_field];\n if (!filepath)\n return;\n const file = await file_1.default.findOne(filepath);\n if (!file)\n throw new Error(\"File not found\");\n return {\n download: {\n filename: file.filename,\n mimetype: file.mimetype,\n blob: await file.get_contents(\"base64\"),\n },\n };\n },\n },\n install_progressive_web_app: {\n description: \"Install a Progressive Web Application\",\n configFields: () => [],\n run: async ({ req }) => {\n const pwaEnabled = await getState().getConfig(\"pwa_enabled\", false);\n return pwaEnabled\n ? { eval_js: \"installPWA()\" }\n : { error: req.__(\"Progressive Web Application is not enabled\") };\n },\n },\n};\n//# sourceMappingURL=actions.js.map\n\n//# sourceURL=webpack://saltcorn/../saltcorn-data/dist/base-plugin/actions.js?\n}");
39
-
40
- /***/ },
41
-
42
- /***/ "../saltcorn-data/dist/base-plugin/fileviews.js"
43
- /*!******************************************************!*\
44
- !*** ../saltcorn-data/dist/base-plugin/fileviews.js ***!
45
- \******************************************************/
46
- (module, __unused_webpack_exports, __webpack_require__) {
47
-
48
- "use strict";
49
- eval("{\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\n/**\n * @category saltcorn-data\n * @module base-plugin/fileview\n * @subcategory base-plugin\n */\nconst { a, img, script, domReady, select, input, button, i, div, span, text, text_attr, audio, video, source, textarea, with_curScript, escape: escapeHtml, } = __webpack_require__(/*! @saltcorn/markup/tags */ \"../saltcorn-markup/dist/tags.js\");\nconst { link } = __webpack_require__(/*! @saltcorn/markup */ \"../saltcorn-markup/dist/index.js\");\nconst { isNode } = __webpack_require__(/*! ../utils */ \"../saltcorn-data/dist/utils.js\");\nconst { select_options } = __webpack_require__(/*! @saltcorn/markup/helpers */ \"../saltcorn-markup/dist/helpers.js\");\nconst file_1 = __importDefault(__webpack_require__(/*! ../models/file */ \"../saltcorn-data/dist/models/file.js\"));\nconst path_1 = __importDefault(__webpack_require__(/*! path */ \"../../node_modules/path-browserify/index.js\"));\nconst { getReq__ } = __webpack_require__(/*! ../db/state */ \"../saltcorn-data/dist/db/state.js\");\nconst buildNodeFileUrl = (filePath, cfg = {}, opts = {}) => file_1.default.pathToServeUrl(filePath, {\n download: opts.download,\n filename: opts.filename,\n targetPrefix: cfg.targetPrefix || \"\",\n preferDirect: opts.preferDirect,\n});\nconst buildNodeFileLinkUrl = (filePath, cfg = {}) => buildNodeFileUrl(filePath, cfg);\nconst btnStyles = [\n { name: \"default\", label: \"Default selector\" },\n { name: \"btn btn-primary\", label: \"Primary button\" },\n { name: \"btn btn-secondary\", label: \"Secondary button\" },\n { name: \"btn btn-success\", label: \"Success button\" },\n { name: \"btn btn-danger\", label: \"Danger button\" },\n { name: \"btn btn-warning\", label: \"Warning button\" },\n { name: \"btn btn-info\", label: \"Info button\" },\n {\n name: \"btn btn--outline-primary\",\n label: \"Primary outline button\",\n },\n {\n name: \"btn btn--outline-decondary\",\n label: \"Secondary outline button\",\n },\n];\nconst btnStylesForLink = [\n { name: \" \", label: \"Link\" },\n { name: \"btn btn-primary\", label: \"Primary button\" },\n { name: \"btn btn-secondary\", label: \"Secondary button\" },\n { name: \"btn btn-success\", label: \"Success button\" },\n { name: \"btn btn-danger\", label: \"Danger button\" },\n { name: \"btn btn-warning\", label: \"Warning button\" },\n { name: \"btn btn-info\", label: \"Info button\" },\n {\n name: \"btn btn-outline-primary\",\n label: \"Primary outline button\",\n },\n {\n name: \"btn btn-outline-decondary\",\n label: \"Secondary outline button\",\n },\n];\nconst buildCustomInput = (id, attrs, file_name) => {\n const __ = getReq__();\n return (button({\n type: \"button\",\n id: `${id}-custom-button`,\n class: attrs.button_style,\n onclick: `$(this).parent().find(\"input\").click()`,\n }, attrs?.label ? attrs.label : __(\"Choose File\")) +\n span({\n id: `${id}-custom-text`,\n class: \"custom-file-label\",\n }, !file_name ? __(\"No file chosen\") : \"\"));\n};\nconst fileviews = {\n // download link\n \"Download link\": {\n configFields: [\n {\n name: \"button_style\",\n label: \"Style\",\n type: \"String\",\n required: true,\n attributes: {\n options: btnStylesForLink,\n },\n },\n ],\n description: \"Link to download file\",\n run: (filePath, file_name, cfg = {}) => {\n if (!filePath)\n return \"\";\n return link(isNode()\n ? buildNodeFileUrl(filePath, cfg, {\n download: true,\n filename: file_name,\n })\n : `javascript:notifyAlert('File donwloads are not supported.')`, path_1.default.basename(filePath) || \"Download\", cfg?.button_style && cfg?.button_style !== \" \"\n ? { class: cfg?.button_style }\n : undefined);\n },\n },\n // Link\n Link: {\n configFields: [\n {\n name: \"button_style\",\n label: \"Style\",\n type: \"String\",\n required: true,\n attributes: {\n options: btnStylesForLink,\n },\n },\n ],\n description: \"Link to open file\",\n run: (filePath, file_name, cfg = {}) => !filePath\n ? \"\"\n : link(isNode()\n ? buildNodeFileLinkUrl(filePath, cfg)\n : `javascript:openFile('${filePath}')`, path_1.default.basename(filePath) || \"Open\", cfg?.button_style && cfg?.button_style !== \" \"\n ? { class: cfg?.button_style }\n : undefined),\n },\n // Link (new tab)\n \"Link (new tab)\": {\n description: \"Link to open file in new tab\",\n configFields: [\n {\n name: \"button_style\",\n label: \"Style\",\n type: \"String\",\n required: true,\n attributes: {\n options: btnStylesForLink,\n },\n },\n ],\n run: (filePath, file_name, cfg = {}) => !filePath\n ? \"\"\n : link(isNode()\n ? buildNodeFileLinkUrl(filePath, cfg)\n : `javascript:openFile('${filePath}')`, path_1.default.basename(filePath) || \"Open\", cfg?.button_style && cfg?.button_style !== \" \"\n ? { target: \"_blank\", class: cfg?.button_style }\n : { target: \"_blank\" }),\n },\n // Show Image\n \"Show Image\": {\n description: \"Show the file as an image\",\n configFields: [\n {\n name: \"imgResponsiveWidths\",\n label: \"Responsive widths\",\n type: \"String\",\n sublabel: \"List of widths to serve resized images, e.g. 300, 400, 600\",\n },\n ],\n run: (filePath, file_name, cfg = {}) => {\n if (!filePath)\n return \"\";\n if (isNode())\n return img({\n // Prefer proxied /files/serve URLs so CSP does not block inline images\n src: buildNodeFileUrl(filePath, cfg, { preferDirect: false }),\n style: \"width: 100%\",\n srcset: cfg?.imgResponsiveWidths &&\n filePath &&\n cfg.imgResponsiveWidths\n .split(\",\")\n .map((w) => `/files/resize/${w.trim()}/0/${encodeURIComponent(filePath)} ${w.trim()}w`)\n .join(\",\"),\n });\n else {\n return img({\n \"mobile-img-path\": filePath,\n style: \"width: 100%\",\n });\n }\n },\n },\n // upload\n upload: {\n description: \"Upload the file\",\n isEdit: true,\n multipartFormData: true,\n valueIsFilename: true,\n configFields: async () => {\n const dirs = await file_1.default.allDirectories();\n return [\n {\n name: \"folder\",\n label: \"Folder\",\n type: \"String\",\n attributes: { options: dirs.map((d) => d.path_to_serve) },\n },\n {\n name: \"button_style\",\n label: \"Button Style\",\n type: \"String\",\n attributes: {\n options: btnStyles,\n },\n required: true,\n default: \"default\",\n },\n {\n name: \"label\",\n label: \"Button Label\",\n type: \"String\",\n showIf: {\n button_style: btnStyles\n .filter((opt) => opt.name !== \"default\")\n .map((opt) => opt.name),\n },\n },\n ];\n },\n run: (nm, file_name, attrs, cls, reqd, field) => {\n //console.log(\"in run attrs.files_accept_filter\", attrs.files_accept_filter);\n const customInput = attrs?.button_style &&\n attrs.button_style !== \"default\" &&\n attrs.button_style !== \" \";\n const id = `input${text_attr(nm)}`;\n return (input({\n class: [\n \"form-control\",\n cls,\n field.class,\n file_name && \"file-has-existing\",\n ],\n \"data-fieldname\": field.form_name,\n name: text_attr(nm),\n id: id,\n type: \"file\",\n disabled: attrs.disabled,\n onChange: attrs.onChange,\n readonly: attrs.readonly,\n \"data-on-cloned\": \"clear_cloned_file_input(this)\",\n accept: attrs.files_accept_filter || undefined,\n ...(customInput ? { hidden: true } : {}),\n ...(customInput\n ? {\n onchange: `$(this).parent().find('span').text(event.target.files[0].name);${attrs.onChange || \"\"}`,\n }\n : attrs.onChange),\n }) +\n (customInput ? buildCustomInput(id, attrs, file_name) : \"\") +\n span({ class: \"file-upload-exising\" }, text(file_name || \"\")));\n },\n },\n // select\n select: {\n isEdit: true,\n setsFileId: true,\n description: \"Select existing file\",\n fill_options: async (field) => {\n const files = await file_1.default.find(field.attributes.folder\n ? { folder: field.attributes.folder }\n : field.attributes.select_file_where || {});\n const extRe = field.attributes.file_exts &&\n new RegExp(`\\\\.(${field.attributes.file_exts\n .split(\",\")\n .map((s) => s.trim())\n .join(\"|\")})$`, \"i\");\n field.options = files\n .filter((f) => !f.isDirectory && (!extRe || extRe.test(f.path_to_serve)))\n .map((f) => ({\n label: f.filename,\n value: f.path_to_serve,\n }));\n if (!this?.required)\n field.options.unshift({ label: \"\", value: \"\" });\n },\n configFields: async () => {\n const dirs = await file_1.default.allDirectories();\n return [\n {\n name: \"folder\",\n label: \"Folder\",\n type: \"String\",\n attributes: { options: dirs.map((d) => d.path_to_serve) },\n },\n {\n name: \"use_picker\",\n label: \"Use picker\",\n sublabel: \"Use the file picker dialog\",\n type: \"Bool\",\n default: false,\n },\n {\n name: \"show_subdirs\",\n type: \"Bool\",\n label: \"navigate subdirectories\",\n sublabel: \"Show and allow to navigate directories\",\n showIf: { use_picker: true },\n default: false,\n },\n {\n name: \"file_exts\",\n label: \"File extensions\",\n type: \"String\",\n subfolder: \"Comma separated file extensions. Example: <code>jpg,png</code>\",\n } /*\n {\n name: \"mime_regex\",\n label: \"MIME regex\",\n type: \"String\"\n }*/,\n ];\n },\n // run\n run: (nm, file_id, attrs, cls, reqd, field) => {\n if (attrs?.use_picker) {\n const folder = attrs?.folder || \"\";\n const inputId = `input${text_attr(nm)}__${Math.floor(Math.random() * 16777215).toString(16)}`;\n return span(a({\n class: \"btn btn-secondary\",\n href: `javascript:ajax_modal('/files/picker?folder=${encodeURIComponent(folder)}&input_id=${encodeURIComponent(inputId)}${attrs?.show_subdirs === false ? \"&no_subdirs=true\" : \"\"}${attrs?.file_exts ? \"&file_exts=\" + attrs?.file_exts : \"\"}')`,\n }, \"select\"), span({\n id: `${inputId}-custom-text`,\n class: \"custom-file-label\",\n }, file_id || \"No file chosen\"), input({\n type: \"hidden\",\n id: inputId,\n name: text_attr(nm),\n \"data-fieldname\": field.form_name,\n value: file_id || false,\n }));\n }\n else {\n return select({\n class: `form-control form-select selectizable ${cls} ${field.class || \"\"}`,\n \"data-fieldname\": field.form_name,\n name: text_attr(nm),\n id: `input${text_attr(nm)}`,\n }, select_options(file_id, field, (attrs || {}).force_required, // todo force_required is unresolved!\n (attrs || {}).neutral_label));\n }\n },\n },\n // Capture\n Capture: {\n isEdit: true,\n multipartFormData: true,\n valueIsFilename: true,\n description: \"Capture image, audio, or video with the user's camera or microphone\",\n configFields: async () => {\n const dirs = await file_1.default.allDirectories();\n return [\n {\n name: \"folder\",\n label: \"Folder\",\n type: \"String\",\n attributes: { options: dirs.map((d) => d.path_to_serve) },\n },\n {\n name: \"device\",\n label: \"Device\",\n type: \"String\",\n required: true,\n attributes: { options: [\"camera\", \"camcorder\", \"microphone\"] },\n },\n {\n name: \"button_style\",\n label: \"Button Style\",\n type: \"String\",\n attributes: {\n options: btnStyles,\n },\n default: \"default\",\n },\n {\n name: \"label\",\n label: \"Button Label\",\n type: \"String\",\n showIf: {\n button_style: btnStyles\n .filter((opt) => opt.name !== \"default\")\n .map((opt) => opt.name),\n },\n },\n ];\n },\n run: (nm, file_name, attrs, cls, reqd, field) => {\n const customInput = attrs?.button_style && attrs.button_style !== \"default\";\n const id = `input${text_attr(nm)}`;\n if (attrs.device === \"camera\" && attrs.isMobile) {\n return div({ class: \"text-nowrap overflow-hidden text-truncate\" }, button({\n id: `cptbtn${text_attr(nm)}`,\n class: attrs?.button_style\n ? attrs.button_style\n : \"btn btn-primary\",\n onclick: `getPicture('${text_attr(nm)}')`,\n }, \"use camera\", i({ class: \"ms-2 fas fa-camera\" })), span({ class: \"ms-2\", id: `cpt-file-name-${text_attr(nm)}` }, \"\"));\n }\n else {\n const mimebase = {\n camera: \"image\",\n camcorder: \"video\",\n microphone: \"audio\",\n }[attrs.device];\n return (input({\n class: `${cls} ${field.class || \"\"}`,\n \"data-fieldname\": field.form_name,\n name: text_attr(nm),\n id: id,\n type: \"file\",\n \"data-on-cloned\": \"$(this).val('')\",\n accept: `${mimebase}/*;capture=${attrs.device}`,\n ...(customInput ? { hidden: true } : {}),\n }) + (customInput ? buildCustomInput(id, attrs) : \"\"));\n }\n },\n },\n // Thumbnail\n Thumbnail: {\n configFields: () => [\n { name: \"width\", type: \"Integer\", required: true, label: \"Width (px)\" },\n { name: \"height\", type: \"Integer\", label: \"Height (px)\" },\n { name: \"expand\", type: \"Bool\", label: \"Click to expand\" },\n ],\n description: \"Show the image file as small thumbnail image\",\n run: (filePath, file_name, cfg = {}) => {\n const { width, height, expand, targetPrefix } = cfg || {};\n if (!filePath)\n return \"\";\n if (isNode())\n return img({\n src: `${targetPrefix || \"\"}/files/resize/${width || 50}/${height || 0}/${filePath}`,\n onclick: expand\n ? `expand_thumbnail('${filePath}', '${path_1.default.basename(filePath)}')`\n : undefined,\n });\n else {\n // TODO resizer on mobile?\n const style = { width: `${width || 50}px` };\n if (height)\n style.height = `${height}px`;\n return img({\n \"mobile-img-path\": filePath,\n style,\n });\n }\n },\n },\n Audio: {\n description: \"Simple audio player\",\n run: (filePath, file_name, cfg = {}) => {\n if (!filePath)\n return \"\";\n return audio({\n src: buildNodeFileUrl(filePath, cfg),\n controls: true,\n });\n },\n },\n Video: {\n description: \"Simple video player\",\n configFields: [\n { name: \"width\", type: \"Integer\", label: \"Width (px)\" },\n { name: \"height\", type: \"Integer\", label: \"Height (px)\" },\n { name: \"controls\", type: \"Bool\", label: \"Controls\" },\n { name: \"autoplay\", type: \"Bool\", label: \"Autoplay\" },\n { name: \"muted\", type: \"Bool\", label: \"Muted\" },\n { name: \"loop\", type: \"Bool\", label: \"Loop\" },\n { name: \"fullscreen\", type: \"Bool\", label: \"Full screen\" },\n ],\n run: (filePath, file_name, cfg = {}) => {\n if (!filePath)\n return \"\";\n return video({\n controls: cfg.controls,\n muted: cfg.muted,\n loop: cfg.loop,\n autoplay: cfg.autoplay,\n width: cfg.width || undefined,\n height: cfg.height || undefined,\n style: cfg.fullscreen\n ? `height: 100vh; width: 100%; object-fit: fill; position: absolute;`\n : undefined,\n }, source({\n src: buildNodeFileUrl(filePath, cfg),\n type: file_1.default.nameToMimeType(filePath),\n }));\n },\n },\n TextEditor: {\n isEdit: true,\n multipartFormData: true,\n editContent: true,\n description: \"Capture image, audio, or video with the user's camera or microphone\",\n configFields: async () => {\n const dirs = await file_1.default.allDirectories();\n return [\n {\n name: \"edit_file_name\",\n label: \"Edit file name\",\n type: \"String\",\n required: true,\n attributes: { options: [\"Never\", \"Always\", \"Only if new file\"] },\n },\n ];\n },\n run: (nm, file_name, attrs, cls, reqd, field, row) => {\n //console.trace({ nm, file_name, attrs, cls, reqd, field, row });\n const contents = row?.[`_content_${nm}`]?.toString?.() || \"\";\n const edit_file_name = attrs?.edit_file_name === \"Always\" ||\n (attrs?.edit_file_name === \"Only if new file\" && !file_name);\n return (input({\n type: edit_file_name ? \"text\" : \"hidden\",\n class: edit_file_name ? \"form-control\" : undefined,\n placeholder: edit_file_name ? \"File name\" : undefined,\n name: text_attr(nm),\n \"data-fieldname\": text_attr(field.name),\n value: file_name ? text_attr(file_name) : undefined,\n }) +\n textarea({\n name: `_content_${text_attr(nm)}`,\n class: [\"form-control\", \"to-code\", cls],\n disabled: attrs.disabled,\n onChange: attrs.onChange,\n readonly: attrs.readonly,\n placeholder: attrs.placeholder,\n spellcheck: \"false\",\n required: !!reqd,\n id: `input${text_attr(nm)}`,\n mode: file_name ? file_1.default.nameToMimeType(file_name) : undefined,\n }, escapeHtml(contents)));\n },\n },\n};\nmodule.exports = fileviews;\n//# sourceMappingURL=fileviews.js.map\n\n//# sourceURL=webpack://saltcorn/../saltcorn-data/dist/base-plugin/fileviews.js?\n}");
50
-
51
- /***/ },
52
-
53
- /***/ "../saltcorn-data/dist/base-plugin/index.js"
54
- /*!**************************************************!*\
55
- !*** ../saltcorn-data/dist/base-plugin/index.js ***!
56
- \**************************************************/
57
- (module, __unused_webpack_exports, __webpack_require__) {
58
-
59
- "use strict";
60
- eval("{\n/**\n * @category saltcorn-data\n * @module base-plugin/index\n * @subcategory base-plugin\n */\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\n/**\n * All files in the base-plugin module.\n * @namespace base-plugin_overview\n * @property {module:base-plugin/actions} actions\n * @property {module:base-plugin/fieldviews} fieldviews\n * @property {module:base-plugin/fileview} fileview\n * @property {module:base-plugin/types} types\n *\n * @property {module:base-plugin/viewtemplates/edit} edit\n * @property {module:base-plugin/viewtemplates/feed} feed\n * @property {module:base-plugin/viewtemplates/filter} filter\n * @property {module:base-plugin/viewtemplates/list} list\n * @property {module:base-plugin/viewtemplates/listshowlist} listshowlist\n * @property {module:base-plugin/viewtemplates/room} room\n * @property {module:base-plugin/viewtemplates/show} show\n * @property {module:base-plugin/viewtemplates/viewable_fields} viewable_fields\n *\n *\n * @category saltcorn-data\n * @subcategory base-plugin\n */\n// import listshowlist = require(\"./viewtemplates/listshowlist\");\n// import list = require(\"./viewtemplates/list\");\n// import show = require(\"./viewtemplates/show\");\n// import feed = require(\"./viewtemplates/feed\");\n// import room = require(\"./viewtemplates/room\");\n// import wfroom = require(\"./viewtemplates/workflow-room\");\n// import edit = require(\"./viewtemplates/edit\");\n// import filter = require(\"./viewtemplates/filter\");\n// import fileviews = require(\"./fileviews\");\n// import fieldviews = require(\"./fieldviews\");\n// import actions = require(\"./actions\");\nconst listshowlist_1 = __importDefault(__webpack_require__(/*! ./viewtemplates/listshowlist */ \"../saltcorn-data/dist/base-plugin/viewtemplates/listshowlist.js\"));\nconst list_1 = __importDefault(__webpack_require__(/*! ./viewtemplates/list */ \"../saltcorn-data/dist/base-plugin/viewtemplates/list.js\"));\nconst show_1 = __importDefault(__webpack_require__(/*! ./viewtemplates/show */ \"../saltcorn-data/dist/base-plugin/viewtemplates/show.js\"));\nconst feed_1 = __importDefault(__webpack_require__(/*! ./viewtemplates/feed */ \"../saltcorn-data/dist/base-plugin/viewtemplates/feed.js\"));\nconst room_1 = __importDefault(__webpack_require__(/*! ./viewtemplates/room */ \"../saltcorn-data/dist/base-plugin/viewtemplates/room.js\"));\nconst workflow_room_1 = __importDefault(__webpack_require__(/*! ./viewtemplates/workflow-room */ \"../saltcorn-data/dist/base-plugin/viewtemplates/workflow-room.js\"));\nconst edit_1 = __importDefault(__webpack_require__(/*! ./viewtemplates/edit */ \"../saltcorn-data/dist/base-plugin/viewtemplates/edit.js\"));\nconst filter_1 = __importDefault(__webpack_require__(/*! ./viewtemplates/filter */ \"../saltcorn-data/dist/base-plugin/viewtemplates/filter.js\"));\nconst fileviews_1 = __importDefault(__webpack_require__(/*! ./fileviews */ \"../saltcorn-data/dist/base-plugin/fileviews.js\"));\nconst fieldviews_1 = __importDefault(__webpack_require__(/*! ./fieldviews */ \"../saltcorn-data/dist/base-plugin/fieldviews.js\"));\nconst actions_1 = __importDefault(__webpack_require__(/*! ./actions */ \"../saltcorn-data/dist/base-plugin/actions.js\"));\nconst types_1 = __importDefault(__webpack_require__(/*! ./types */ \"../saltcorn-data/dist/base-plugin/types.js\"));\nconst { string, int, bool, date, float, color } = types_1.default;\nconst viewtemplates = [\n list_1.default,\n edit_1.default,\n show_1.default,\n listshowlist_1.default,\n feed_1.default,\n filter_1.default,\n room_1.default,\n workflow_room_1.default,\n];\nmodule.exports = {\n /** @type {number} */\n sc_plugin_api_version: 1,\n /** @type {object[]} */\n types: [string, int, bool, date, float, color],\n /** @type {object[]} */\n viewtemplates,\n /** @type {base-plugin/fileviews} */\n fileviews: fileviews_1.default,\n /** @type {base-plugin/actions} */\n actions: actions_1.default,\n /** @type {base-plugin/fieldviews} */\n fieldviews: fieldviews_1.default,\n /** @type {object} */\n};\n//# sourceMappingURL=index.js.map\n\n//# sourceURL=webpack://saltcorn/../saltcorn-data/dist/base-plugin/index.js?\n}");
61
-
62
- /***/ },
63
-
64
- /***/ "../saltcorn-data/dist/base-plugin/viewtemplates/edit.js"
65
- /*!***************************************************************!*\
66
- !*** ../saltcorn-data/dist/base-plugin/viewtemplates/edit.js ***!
67
- \***************************************************************/
68
- (module, __unused_webpack_exports, __webpack_require__) {
69
-
70
- "use strict";
71
- eval("{\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\n/**\n * @category saltcorn-data\n * @module base-plugin/viewtemplates/edit\n * @subcategory base-plugin\n */\nconst field_1 = __importDefault(__webpack_require__(/*! ../../models/field */ \"../saltcorn-data/dist/models/field.js\"));\nconst table_1 = __importDefault(__webpack_require__(/*! ../../models/table */ \"../saltcorn-data/dist/models/table.js\"));\nconst user_1 = __importDefault(__webpack_require__(/*! ../../models/user */ \"../saltcorn-data/dist/models/user.js\"));\nconst crash_1 = __importDefault(__webpack_require__(/*! ../../models/crash */ \"../saltcorn-data/dist/models/crash.js\"));\nconst form_1 = __importDefault(__webpack_require__(/*! ../../models/form */ \"../saltcorn-data/dist/models/form.js\"));\nconst page_1 = __importDefault(__webpack_require__(/*! ../../models/page */ \"../saltcorn-data/dist/models/page.js\"));\nconst view_1 = __importDefault(__webpack_require__(/*! ../../models/view */ \"../saltcorn-data/dist/models/view.js\"));\nconst workflow_1 = __importDefault(__webpack_require__(/*! ../../models/workflow */ \"../saltcorn-data/dist/models/workflow.js\"));\nconst trigger_1 = __importDefault(__webpack_require__(/*! ../../models/trigger */ \"../saltcorn-data/dist/models/trigger.js\"));\nconst file_1 = __importDefault(__webpack_require__(/*! ../../models/file */ \"../saltcorn-data/dist/models/file.js\"));\nconst PageGroup = __webpack_require__(/*! ../../models/page_group */ \"../saltcorn-data/dist/models/page_group.js\");\nconst FieldRepeat = __webpack_require__(/*! ../../models/fieldrepeat */ \"../saltcorn-data/dist/models/fieldrepeat.js\");\nconst Library = __webpack_require__(/*! ../../models/library */ \"../saltcorn-data/dist/models/library.js\");\nconst { getState } = __webpack_require__(/*! ../../db/state */ \"../saltcorn-data/dist/db/state.js\");\nconst tags_1 = __webpack_require__(/*! @saltcorn/markup/tags */ \"../saltcorn-markup/dist/tags.js\");\nconst { renderForm } = __webpack_require__(/*! @saltcorn/markup */ \"../saltcorn-markup/dist/index.js\");\nconst expression_1 = __importDefault(__webpack_require__(/*! ../../models/expression */ \"../saltcorn-data/dist/models/expression.js\"));\nconst { get_expression_function, expressionChecker, eval_expression, freeVariables, freeVariablesInInterpolation, add_free_variables_to_aggregations, } = expression_1.default;\nconst utils_1 = __importDefault(__webpack_require__(/*! ../../utils */ \"../saltcorn-data/dist/utils.js\"));\nconst { InvalidConfiguration, isNode, isWeb, isTest, mergeIntoWhere, interpolate, asyncMap, removeEmptyStrings, structuredClone, is_relative_url, toSafeRelativeUrl, } = utils_1.default;\nconst plugin_testing_1 = __webpack_require__(/*! ../../plugin-testing */ \"../saltcorn-data/dist/mobile-mocks/saltcorn/plugin-testing.js\");\nconst plugin_helper_1 = __webpack_require__(/*! ../../plugin-helper */ \"../saltcorn-data/dist/plugin-helper.js\");\nconst viewable_fields_1 = __webpack_require__(/*! ../../viewable_fields */ \"../saltcorn-data/dist/viewable_fields.js\");\nconst layout_1 = __importDefault(__webpack_require__(/*! ../../models/layout */ \"../saltcorn-data/dist/models/layout.js\"));\nconst { traverse, getStringsForI18n, traverseSync, splitLayoutContainerFields, findLayoutBranchWith, } = layout_1.default;\nconst node_extract_utils_1 = __webpack_require__(/*! ../../diagram/node_extract_utils */ \"../saltcorn-data/dist/diagram/node_extract_utils.js\");\nconst db_1 = __importDefault(__webpack_require__(/*! ../../db */ \"../saltcorn-data/dist/db/index.js\"));\nconst { Relation, RelationType } = __webpack_require__(/*! @saltcorn/common-code */ \"../common-code/dist/index.js\");\n/**\n * @param req\n * @returns\n */\nconst configuration_workflow = (req) => new workflow_1.default({\n steps: [\n {\n name: req.__(\"Layout\"),\n builder: async (context) => {\n const table = table_1.default.findOne({ id: context.table_id });\n const fields = table\n .getFields()\n .filter((f) => !f.primary_key || f.attributes?.NonSerial);\n for (const field of fields) {\n if (field.type === \"Key\") {\n field.reftable = table_1.default.findOne({\n name: field.reftable_name,\n });\n if (field.reftable)\n await field.reftable.getFields();\n }\n }\n const { field_view_options, handlesTextStyle, blockDisplay } = (0, plugin_helper_1.calcfldViewOptions)(fields, \"edit\");\n const roles = await user_1.default.get_roles();\n const images = await file_1.default.find({ mime_super: \"image\" });\n const stateActions = Object.entries(getState().actions).filter(([k, v]) => !v.disableInBuilder && !v.disableIf?.());\n const triggerActions = trigger_1.default.trigger_actions({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n });\n const actions = trigger_1.default.action_options({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n forBuilder: true,\n builtInLabel: \"Edit Actions\",\n builtIns: viewable_fields_1.edit_build_in_actions,\n });\n const actionConfigForms = {\n Delete: [\n {\n name: \"after_delete_url\",\n label: req.__(\"URL after delete\"),\n type: \"String\",\n },\n ],\n GoBack: [\n {\n name: \"save_first\",\n label: req.__(\"Save before going back\"),\n type: \"Bool\",\n },\n {\n name: \"reload_after\",\n label: req.__(\"Reload after going back\"),\n type: \"Bool\",\n },\n {\n name: \"steps\",\n label: req.__(\"Steps to go back\"),\n type: \"Integer\",\n default: 1,\n },\n ],\n };\n const actionDescriptions = {\n Save: req.__(\"Submit the form and save, redirecting to the destination\"),\n SaveAndContinue: req.__(\"Save the form contents on the server and stay on the current page\"),\n SubmitWithAjax: req.__(\"Submit the form with Ajax and follow the set destination if there are no errors\"),\n UpdateMatchingRows: req.__(\"Update rows in the table matching the filter state with the values in the form\"),\n Reset: req.__(\"Reset the form to the state it was in at page load\"),\n GoBack: req.__(\"Navigate back to the previous page\"),\n Delete: req.__(\"Delete the current row\"),\n Cancel: req.__(\"Cancel form editing and go back\"),\n };\n for (const [name, action] of stateActions) {\n if (action.configFields) {\n actionConfigForms[name] = await (0, plugin_helper_1.getActionConfigFields)(action, table, { mode: \"edit\", req });\n }\n if (action.description)\n actionDescriptions[name] = action.description;\n }\n const workflowActions = trigger_1.default.trigger_actions({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n onlyWorkflows: true,\n });\n for (const name of workflowActions) {\n actionConfigForms[name] = [\n {\n name: \"initial_context\",\n label: \"Additional context\",\n type: \"String\",\n class: \"validate-expression\",\n },\n ];\n }\n if (table.name === \"users\") {\n actions.push(\"Login\");\n actions.push(\"Sign up\");\n Object.entries(getState().auth_methods).forEach(([k, v]) => {\n actions.push(`Login with ${k}`);\n });\n fields.push(new field_1.default({\n name: \"password\",\n label: req.__(\"Password\"),\n type: \"String\",\n }));\n fields.push(new field_1.default({\n name: \"passwordRepeat\",\n label: req.__(\"Password Repeat\"),\n type: \"String\",\n }));\n fields.push(new field_1.default({\n name: \"remember\",\n label: req.__(\"Remember me\"),\n type: \"Bool\",\n }));\n field_view_options.password = [\"password\"];\n field_view_options.passwordRepeat = [\"password\"];\n field_view_options.remember = [\"edit\"];\n }\n const library = (await Library.find({})).filter((l) => l.suitableFor(\"edit\"));\n const myviewrow = view_1.default.findOne({ name: context.viewname });\n const { parent_field_list } = await table.get_parent_relations(true, true);\n const pages = await page_1.default.find();\n const groups = (await PageGroup.find()).map((g) => ({\n name: g.name,\n }));\n const { on_done_redirect, ...current_filter_state } = req.query;\n return {\n tableName: table.name,\n fields: fields.map((f) => f.toBuilder || f),\n field_view_options,\n parent_field_list,\n handlesTextStyle,\n blockDisplay,\n current_filter_state,\n roles,\n actions,\n triggerActions,\n builtInActions: viewable_fields_1.edit_build_in_actions,\n actionConfigForms,\n actionDescriptions,\n images,\n allowMultiStepAction: true,\n min_role: (myviewrow || {}).min_role,\n library,\n mode: \"edit\",\n ownership: !!table.ownership_field_id ||\n !!table.ownership_formula ||\n table.name === \"users\",\n excluded_subview_templates: [\"Room\"],\n pages,\n page_groups: groups,\n has_copilot_generate: !!getState().functions.copilot_generate_layout,\n };\n },\n },\n {\n name: req.__(\"Fixed and blocked fields\"),\n contextField: \"fixed\",\n onlyWhen: async (context) => {\n const table = table_1.default.findOne({ id: context.table_id });\n const fields = table.getFields();\n const in_form_fields = context.columns.map((f) => f.field_name);\n return fields.some((f) => !in_form_fields.includes(f.name) &&\n !f.calculated &&\n !f.primary_key);\n },\n form: async (context) => {\n const table = table_1.default.findOne({ id: context.table_id });\n const fields = table.getFields();\n const in_form_fields = context.columns.map((f) => f.field_name);\n const omitted_fields = fields.filter((f) => !in_form_fields.includes(f.name) &&\n !f.calculated &&\n !f.primary_key);\n const formFields = [];\n const blockFields = [];\n omitted_fields.forEach((f) => {\n f.required = false;\n if (f.type?.name === \"Bool\") {\n f.fieldview = \"tristate\";\n }\n formFields.push(f);\n if (f.presets) {\n formFields.push(new field_1.default({\n name: \"preset_\" + f.name,\n label: req.__(\"Preset %s\", f.label),\n type: \"String\",\n attributes: { options: Object.keys(f.presets) },\n }));\n }\n blockFields.push({\n name: `_block_${f.name}`,\n type: \"Bool\",\n label: f.label,\n });\n });\n const form = new form_1.default({\n fields: [\n {\n input_type: \"section_header\",\n label: req.__(\"These fields were missing, you can give values here. The values you enter here can be overwritten by information coming from other views, for instance if the form is triggered from a list.\"),\n },\n ...formFields,\n {\n input_type: \"section_header\",\n label: req.__(\"Do not allow the following fields to have a value set from the query string or state\"),\n },\n ...blockFields,\n ],\n });\n await form.fill_fkey_options();\n return form;\n },\n },\n {\n name: req.__(\"Edit options\"),\n form: async (context) => {\n const own_views = await view_1.default.find_all_views_where(({ state_fields, viewrow }) => viewrow.table_id === context.table_id ||\n state_fields.every((sf) => !sf.required));\n const table = table_1.default.findOne({ id: context.table_id });\n own_views.forEach((v) => {\n if (!v.table && v.table_id === table.id)\n v.table = table;\n else if (!v.table && v.table_id) {\n const vtable = table_1.default.findOne({ id: v.table_id });\n v.table = vtable;\n }\n });\n const parent_views = await (0, plugin_helper_1.get_parent_views)(table, context.viewname);\n const done_view_opts = own_views.map((v) => v.select_option);\n parent_views.forEach(({ relation, related_table, views }) => views.forEach((v) => {\n done_view_opts.push(`${v.name}.${relation.name}`);\n }));\n const pages = await page_1.default.find();\n const groups = await PageGroup.find();\n const triggers = trigger_1.default.find();\n return new form_1.default({\n fields: [\n {\n name: \"auto_save\",\n label: req.__(\"Auto save\"),\n sublabel: req.__(\"Save any changes immediately\"),\n type: \"Bool\",\n },\n {\n name: \"confirm_leave\",\n label: req.__(\"Confirm leaving unsaved\"),\n sublabel: req.__(\"Ask the user to confirm if they close a tab with unsaved changes\"),\n type: \"Bool\",\n showIf: { auto_save: false },\n },\n {\n name: \"auto_create\",\n label: req.__(\"Allocate new row\"),\n sublabel: req.__(\"If the view is run without existing row, allocate a new row on load. Defaults must be set on all required fields.\"),\n type: \"Bool\",\n },\n {\n name: \"delete_unchanged_auto_create\",\n label: req.__(\"Delete unchanged\"),\n sublabel: req.__(\"Delete allocated row if there are no changes.\"),\n type: \"Bool\",\n showIf: { auto_create: true },\n },\n {\n name: \"split_paste\",\n label: req.__(\"Split paste\"),\n sublabel: req.__(\"Separate paste content into separate inputs\"),\n type: \"Bool\",\n },\n {\n name: \"enable_realtime\",\n label: req.__(\"Real-time updates\"),\n sublabel: req.__(\"Enable real-time updates for this view\"),\n type: \"Bool\",\n default: false,\n },\n new FieldRepeat({\n name: \"update_events\",\n showIf: { enable_realtime: true },\n fields: [\n {\n type: \"String\",\n name: \"event\",\n label: req.__(\"Update event\"),\n sublabel: req.__(\"Custom event for real-time updates\"),\n attributes: {\n options: triggers.map((t) => t.name),\n },\n },\n ],\n }),\n {\n name: \"destination_type\",\n label: \"Destination type\",\n type: \"String\",\n required: true,\n sublabel: req.__(\"This is the view to which the user will be sent when the form is submitted. The view you specify here can be ignored depending on the context of the form, for instance if it appears in a pop-up the redirect will not take place.\"),\n attributes: {\n options: [\n \"Back to referer\",\n \"View\",\n \"Page\",\n \"PageGroup\",\n \"Formula\",\n \"URL formula\",\n ],\n },\n },\n {\n name: \"view_when_done\",\n label: req.__(\"Destination view\"),\n type: \"String\",\n required: true,\n attributes: {\n options: done_view_opts,\n },\n showIf: { destination_type: \"View\" },\n },\n {\n name: \"page_when_done\",\n label: req.__(\"Destination page\"),\n type: \"String\",\n required: true,\n attributes: {\n options: pages.map((p) => p.name),\n },\n showIf: { destination_type: \"Page\" },\n },\n {\n name: \"page_group_when_done\",\n label: req.__(\"Destination page group\"),\n type: \"String\",\n required: true,\n attributes: {\n options: groups.map((p) => p.name),\n },\n showIf: { destination_type: \"PageGroup\" },\n },\n {\n name: \"dest_url_formula\",\n label: req.__(\"Destination URL Formula\"),\n type: \"String\",\n required: true,\n class: \"validate-expression\",\n showIf: { destination_type: \"URL formula\" },\n },\n new FieldRepeat({\n name: \"formula_destinations\",\n showIf: { destination_type: \"Formula\" },\n fields: [\n {\n type: \"String\",\n name: \"expression\",\n label: \"Formula\",\n class: \"validate-expression\",\n sublabel: \"if this formula evaluates to true, use the following view\",\n },\n {\n name: \"view\",\n label: req.__(\"View\"),\n type: \"String\",\n required: true,\n attributes: {\n options: done_view_opts,\n },\n },\n ],\n }),\n ],\n });\n },\n },\n ],\n});\n/**\n * @param table_id\n * @param viewname\n * @param param2\n * @returns\n */\nconst get_state_fields = async (table_id, viewname, { columns }) => [\n {\n name: \"id\",\n type: \"Integer\",\n primary_key: true,\n },\n];\nconst initial_config = (0, plugin_helper_1.initial_config_all_fields)(true);\n/**\n * @param table_id\n * @param viewname\n * @param cfg\n * @param state\n * @param param4\n * @param param5\n * @returns\n */\nconst run = async (table_id, viewname, cfg, state, { res, req, isPreview, hiddenLoginDest, }, { editQuery }) => {\n const mobileReferrer = isWeb(req) ? undefined : req?.headers?.referer;\n return await editQuery(state, mobileReferrer, isPreview, hiddenLoginDest);\n};\n/**\n * @param table_id\n * @param viewname\n * @param param2\n * @param state\n * @param extra\n * @param param5\n * @returns\n */\nconst runMany = async (table_id, viewname, { columns, layout, auto_save, split_paste, confirm_leave, enable_realtime, update_events, }, state, extra, { editManyQuery, getRowQuery, optionsQuery }) => {\n let { table, fields, rows } = await editManyQuery(state, {\n limit: extra.limit,\n offset: extra.offset,\n orderBy: extra.orderBy,\n orderDesc: extra.orderDesc,\n where: extra.where,\n });\n if (!isNode()) {\n table = table_1.default.findOne({ id: table.id });\n fields = table.getFields();\n }\n const isRemote = !isWeb(extra.req);\n return await asyncMap(rows, async (row) => {\n const html = await render({\n table,\n fields,\n viewname,\n columns,\n layout,\n row,\n req: extra.req,\n res: extra.res,\n state,\n auto_save,\n getRowQuery,\n optionsQuery,\n split_paste,\n isRemote,\n confirm_leave,\n enable_realtime,\n update_events,\n });\n return { html, row };\n });\n};\nconst realTimeScript = (viewname, table_id, row, scriptId) => {\n const view = view_1.default.findOne({ name: viewname });\n const table = table_1.default.findOne({ id: table_id });\n const rowId = row[table.pk_name];\n return `\n const collabCfg = {\n events: {\n '${view.getRealTimeEventName(`UPDATE_EVENT?id=${rowId}`)}': async (data) => {\n console.log(\"Update event received for view ${viewname}\", data);\n const script = document.getElementById('${scriptId}');\n const closestDiv = script?.closest(\n 'div[data-sc-embed-viewname=\"${viewname}\"]'\n );\n if (data.updates) {\n if (closestDiv) await common_done({set_fields: data.updates, no_onchange: true}, closestDiv);\n else await common_done({set_fields: data.updates, no_onchange: true}, \"${viewname}\");\n }\n if (data.actions) {\n for (const action of data.actions) {\n if (closestDiv) await common_done(action, closestDiv);\n else await common_done(action, \"${viewname}\");\n }\n }\n }\n }\n };\n init_collab_room('${viewname}', collabCfg);`.trim();\n};\nconst render = async ({ table, fields, viewname, columns, layout, row, req, state, res, auto_save, destination_type, isRemote, getRowQuery, optionsQuery, split_paste, mobileReferrer, confirm_leave, delete_unchanged_auto_create, isPreview, auto_created_row, hiddenLoginDest, enable_realtime, }) => {\n const form = await (0, viewable_fields_1.getForm)(table, viewname, columns, layout, state[table.pk_name], req, isRemote);\n if (split_paste)\n form.splitPaste = true;\n if (row) {\n form.values = row;\n const file_fields = form.fields.filter((f) => f.type === \"File\");\n if (isWeb(req)) {\n for (const field of file_fields) {\n if (field.fieldviewObj?.valueIsFilename && row[field.name]) {\n const file = await file_1.default.findOne({ id: row[field.name] });\n if (file?.id)\n form.values[field.name] = file.filename;\n }\n if (field.fieldviewObj?.editContent && row[field.name]) {\n const file = await file_1.default.findOne(row[field.name]);\n if (file && file.min_role_read >= (req.user?.role_id || 100))\n form.values[`_content_${field.name}`] = await file.get_contents();\n }\n }\n }\n form.hidden(table.pk_name);\n const user_id = req.user ? req.user.id : null;\n const owner_field = await table.owner_fieldname();\n if (table.ownership_formula && user_id) {\n const freeVars = freeVariables(table.ownership_formula);\n if (freeVars.size > 0) {\n const joinFields = {};\n (0, plugin_helper_1.add_free_variables_to_joinfields)(freeVars, joinFields, fields);\n const row_joined = await table.getJoinedRow({\n where: { [table.pk_name]: row[table.pk_name] },\n forPublic: !req.user,\n forUser: req.user,\n joinFields,\n });\n form.isOwner = await table.is_owner(req.user, row_joined);\n }\n else\n form.isOwner = await table.is_owner(req.user, row);\n }\n else\n form.isOwner = owner_field && user_id && row[owner_field] === user_id;\n }\n else {\n form.isOwner = true;\n }\n if (destination_type === \"Back to referer\") {\n form.hidden(\"_referer\");\n form.values._referer = mobileReferrer\n ? mobileReferrer\n : req.headers?.referer;\n }\n if (hiddenLoginDest && req.query.dest) {\n form.hidden(\"dest\");\n if (!req.query.dest.includes(\":/\") && !req.query.dest.includes(\"//\"))\n form.values.dest = req.query.dest;\n }\n Object.entries(state).forEach(([k, v]) => {\n const field = form.fields.find((f) => f.name === k);\n if (field && ((field.type && field.type.read) || field.is_fkey)) {\n form.values[k] = field.type.read\n ? field.type.read(v, field.attributes)\n : v;\n }\n else {\n const tbl_field = fields.find((f) => f.name === k);\n if (tbl_field && !field) {\n form.fields.push(new field_1.default({ name: k, input_type: \"hidden\" }));\n form.values[k] = tbl_field.type.read\n ? tbl_field.type.read(v, tbl_field.attributes)\n : v;\n }\n }\n });\n // add row values not in columns as hidden if needed for join fields\n if (row) {\n const need_join_fields = new Set(columns\n .filter((c) => c.type === \"JoinField\")\n .map((c) => c.join_field.split(\".\")[0]));\n const colFields = new Set(columns\n .filter((c) => c.type === \"Field\")\n .map((c) => c.field_name));\n const formFields = new Set(form.fields.map((f) => f.name));\n fields.forEach((f) => {\n if (!colFields.has(f.name) &&\n !formFields.has(f.name) &&\n typeof row[f.name] !== \"undefined\" &&\n need_join_fields.has(f.name))\n form.fields.push(new field_1.default({ name: f.name, input_type: \"hidden\" }));\n });\n }\n // no autosave if new and save button exists\n let hasSave = false;\n traverseSync(layout, {\n action({ action_name }) {\n if (action_name === \"Save\" || action_name === \"SubmitWithAjax\") {\n hasSave = true;\n }\n },\n });\n const actually_auto_save = auto_save && !(!row && hasSave);\n if (actually_auto_save)\n form.onChange = `saveAndContinueDelayed(this, ${!isWeb(req) ? `'${form.action}'` : undefined}, event);`;\n let reloadAfterCloseInModalScript = actually_auto_save && req.xhr\n ? (0, tags_1.script)((0, tags_1.domReady)(`\n $(\"#scmodal\").on(\"hidden.bs.modal\", function (e) {\n const on_close_reload_view = $(\"#scmodal\").attr(\n \"data-on-close-reload-view\"\n );\n if(on_close_reload_view)\n reload_embedded_view(on_close_reload_view)\n else\n setTimeout(()=>location.reload(),0);\n });`))\n : \"\";\n let confirmLeaveScript = \"\";\n if (confirm_leave) {\n if (!form.onChange)\n form.onChange = \"\";\n form.onChange += \"this.setAttribute('data-unsaved-changes','true');\";\n if (!form.onSubmit)\n form.onSubmit = \"\";\n form.onSubmit += \"this.removeAttribute('data-unsaved-changes')\";\n confirmLeaveScript = (0, tags_1.script)(`((curScript)=>{window.addEventListener(\"beforeunload\", (e) => check_unsaved_form(e, curScript));})(document.currentScript)`);\n }\n let deleteUnchangedScript = \"\";\n if (auto_created_row && delete_unchanged_auto_create && !isPreview) {\n if (hasSave) {\n if (!form.onSubmit)\n form.onSubmit = \"\";\n form.onSubmit += \"this.setAttribute('data-form-changed','true');\";\n }\n else {\n if (!form.onChange)\n form.onChange = \"\";\n form.onChange += \"this.setAttribute('data-form-changed','true');\";\n }\n deleteUnchangedScript = (0, tags_1.script)(`((curScript)=>{window.addEventListener(\"beforeunload\", () => check_delete_unsaved(\"${table.name}\", curScript));})(document.currentScript)`);\n }\n const formId = isTest()\n ? \"test-form-id\"\n : `form${Math.floor(Math.random() * 16777215).toString(16)}`;\n const identicalFieldsScript = (0, tags_1.script)((0, tags_1.domReady)(`const editForm = document.getElementById('${formId}'); if (editForm) editForm.addEventListener(\"change\", handle_identical_fields, true);`));\n const dynamic_updates_enabled = getState().getConfig(\"enable_dynamic_updates\", true);\n const rndid = isTest()\n ? \"test-script-id\"\n : Math.floor(Math.random() * 16777215).toString(16);\n const realTimeCollabScript = enable_realtime && row && !(req.headers?.pjaxpageload === \"true\")\n ? (!dynamic_updates_enabled\n ? (0, tags_1.script)({\n src: `/static_assets/${db_1.default.connectObj.version_tag}/socket.io.min.js`,\n })\n : \"\") +\n (0, tags_1.script)({ id: rndid }, (0, tags_1.domReady)(realTimeScript(viewname, table.id, row, rndid)))\n : \"\";\n if (actually_auto_save) {\n for (const field of form.fields) {\n field.in_auto_save = true;\n }\n }\n await form.fill_fkey_options(false, optionsQuery, req.user);\n await (0, viewable_fields_1.transformForm)({\n form,\n table,\n req,\n row: row,\n res,\n getRowQuery,\n viewname,\n optionsQuery,\n state,\n });\n form.id = formId;\n return (renderForm(form, !isRemote && req.csrfToken ? req.csrfToken() : false) +\n reloadAfterCloseInModalScript +\n confirmLeaveScript +\n deleteUnchangedScript +\n identicalFieldsScript +\n realTimeCollabScript);\n};\nconst identicalFieldNames = (columns) => {\n const fieldNames = new Set();\n const result = new Set();\n for (const field of columns) {\n if (field.type === \"Field\") {\n if (fieldNames.has(field.field_name))\n result.add(field.field_name);\n else\n fieldNames.add(field.field_name);\n }\n }\n return result;\n};\nconst prepSafeBody = (body, columns) => {\n const safeBody = { ...body };\n const identicalFields = identicalFieldNames(columns);\n for (const field of identicalFields) {\n if (body && body[field] && Array.isArray(body[field])) {\n safeBody[field] = body[field][0];\n }\n }\n return safeBody;\n};\n/**\n * @param table_id\n * @param viewname\n * @param param2\n * @param state\n * @param body\n * @param param5\n * @param param6\n * @param remote\n * @returns\n */\nconst runPost = async (table_id, viewname, { columns, layout, fixed, view_when_done, formula_destinations, auto_save, destination_type, dest_url_formula, page_when_done, page_group_when_done, }, state, body, { res, req, redirect }, { tryInsertQuery, tryUpdateQuery, getRowQuery, saveFileQuery, saveFileFromContentsQuery, optionsQuery, getRowByIdQuery, }, remote) => {\n const safeBody = prepSafeBody(body, columns);\n const table = table_1.default.findOne({ id: table_id });\n const fields = table.getFields();\n if (safeBody?.password && table_id === user_1.default.table.id) {\n safeBody.password = await user_1.default.hashPassword(safeBody.password);\n }\n const prepResult = await prepare(viewname, table, fields, {\n columns,\n layout,\n fixed,\n auto_save,\n }, { req, res }, safeBody, {\n getRowQuery,\n saveFileQuery,\n saveFileFromContentsQuery,\n optionsQuery,\n getRowByIdQuery,\n }, remote);\n const view = view_1.default.findOne({ name: viewname });\n const pagetitle = { title: viewname, no_menu: view?.attributes?.no_menu };\n if (prepResult) {\n let { form, row, pk, id } = prepResult;\n const cancel = safeBody._cancel;\n const originalID = id;\n let trigger_return;\n let ins_upd_error;\n if (!cancel) {\n getState().log(6, `Edit POST ready to insert/update into ${table.name} Row=${JSON.stringify(row)} ID=${id} Ajax=${!!req.xhr}`);\n const doReturn = await db_1.default.withTransaction(async (rollback) => {\n if (typeof id === \"undefined\") {\n const ins_res = await tryInsertQuery(row);\n if (ins_res.success) {\n id = ins_res.success;\n row[pk.name] = id;\n trigger_return = ins_res.trigger_return;\n }\n else {\n ins_upd_error = ins_res.error;\n }\n }\n else {\n if (table.composite_pk_names ||\n table.getField(table.pk_name).attributes.NonSerial) {\n const upd_res = await tryInsertOrUpdateImpl(row, id, table, req);\n if (upd_res.error) {\n ins_upd_error = upd_res.error;\n }\n trigger_return = upd_res.trigger_return;\n }\n else {\n const upd_res = await tryUpdateQuery(row, id);\n if (upd_res.error) {\n ins_upd_error = upd_res.error;\n }\n trigger_return = upd_res.trigger_return;\n }\n }\n if (ins_upd_error) {\n await rollback();\n getState().log(6, `Insert or update failure ${JSON.stringify(ins_upd_error)}`);\n res.status(422);\n if (req.xhr) {\n res.json({ error: ins_upd_error });\n }\n else {\n await form.fill_fkey_options(false, optionsQuery, req.user);\n req.flash(\"error\", (0, tags_1.text_attr)(ins_upd_error));\n for (const file_field of fields.filter((f) => f.type === \"File\")) {\n if (!form.values[file_field.name])\n continue;\n form.values[`__exisiting_file_${file_field.name}`] =\n form.values[file_field.name];\n form.hidden(`__exisiting_file_${file_field.name}`);\n }\n res.sendWrap(pagetitle, renderForm(form, req.csrfToken()));\n }\n return true;\n }\n for (const field of form.fields.filter((f) => f.isRepeat)) {\n const view_select = (0, viewable_fields_1.parse_view_select)(field.metadata.view, field.metadata.relation_path);\n const order_field = field.metadata.order_field;\n const childView = view_1.default.findOne({ name: view_select.viewname });\n if (!childView)\n throw new InvalidConfiguration(`Cannot find embedded view: ${view_select.viewname}`);\n if (field.metadata.relation_path &&\n view_select.type === \"RelationPath\") {\n const targetTbl = table_1.default.findOne({ id: childView.table_id });\n const relation = new Relation(field.metadata.relation_path, targetTbl.name, (0, plugin_helper_1.displayType)(await childView.get_state_fields()));\n if (relation.type === RelationType.CHILD_LIST)\n (0, viewable_fields_1.updateViewSelect)(view_select);\n }\n const childTable = table_1.default.findOne({ id: field.metadata?.table_id });\n const submitted_row_ids = new Set((form.values[field.name] || []).map((srow) => `${srow[childTable.pk_name]}`));\n const childFields = new Set(childTable.fields.map((f) => f.name));\n let repeatIx = 0;\n for (const [childRow, row_ix] of form.values[field.name].map((r, ix) => [r, ix])) {\n // set fixed here\n childRow[field.metadata?.relation] = id;\n for (const [k, v] of Object.entries(childView?.configuration?.fixed || {})) {\n if (typeof childRow[k] === \"undefined\" &&\n !k.startsWith(\"_block_\") &&\n childFields.has(k) &&\n (v || v === 0))\n childRow[k] = v;\n }\n if (order_field && !childRow[order_field])\n childRow[order_field] = row_ix;\n for (const file_field of field.fields.filter((f) => f.type === \"File\")) {\n const key = `${file_field.name}_${repeatIx}`;\n if (req.files?.[key] &&\n (!file_field.fieldviewObj || file_field.fieldviewObj.isEdit)) {\n const file = await file_1.default.from_req_files(req.files[key], req.user ? req.user.id : undefined, (file_field.attributes &&\n +file_field.attributes.min_role_read) ||\n 1, file_field?.attributes?.folder);\n childRow[file_field.name] = file.field_value;\n }\n }\n getState().log(6, `Edit POST ready to insert/update Child row into ${childTable.name} Row=${JSON.stringify(childRow)} ID=${childRow[childTable.pk_name]} Ajax=${!!req.xhr}`);\n if (childRow[childTable.pk_name]) {\n const upd_res = await childTable.tryUpdateRow(childRow, childRow[childTable.pk_name], req.user || { role_id: 100 }, undefined, { req });\n if (upd_res.error) {\n await rollback();\n getState().log(6, `Update child row failure ${JSON.stringify(upd_res)}`);\n req.flash(\"error\", (0, tags_1.text_attr)(upd_res.error));\n res.sendWrap(pagetitle, renderForm(form, req.csrfToken()));\n return true;\n }\n }\n else {\n const ins_res = await childTable.tryInsertRow(childRow, req.user || { role_id: 100 });\n if (ins_res.error) {\n await rollback();\n getState().log(6, `Insert child row failure ${JSON.stringify(ins_res)}`);\n req.flash(\"error\", (0, tags_1.text_attr)(ins_res.error));\n res.sendWrap(pagetitle, renderForm(form, req.csrfToken()));\n return true;\n }\n else if (ins_res.success) {\n submitted_row_ids.add(`${ins_res.success}`);\n }\n }\n repeatIx += 1;\n }\n //need to delete any rows that are missing\n if (originalID && field.metadata) {\n const childRows = getRowQuery\n ? await getRowQuery(field.metadata.table_id, view_select, originalID)\n : await childTable.getRows({\n [view_select.field_name]: originalID,\n });\n for (const db_child_row of childRows) {\n if (!submitted_row_ids.has(`${db_child_row[childTable.pk_name]}`)) {\n await childTable.deleteRows({\n [childTable.pk_name]: db_child_row[childTable.pk_name],\n }, req.user || { role_id: 100 });\n }\n }\n }\n }\n });\n if (doReturn)\n return;\n //Edit-in-edit\n }\n trigger_return = trigger_return || {};\n if (trigger_return.notify && trigger_return.details)\n req.flash(\"success\", (0, tags_1.div)({ class: \"d-inline\" }, trigger_return.notify, (0, tags_1.button)({\n class: \"btn btn-sm btn-outline-secondary btn-xs\",\n type: \"button\",\n \"data-bs-toggle\": \"collapse\",\n \"data-bs-target\": \"#notifyDetails\",\n \"aria-expanded\": \"false\",\n \"aria-controls\": \"notifyDetails\",\n }, (0, tags_1.i)({ class: \"fas fa-plus\" })), (0, tags_1.div)({ class: \"collapse\", id: \"notifyDetails\" }, (0, tags_1.pre)(trigger_return.details))));\n else if (trigger_return.notify)\n req.flash(\"success\", trigger_return.notify);\n if (trigger_return.error) {\n req.flash(\"danger\", trigger_return.error);\n crash_1.default.create({ message: trigger_return.error, stack: \"\" }, req);\n }\n if (trigger_return.goto) {\n res.redirect(trigger_return.goto);\n return;\n }\n await whenDone(viewname, table_id, fields, pk, {\n view_when_done,\n formula_destinations,\n destination_type,\n dest_url_formula,\n page_when_done,\n page_group_when_done,\n redirect,\n }, req, res, safeBody, row, !originalID ? { id, ...trigger_return } : trigger_return, true, originalID, table);\n }\n};\nconst doAuthPost = async ({ body, table_id, req, }) => {\n const table = table_1.default.findOne({ id: table_id });\n const user_id = req.user ? req.user.id : null;\n if (table.ownership_field_id && user_id) {\n const field_name = await table.owner_fieldname();\n if (typeof body[field_name || \"\"] === \"undefined\") {\n const fields = table.getFields();\n const { uniques } = (0, viewable_fields_1.splitUniques)(fields, body);\n if (Object.keys(uniques).length > 0) {\n const dbrow = await table.getRow(uniques, {\n forUser: req.user,\n forPublic: !req.user,\n });\n if (!dbrow)\n return false;\n return table.is_owner(req.user, dbrow);\n }\n }\n else\n return field_name && `${body[field_name]}` === `${user_id}`;\n }\n if (table.ownership_formula && user_id) {\n let row = { ...body };\n if (body[table.pk_name]) {\n const joinFields = {};\n if (table.ownership_formula) {\n const fields = table.getFields();\n const freeVars = freeVariables(table.ownership_formula);\n (0, plugin_helper_1.add_free_variables_to_joinfields)(freeVars, joinFields, fields);\n }\n const dbrow = await table.getJoinedRows({\n where: {\n [table.pk_name]: body[table.pk_name],\n },\n joinFields,\n });\n if (dbrow.length > 0)\n row = { ...body, ...dbrow[0] };\n }\n else {\n const freeVars = freeVariables(table.ownership_formula);\n const fields = table.getFields();\n const field_names = new Set(fields.map((f) => f.name));\n for (const fv of freeVars) {\n const kpath = fv.split(\".\");\n if (field_names.has(kpath[0]) && kpath.length > 1) {\n const field = fields.find((f) => f.name === kpath[0]);\n if (!field)\n throw new Error(\"Invalid formula:\" + table.ownership_formula);\n const reftable = table_1.default.findOne({ name: field.reftable_name });\n const joinFields = {};\n const [kpath0, ...kpathrest] = kpath;\n (0, plugin_helper_1.add_free_variables_to_joinfields)(new Set([kpathrest.join(\".\")]), joinFields, fields);\n const rows = await reftable.getJoinedRows({\n where: {\n [reftable.pk_name]: body[kpath0],\n },\n joinFields,\n });\n row[kpath0] = rows[0];\n }\n }\n }\n const is_owner = await table.is_owner(req.user, row);\n return is_owner;\n }\n if (table.name === \"users\" && `${body.id}` === `${user_id}`)\n return true;\n return false;\n};\n/**\n * @param param0\n * @param param1\n * @returns\n */\nconst authorise_post = async ({ body, table_id, req, }, { authorizePostQuery }) => {\n return await authorizePostQuery(body, table_id);\n};\n/**\n * @param tableId\n * @param viewName\n * @param id\n * @param fieldName\n * @param fieldView\n * @param user\n * @param configuration\n * @param targetOpts\n * @returns\n */\nconst openDataStream = async (tableId, viewName, id, fieldName, fieldView, user, configuration, targetOpts) => {\n const table = table_1.default.findOne({ id: tableId });\n const field = table.getField(fieldName);\n if (!field)\n throw new InvalidConfiguration(`Field ${fieldName} not found`);\n if (field.type === \"File\") {\n const cfgCol = configuration.columns.find((col) => col.fieldview === fieldView && col.field_name === fieldName);\n const fileView = getState().fileviews[fieldView];\n if (!fileView)\n throw new InvalidConfiguration(`File view ${fieldView} not found`);\n return await fileView.openDataStream(tableId, id, fieldName, user, cfgCol.configuration, targetOpts);\n }\n};\n/**\n * @param view\n * @param id\n * @param fieldName\n * @param user\n * @param targetOpts\n * @returns\n */\nconst authorizeDataStream = async (view, id, fieldName, user, targetOpts) => {\n if (!user || user.role_id > view.min_role)\n return false;\n else {\n const table = table_1.default.findOne({ id: view.table_id });\n if (!table || user.role_id > table.min_role_write)\n return false;\n else {\n const field = table.getField(fieldName);\n if (field.type === \"File\") {\n if (targetOpts?.oldTarget) {\n const file = await file_1.default.findOne(targetOpts.oldTarget);\n if (file)\n return file.min_role_read >= user.role_id;\n }\n else if (id) {\n const row = await table.getRow({ [table.pk_name]: id });\n const fileCol = row[fieldName];\n if (fileCol) {\n const file = await file_1.default.findOne(row[fieldName]);\n if (file)\n return file.min_role_read >= user.role_id;\n }\n }\n return true;\n }\n else {\n return false;\n }\n }\n }\n};\nconst run_action = async (table_id, viewname, { columns, layout }, body, { req, res }, { actionQuery }) => {\n const result = await actionQuery();\n if (result.json.error) {\n crash_1.default.create({ message: result.json.error, stack: \"\" }, req);\n }\n return result;\n};\nconst update_matching_rows = async (table_id, viewname, { columns, layout, fixed, view_when_done, formula_destinations, auto_save, destination_type, dest_url_formula, page_when_done, page_group_when_done, }, body, { req, res, redirect }, { updateMatchingQuery, getRowQuery, saveFileQuery, saveFileFromContentsQuery, optionsQuery, getRowByIdQuery, }) => {\n const table = table_1.default.findOne({ id: table_id });\n const fields = table.getFields();\n const prepResult = await prepare(viewname, table, fields, {\n columns,\n layout,\n fixed,\n auto_save,\n }, { req, res }, body, {\n getRowQuery,\n saveFileQuery,\n saveFileFromContentsQuery,\n optionsQuery,\n getRowByIdQuery,\n });\n if (prepResult) {\n let { form, row, pk } = prepResult;\n const state = req?.query\n ? (0, plugin_helper_1.readState)(removeEmptyStrings(req.query), fields, req)\n : {};\n const where = (0, plugin_helper_1.stateFieldsToWhere)({ fields, state, table });\n const repeatFields = form.fields.filter((f) => f.isRepeat);\n const childRows = {};\n for (const field of repeatFields)\n childRows[field.name] = form.values[field.name];\n const { id, ...rest } = row;\n const uptResults = await updateMatchingQuery(where, rest, repeatFields, childRows);\n if (uptResults.error || uptResults.rowError || uptResults.inEditError) {\n res.status(422);\n req.flash(\"error\", (0, tags_1.text_attr)(uptResults.error || uptResults.rowError || uptResults.inEditError));\n res.sendWrap(viewname, renderForm(form, req.csrfToken()));\n return;\n }\n const { success, danger, goto } = combineResults(uptResults);\n if (success.length > 0) {\n req.flash(\"success\", success);\n }\n if (danger.length > 0) {\n req.flash(\"danger\", danger);\n }\n else if (goto) {\n res.redirect(goto);\n return;\n }\n await whenDone(viewname, table_id, fields, pk, {\n view_when_done,\n formula_destinations,\n destination_type,\n dest_url_formula,\n page_when_done,\n page_group_when_done,\n redirect,\n }, req, res, body, row);\n }\n};\nconst prepare = async (viewname, table, fields, { columns, layout, fixed, auto_save }, { req, res }, body, { getRowQuery, saveFileQuery, saveFileFromContentsQuery, optionsQuery, getRowByIdQuery, }, remote) => {\n const isRemote = !isWeb(req);\n const form = await (0, viewable_fields_1.getForm)(table, viewname, columns, layout, body.id, req, isRemote);\n if (auto_save)\n form.onChange = `saveAndContinueDelayed(this, ${!isWeb(req) ? `'${form.action}'` : undefined}, event);`;\n Object.entries(body).forEach(([k, v]) => {\n const form_field = form.fields.find((f) => f.name === k);\n const tbl_field = fields.find((f) => f.name === k);\n if (tbl_field && !form_field && !fixed?.[`_block_${k}`]) {\n form.fields.push(new field_1.default({ name: k, input_type: \"hidden\" }));\n }\n });\n (0, viewable_fields_1.setDateLocales)(form, req.getLocale());\n await (0, viewable_fields_1.transformForm)({\n form,\n table,\n req,\n res,\n row: body[table.pk_name] ? { [table.pk_name]: body[table.pk_name] } : null,\n getRowQuery,\n viewname,\n optionsQuery,\n });\n const cancel = body._cancel;\n await form.asyncValidate({\n ...body,\n _file_names: Object.keys(req.files || {}),\n });\n if (form.hasErrors && !cancel) {\n if (req.xhr)\n res.status(422);\n await form.fill_fkey_options(false, optionsQuery, req.user);\n const view = view_1.default.findOne({ name: viewname });\n res.sendWrap({ title: viewname, no_menu: view?.attributes?.no_menu }, renderForm(form, req.csrfToken ? req.csrfToken() : false));\n return null;\n }\n let row;\n const pk = fields.find((f) => f.primary_key);\n let id;\n if (table.composite_pk_names) {\n id = {};\n table.fields\n .filter((f) => f.primary_key)\n .forEach((f) => {\n id[f.name] = f.type.read(body[f.name]);\n });\n }\n else {\n id = pk.type.read(body[pk.name]);\n }\n if (typeof id === \"undefined\") {\n const use_fixed = await (0, viewable_fields_1.fill_presets)(table, req, fixed);\n row = { ...use_fixed, ...form.values };\n }\n else if (cancel) {\n row = getRowByIdQuery\n ? await getRowByIdQuery(id)\n : await table.getRow({ id }, { forUser: req.user, forPublic: !req.user });\n }\n else {\n row = { ...form.values };\n }\n for (const field of form.fields.filter((f) => f.isRepeat)) {\n delete row[field.name];\n }\n const file_fields = form.fields.filter((f) => f.type === \"File\");\n for (const field of file_fields) {\n if (!field.fieldviewObj?.isEdit || field.fieldviewObj?.isStream)\n continue;\n if (field.fieldviewObj?.setsFileId) {\n //do nothing\n }\n else if (field.fieldviewObj?.setsDataURL) {\n if (body[field.name]) {\n if (body[field.name].startsWith(\"data:\")) {\n const path_to_serve = await saveFileQuery(body[field.name], field.id, field.fieldview, row);\n const storedValue = file_1.default.fieldValueFromRelative(path_to_serve);\n row[field.name] = storedValue;\n form.values[field.name] = storedValue;\n }\n }\n }\n else if (field.fieldviewObj?.editContent) {\n if (body[field.name]) {\n const path_to_serve = await saveFileFromContentsQuery(body[`_content_${field.name}`], field.id, field.fieldview, row, body[field.name], \"utf8\");\n const storedValue = file_1.default.fieldValueFromRelative(path_to_serve);\n row[field.name] = storedValue;\n form.values[field.name] = storedValue;\n }\n }\n else if (req.files && req.files[field.name]) {\n if (!isWeb(req) && !remote && req.files[field.name].name) {\n throw new Error(\"The mobile-app supports no local files, please use a remote table.\");\n }\n if (isWeb(req)) {\n const file = await file_1.default.from_req_files(req.files[field.name], req.user ? req.user.id : undefined, (field.attributes && +field.attributes.min_role_read) || 1, field?.attributes?.folder);\n row[field.name] = file.field_value;\n form.values[field.name] = file.field_value;\n }\n else {\n const file = req.files[field.name];\n if (file) {\n const serverResp = await file_1.default.upload(req.files[field.name]);\n if (serverResp?.location)\n row[field.name] = file_1.default.normalizeFieldValueInput(serverResp.location);\n }\n }\n }\n else if (typeof body[`__exisiting_file_${field.name}`] === \"string\") {\n row[field.name] = file_1.default.normalizeFieldValueInput(body[`__exisiting_file_${field.name}`]);\n form.values[field.name] = row[field.name];\n }\n else {\n delete row[field.name];\n }\n }\n return { form, row, pk, id };\n};\nconst whenDone = async (viewname, table_id, fields, pk, { view_when_done, formula_destinations, destination_type, dest_url_formula, page_when_done, page_group_when_done, redirect, }, req, res, body, row0, trigger_return, check_ajax, originalID, table) => {\n const res_redirect = (url) => {\n if (check_ajax && req.xhr && !req.smr)\n res.json({\n view_when_done,\n url_when_done: url,\n ...(trigger_return || {}),\n });\n else\n res.redirect(url);\n };\n if (redirect) {\n res_redirect(redirect);\n return;\n }\n if (check_ajax && req.xhr && !req.smr && trigger_return?.error) {\n res.json({\n view_when_done,\n ...(trigger_return || {}),\n });\n return;\n }\n let use_view_when_done = view_when_done;\n let row;\n if (table &&\n ((originalID && destination_type === \"URL formula\") ||\n (use_view_when_done || \"\").includes(\".\"))) {\n const db_row = await table.getRow({ [table.pk_name]: originalID });\n row = { ...db_row, ...row0 };\n }\n else\n row = row0;\n if (destination_type === \"Back to referer\" && body._referer) {\n res_redirect(toSafeRelativeUrl(body._referer));\n return;\n }\n else if (destination_type === \"Page\" && page_when_done) {\n res_redirect(`/page/${page_when_done}`);\n return;\n }\n else if (destination_type === \"PageGroup\" && page_group_when_done) {\n res_redirect(`/page/${page_group_when_done}`);\n return;\n }\n else if (destination_type === \"URL formula\" && dest_url_formula) {\n const url = eval_expression(dest_url_formula, row, \"Destination URL formula\");\n res_redirect(url);\n return;\n }\n else if (destination_type !== \"View\")\n for (const { view, expression } of formula_destinations || []) {\n if (expression) {\n const f = get_expression_function(expression, fields);\n if (f(row)) {\n use_view_when_done = view;\n continue;\n }\n }\n }\n if (!use_view_when_done) {\n res_redirect(`/`);\n return;\n }\n const [viewname_when_done, relation] = use_view_when_done.split(\".\");\n const nxview = view_1.default.findOne({ name: viewname_when_done });\n if (!nxview) {\n req.flash(\"warning\", `View \"${use_view_when_done}\" not found - change \"View when done\" in \"${viewname}\" view`);\n res_redirect(`/`);\n }\n else {\n const state_fields = await nxview.get_state_fields();\n let target = `/view/${(0, tags_1.text)(viewname_when_done)}`;\n let query = \"\";\n if ((nxview.table_id === table_id || relation) &&\n state_fields.some((sf) => sf.name === pk.name) &&\n viewname_when_done !== viewname) {\n const get_query = (0, viewable_fields_1.get_view_link_query)(fields, nxview);\n query = relation ? `?${pk.name}=${(0, tags_1.text)(row[relation])}` : get_query(row);\n }\n const redirectPath = `${target}${query}`;\n if (!isWeb(req)) {\n res.json({ redirect: `get${redirectPath}` });\n }\n else {\n res_redirect(redirectPath);\n }\n }\n};\nconst combineResults = (results) => {\n const combined = { success: [], danger: [] };\n for (const uptResult of results) {\n const trigger_return = uptResult.trigger_return || {};\n if (trigger_return.notify && trigger_return.details)\n combined.success.push((0, tags_1.div)({ class: \"d-inline\" }, trigger_return.notify, (0, tags_1.button)({\n class: \"btn btn-sm btn-outline-secondary btn-xs\",\n type: \"button\",\n \"data-bs-toggle\": \"collapse\",\n \"data-bs-target\": \"#notifyDetails\",\n \"aria-expanded\": \"false\",\n \"aria-controls\": \"notifyDetails\",\n }, (0, tags_1.i)({ class: \"fas fa-plus\" })), (0, tags_1.div)({ class: \"collapse\", id: \"notifyDetails\" }, (0, tags_1.pre)(trigger_return.details))));\n else if (trigger_return.notify)\n combined.success.push(trigger_return.notify);\n if (trigger_return.error)\n combined.danger.push(trigger_return.error);\n if (trigger_return.goto && !combined.goto)\n combined.goto = trigger_return.goto;\n }\n return combined;\n};\nconst tryUpdateImpl = async (row, id, table, req) => {\n const result = {};\n const upd_res = await table.tryUpdateRow(row, id, req.user || { role_id: 100 }, result, { req });\n upd_res.trigger_return = result;\n return upd_res;\n};\nconst tryInsertOrUpdateImpl = async (row, id, table, req) => {\n const result = {};\n const exists = await table.getRow(typeof id === \"object\" ? id : { [table.pk_name]: id });\n if (exists) {\n const upd_res = await table.tryUpdateRow(row, id, req.user || { role_id: 100 }, result, { req });\n upd_res.trigger_return = result;\n return upd_res;\n }\n else {\n const result = {};\n const ins_res = await table.tryInsertRow(row, req.user || { role_id: 100 }, result);\n ins_res.trigger_return = result;\n return ins_res;\n }\n};\n/**\n * @param param0\n * @returns\n */\nconst createBasicView = async ({ table, viewname, template_view, template_table, all_views_created, }) => {\n if (!template_view) {\n const configuration = await (0, plugin_helper_1.initial_config_all_fields)(true)({\n table_id: table.id,\n });\n configuration.destination_type = \"Back to referer\";\n return configuration;\n }\n const { inner, outer } = splitLayoutContainerFields(template_view.configuration.layout);\n const templateFieldTypes = {}, templateFieldLabels = {};\n for (const field of template_table.fields) {\n templateFieldTypes[field.name] = field.type_name;\n templateFieldLabels[field.name] = field.label;\n }\n const defaultBranch = inner\n ? findLayoutBranchWith(inner.above || inner.contents.above, (s) => {\n return s.type === \"field\";\n })\n : null;\n const inners = [], columns = [];\n for (const field of table.fields) {\n if (field.primary_key)\n continue;\n const branch = findLayoutBranchWith(inner.above || inner.contents.above, (s) => {\n return (s.type === \"field\" &&\n templateFieldTypes[s.field_name] === field.type_name);\n }) || defaultBranch;\n let oldField;\n if (branch)\n traverseSync(branch, {\n field(s) {\n oldField = template_table.getField(s.field_name);\n },\n });\n const newBranch = structuredClone(branch);\n let newCol = {};\n traverseSync(newBranch, {\n field(s) {\n s.field_name = field.name;\n newCol = {\n type: \"Field\",\n fieldview: s.fieldview,\n field_name: field.name,\n };\n },\n blank(s) {\n if (s.contents === oldField.label)\n s.contents = field.label;\n if (s.labelFor === oldField.name)\n s.labelFor = field.name;\n },\n });\n inners.push(newBranch);\n columns.push(newCol);\n }\n //clone any actions in inner\n for (const tmpl_inner of inner.above || inner.contents.above) {\n let hasField = false;\n let hasAction = false;\n const theActions = [];\n traverseSync(tmpl_inner, {\n field() {\n hasField = true;\n },\n action(s) {\n hasAction = true;\n theActions.push(s);\n },\n });\n if (hasAction && !hasField)\n inners.push(tmpl_inner);\n theActions.forEach((a) => columns.push({ ...a, type: \"Action\" }));\n }\n const cfg = {\n layout: outer({ above: inners }),\n columns,\n };\n cfg.destination_type = \"Back to referer\";\n cfg.auto_save = template_view.configuration.auto_save;\n cfg.confirm_leave = template_view.configuration.confirm_leave;\n cfg.auto_create = template_view.configuration.auto_create;\n cfg.delete_unchanged_auto_create =\n template_view.configuration.delete_unchanged_auto_create;\n cfg.split_paste = template_view.configuration.split_paste;\n return cfg;\n};\n/**\n * @param table_id\n * @param viewname\n * @param param2\n * @returns\n */\nconst virtual_triggers = (table_id, viewname, { enable_realtime, update_events }) => {\n if (!enable_realtime)\n return [];\n const table = table_1.default.findOne({ id: table_id });\n const view = view_1.default.findOne({ name: viewname });\n return [\n {\n when_trigger: \"Update\",\n table_id: table_id,\n run: async (row, { old_row, user }) => {\n getState().log(6, `Virtual trigger Update for ${viewname} on table ${table.name}`);\n const fields = table.getFields();\n const changedFields = fields.filter((f) => {\n if (f.name === table.pk_name)\n return false;\n const a = row[f.name];\n const b = old_row[f.name];\n if (f.type?.equals)\n return !f.type.equals(a, b, f.attributes || {});\n else\n return row[f.name] !== old_row[f.name];\n });\n const changedLayoutFields = new Set();\n await traverse(view.configuration.layout, {\n field(segment) {\n const { field_name } = segment;\n if (changedFields.find((f) => f.name === field_name))\n changedLayoutFields.add(field_name);\n },\n });\n if (changedLayoutFields.size === 0) {\n getState().log(6, \"No layout fields changed, skipping real-time update\");\n }\n else {\n const updates = {};\n for (const fieldName of changedLayoutFields) {\n const newVal = row[fieldName];\n updates[fieldName] = newVal;\n }\n const rowId = row[table.pk_name];\n const actionResults = await (0, plugin_helper_1.runCollabEvents)(update_events, user, {\n new_row: row,\n old_row: old_row,\n updates: updates,\n });\n getState().log(6, \"Emitting real-time update for row\", rowId, updates);\n view.emitRealTimeEvent(`UPDATE_EVENT?id=${rowId}`, {\n updates: updates,\n actions: actionResults,\n });\n }\n },\n },\n ];\n};\nmodule.exports = {\n /** @type {string} */\n name: \"Edit\",\n /** @type {string} */\n description: \"Form for creating a new row or editing existing rows\",\n configuration_workflow,\n run,\n runMany,\n runPost,\n openDataStream,\n authorizeDataStream,\n get_state_fields,\n initial_config,\n createBasicView,\n authorise_post,\n virtual_triggers,\n /**\n * @param param0\n * @param param1\n * @returns\n */\n authorise_get: async ({ query, table_id, req, }, { authorizeGetQuery }) => {\n return await authorizeGetQuery(query, table_id);\n },\n /**\n * @param param0\n * @returns\n */\n getStringsForI18n({ layout }) {\n return getStringsForI18n(layout);\n },\n /**\n * @param param0\n * @returns\n */\n queries: ({ table_id, name, configuration: { columns, default_state, layout, auto_save, split_paste, destination_type, fixed, confirm_leave, auto_create, delete_unchanged_auto_create, enable_realtime, update_events, }, req, res, }) => ({\n async editQuery(state, mobileReferrer, isPreview, hiddenLoginDest) {\n const table = table_1.default.findOne({ id: table_id });\n const fields = table.getFields();\n const { uniques } = (0, viewable_fields_1.splitUniques)(fields, state);\n let row = null;\n let auto_created_row = false;\n const unique_constraints = table.constraints.filter((tc) => tc.type === \"Unique\");\n const getRow = async (where) => {\n const joinFields = {};\n const picked = (0, plugin_helper_1.picked_fields_to_query)([], fields, layout, req, table);\n const colFields = new Set(columns.map((c) => c.join_field ? c.join_field.split(\".\")[0] : c.field_name));\n Object.entries(picked.joinFields).forEach(([nm, jfv]) => {\n if (!colFields.has(jfv.ref))\n joinFields[nm] = jfv;\n });\n return await table.getJoinedRow({\n where,\n joinFields,\n forPublic: !req.user,\n forUser: req.user,\n });\n };\n if (Object.keys(uniques).length > 0) {\n row = await getRow(uniques);\n }\n else if (unique_constraints.length) {\n for (const tc of unique_constraints) {\n const fields = tc.configuration.fields;\n if (fields &&\n (fields || []).every((fname) => typeof state[fname] !== \"undefined\")) {\n const where = {};\n fields.forEach((fnm) => (where[fnm] = state[fnm]));\n row = await getRow(where);\n break;\n }\n }\n }\n if (!row && auto_create && !isPreview) {\n row = {};\n fields.forEach((f) => {\n if (typeof state[f.name] !== \"undefined\") {\n if (f.type?.read)\n row[f.name] = f.type?.read\n ? f.type.read(state[f.name], f.attributes)\n : state[f.name];\n }\n else if (f.required)\n if (typeof f.attributes?.default !== \"undefined\" &&\n f.attributes?.default !== null)\n row[f.name] = f.attributes.default;\n else if (f.type.sql_name === \"text\")\n row[f.name] = \"\";\n });\n const use_fixed = await (0, viewable_fields_1.fill_presets)(table, req, fixed);\n row = { ...row, ...use_fixed };\n row.id = await table.insertRow(row, req.user);\n auto_created_row = true;\n }\n const isRemote = !isWeb(req);\n return await render({\n table,\n fields,\n viewname: name,\n columns,\n layout,\n row,\n req,\n res,\n state,\n auto_save,\n destination_type,\n isRemote,\n split_paste,\n confirm_leave,\n mobileReferrer,\n delete_unchanged_auto_create,\n isPreview,\n auto_created_row,\n hiddenLoginDest,\n enable_realtime,\n update_events,\n });\n },\n async editManyQuery(state, { limit, offset, orderBy, orderDesc, where }) {\n const table = table_1.default.findOne({ id: table_id });\n const fields = table.getFields();\n const { joinFields, aggregations } = (0, plugin_helper_1.picked_fields_to_query)(columns, fields, undefined, req, table);\n const qstate = (0, plugin_helper_1.stateFieldsToWhere)({\n fields,\n state,\n table,\n prefix: \"a.\",\n });\n const q = (0, plugin_helper_1.stateFieldsToQuery)({ state, fields });\n if (where)\n mergeIntoWhere(qstate, where);\n const rows = await table.getJoinedRows({\n where: qstate,\n joinFields,\n aggregations,\n ...(limit && { limit: limit }),\n ...(offset && { offset: offset }),\n ...(orderBy && { orderBy: orderBy }),\n ...(orderDesc && { orderDesc: orderDesc }),\n ...q,\n forPublic: !req.user,\n forUser: req.user,\n });\n return {\n table,\n fields,\n rows,\n };\n },\n async tryInsertQuery(row) {\n const table = table_1.default.findOne({ id: table_id });\n const result = {};\n const ins_res = await table.tryInsertRow(row, req.user || { role_id: 100 }, result);\n ins_res.trigger_return = result;\n return ins_res;\n },\n async tryUpdateQuery(row, id) {\n const table = table_1.default.findOne(table_id);\n return await tryUpdateImpl(row, id, table, req);\n },\n async saveFileQuery(fieldVal, fieldId, fieldView, row) {\n const field = await field_1.default.findOne({ id: fieldId });\n const column = columns.find((c) => c.type === \"Field\" && c.field_name === field.name);\n field.fieldviewObj = getState().fileviews[fieldView];\n const [pre, allData] = fieldVal.split(\",\");\n const buffer = (__webpack_require__(/*! buffer/ */ \"../../node_modules/buffer/index.js\").Buffer).from(allData, \"base64\");\n const mimetype = pre.split(\";\")[0].split(\":\")[1];\n const filename = field.fieldviewObj?.setsDataURL?.get_filename?.({\n ...row,\n ...field.attributes,\n }) || \"file\";\n const folder = field.fieldviewObj?.setsDataURL?.get_folder?.({\n ...row,\n ...field.attributes,\n ...(column?.configuration || {}),\n });\n const file = await file_1.default.from_contents(filename, mimetype, buffer, req.user?.id, field.attributes.min_role_read || 1, folder);\n return file_1.default.fieldValueFromRelative(file.path_to_serve);\n },\n async saveFileFromContentsQuery(fieldVal, fieldId, fieldView, row, filename, encoding = \"base64\") {\n const field = await field_1.default.findOne({ id: fieldId });\n const column = columns.find((c) => c.type === \"Field\" && c.field_name === field.name);\n field.fieldviewObj = getState().fileviews[fieldView];\n let mimetype, allData;\n if (encoding == \"base64\") {\n let [pre, allData0] = fieldVal.split(\",\");\n mimetype = pre.split(\";\")[0].split(\":\")[1];\n allData = allData0;\n }\n else {\n allData = fieldVal;\n mimetype =\n (filename && file_1.default.nameToMimeType(filename)) ||\n \"application/octet-stream\";\n }\n const buffer = (__webpack_require__(/*! buffer/ */ \"../../node_modules/buffer/index.js\").Buffer).from(allData, encoding);\n const filename1 = filename || \"file\";\n const existing_file = await file_1.default.findOne(filename1);\n if (existing_file) {\n if (existing_file.min_role_read >= (req.user?.role_id || 100)) {\n await existing_file.overwrite_contents(buffer);\n return file_1.default.fieldValueFromRelative(existing_file.path_to_serve);\n }\n else\n throw new Error(\"Not authorized to write file\");\n }\n const file = await file_1.default.from_contents(filename1, mimetype, buffer, req.user?.id, field.attributes.min_role_read || 1);\n return file_1.default.fieldValueFromRelative(file.path_to_serve);\n },\n async authorizePostQuery(body, table_id) {\n return await doAuthPost({ body, table_id, req });\n },\n async authorizeGetQuery(query, table_id) {\n let body = query || {};\n const table = table_1.default.findOne({ id: table_id });\n if (Object.keys(body).length == 1) {\n if (table.ownership_field_id || table.ownership_formula) {\n const fields = table.getFields();\n const { uniques } = (0, viewable_fields_1.splitUniques)(fields, body);\n if (Object.keys(uniques).length > 0) {\n const joinFields = {};\n if (table.ownership_formula) {\n const freeVars = freeVariables(table.ownership_formula);\n (0, plugin_helper_1.add_free_variables_to_joinfields)(freeVars, joinFields, fields);\n }\n const row = await table.getJoinedRows({\n where: uniques,\n joinFields,\n });\n if (row.length > 0)\n return table.is_owner(req.user, row[0]);\n else\n return true;\n }\n else {\n return true;\n }\n }\n }\n else {\n return table.ownership_field_id || table.ownership_formula;\n }\n return doAuthPost({ body, table_id, req });\n },\n async getRowQuery(table_id, view_select, row_id, order_field) {\n const childTable = table_1.default.findOne({ id: table_id });\n return await childTable.getRows({\n [view_select.field_name]: row_id,\n }, {\n forPublic: !req.user,\n forUser: req.user,\n orderBy: order_field || undefined,\n });\n },\n async getRowByIdQuery(id) {\n const table = table_1.default.findOne({ id: table_id });\n return await table.getRow(typeof id === \"object\" ? id : { id }, {\n forUser: req.user,\n forPublic: !req.user,\n });\n },\n async actionQuery() {\n const { rndid, _csrf, onchange_action, onchange_field, click_action, ...body } = req.body || {};\n const table = table_1.default.findOne({ id: table_id });\n const pk_name = table.pk_name;\n let row = body[pk_name]\n ? (await table.getRow({ [pk_name]: body[pk_name] }, {\n forPublic: !req.user,\n forUser: req.user,\n })) || {}\n : {};\n table.fields.forEach((f) => {\n if (!f?.validate)\n return;\n const valres = f.validate(body);\n if (\"success\" in valres)\n row[f.name] = valres.success;\n });\n if (fixed) {\n const use_fixed = await (0, viewable_fields_1.fill_presets)(table, req, fixed);\n Object.keys(use_fixed).forEach((k) => {\n if (row[k] === null || typeof row[k] === \"undefined\")\n row[k] = use_fixed[k];\n });\n }\n try {\n return await db_1.default.withTransaction(async () => {\n if (click_action) {\n let container;\n traverseSync(layout, {\n container(segment) {\n if (segment.click_action === click_action)\n container = segment;\n },\n });\n if (!container)\n return { json: { error: \"Action not found\" } };\n const trigger = trigger_1.default.findOne({ name: click_action });\n if (!trigger)\n throw new Error(`View ${name}: Container click action ${click_action} not found`);\n const result = await trigger.runWithoutRow({\n table,\n Table: table_1.default,\n req,\n row,\n referrer: req?.get?.(\"Referrer\"),\n user: req.user,\n });\n return { json: { success: \"ok\", ...(result || {}) } };\n }\n else if (onchange_action && !rndid) {\n const fldCol = columns.find((c) => c.field_name === onchange_field &&\n c.onchange_action === onchange_action);\n if (!fldCol)\n return { json: { error: \"Field not found\" } };\n const trigger = trigger_1.default.findOne({ name: onchange_action });\n if (!trigger)\n throw new Error(`View ${name}: On change action ${onchange_action} for field ${onchange_field} not found`);\n const result = await trigger.runWithoutRow({\n table,\n Table: table_1.default,\n req,\n row,\n referrer: req?.get?.(\"Referrer\"),\n user: req.user,\n });\n return { json: { success: \"ok\", ...(result || {}) } };\n }\n else {\n const col = columns.find((c) => c.type === \"Action\" && c.rndid === rndid && rndid);\n const result = await (0, plugin_helper_1.run_action_column)({\n col,\n req,\n table,\n row,\n res,\n referrer: req?.get?.(\"Referrer\"),\n columns,\n viewname: name,\n });\n return { json: { success: \"ok\", ...(result || {}) } };\n }\n });\n }\n catch (e) {\n console.error(e);\n return { json: { error: e.message || e } };\n }\n },\n async optionsQuery(reftable_name, type, attributes, where) {\n const refTable = table_1.default.findOne({ name: reftable_name });\n const rows = await refTable.getRows(where, {\n forUser: req.user,\n forPublic: !req.user,\n });\n return rows;\n },\n async updateMatchingQuery(where, updateVals, repeatFields, childRows) {\n const table = table_1.default.findOne(table_id);\n const rows = await table.getRows(where, {\n forUser: req.user,\n forPublic: !req.user,\n });\n const results = [];\n let inTransaction = false;\n try {\n if (rows.length === 0)\n return results;\n await db_1.default.begin();\n inTransaction = true;\n for (const row of rows) {\n const uptRes = await tryUpdateImpl(updateVals, row.id, table, req);\n if (uptRes.error) {\n inTransaction = false;\n await db_1.default.rollback();\n return { rowError: uptRes.error };\n }\n results.push(uptRes);\n for (const field of repeatFields) {\n const childTable = table_1.default.findOne({ id: field.metadata?.table_id });\n await childTable.deleteRows({ [field.metadata?.relation]: row.id }, req.user || { role_id: 100 });\n for (const childRow of childRows[field.name]) {\n childRow[field.metadata?.relation] = row.id;\n const insRow = { ...childRow };\n delete insRow[childTable.pk_name];\n const insRes = await childTable.tryInsertRow(insRow, req.user || { role_id: 100 });\n if (insRes.error) {\n inTransaction = false;\n await db_1.default.rollback();\n return { inEditError: insRes.error };\n }\n }\n }\n }\n if (inTransaction)\n await db_1.default.commit();\n }\n catch (error) {\n if (inTransaction)\n await db_1.default.rollback();\n return { error: error.message };\n }\n return results;\n },\n }),\n routes: { run_action, update_matching_rows },\n /**\n * @param table_id\n * @param title\n * @param state\n * @returns\n */\n async interpolate_title_string(table_id, title, state) {\n const tbl = table_1.default.findOne(table_id);\n if (state?.[tbl.pk_name]) {\n const freeVars = freeVariablesInInterpolation(title);\n const joinFields = {};\n const aggregations = {};\n (0, plugin_helper_1.add_free_variables_to_joinfields)(freeVars, joinFields, tbl.fields);\n add_free_variables_to_aggregations(freeVars, aggregations, tbl);\n const row = await tbl.getJoinedRow({\n where: { [tbl.pk_name]: state[tbl.pk_name] },\n joinFields,\n aggregations,\n });\n return interpolate(title, row, null, \"Edit view title string\");\n }\n else {\n return interpolate(title, null, null, \"Edit view title string\");\n }\n },\n /**\n * @param view\n * @returns\n */\n configCheck: async (view) => {\n const { name, configuration: { view_when_done, destination_type, dest_url_formula, formula_destinations, page_when_done, page_group_when_done, }, } = view;\n const errs = [];\n const warnings = [];\n if (!destination_type || destination_type === \"View\") {\n const vwd = view_1.default.findOne({\n name: (view_when_done || \"\").split(\".\")[0],\n });\n if (!vwd)\n warnings.push(`In View ${name}, view when done ${view_when_done} not found`);\n }\n if (destination_type === \"Page\") {\n const page = page_1.default.findOne({ name: page_when_done });\n if (!page)\n errs.push(`In View ${name}, page when done ${page_when_done} not found`);\n }\n if (destination_type === \"PageGroup\") {\n const group = PageGroup.findOne({ name: page_group_when_done });\n if (!group)\n errs.push(`In View ${name}, page group when done ${page_group_when_done} not found`);\n }\n if (destination_type === \"Formula\") {\n for (const { expression } of formula_destinations || []) {\n if (expression)\n expressionChecker(expression, `In View ${name}, destination formula ${expression} error: `, errs);\n }\n }\n if (destination_type === \"URL Formula\") {\n expressionChecker(dest_url_formula, `In View ${name}, URL formula ${dest_url_formula} error: `, errs);\n }\n const colcheck = await (0, plugin_testing_1.check_view_columns)(view, view.configuration.columns);\n errs.push(...colcheck.errors);\n warnings.push(...colcheck.warnings);\n return { errors: errs, warnings };\n },\n /**\n * @param configuration\n * @returns\n */\n connectedObjects: async (configuration) => {\n return (0, node_extract_utils_1.extractFromLayout)(configuration.layout);\n },\n};\n//# sourceMappingURL=edit.js.map\n\n//# sourceURL=webpack://saltcorn/../saltcorn-data/dist/base-plugin/viewtemplates/edit.js?\n}");
72
-
73
- /***/ },
74
-
75
- /***/ "../saltcorn-data/dist/base-plugin/viewtemplates/feed.js"
76
- /*!***************************************************************!*\
77
- !*** ../saltcorn-data/dist/base-plugin/viewtemplates/feed.js ***!
78
- \***************************************************************/
79
- (module, __unused_webpack_exports, __webpack_require__) {
80
-
81
- "use strict";
82
- eval("{\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\n/**\n * @category saltcorn-data\n * @module base-plugin/viewtemplates/feed\n * @subcategory base-plugin\n */\nconst table_1 = __importDefault(__webpack_require__(/*! ../../models/table */ \"../saltcorn-data/dist/models/table.js\"));\nconst view_1 = __importDefault(__webpack_require__(/*! ../../models/view */ \"../saltcorn-data/dist/models/view.js\"));\nconst field_1 = __importDefault(__webpack_require__(/*! ../../models/field */ \"../saltcorn-data/dist/models/field.js\"));\nconst form_1 = __importDefault(__webpack_require__(/*! ../../models/form */ \"../saltcorn-data/dist/models/form.js\"));\nconst workflow_1 = __importDefault(__webpack_require__(/*! ../../models/workflow */ \"../saltcorn-data/dist/models/workflow.js\"));\nconst { text, div, h4, hr, a, h3, button, code, h2, ul, li, } = __webpack_require__(/*! @saltcorn/markup/tags */ \"../saltcorn-markup/dist/tags.js\");\nconst { pagination } = __webpack_require__(/*! @saltcorn/markup/helpers */ \"../saltcorn-markup/dist/helpers.js\");\nconst { renderForm, tabs, link } = __webpack_require__(/*! @saltcorn/markup */ \"../saltcorn-markup/dist/index.js\");\nconst { mkTable } = __webpack_require__(/*! @saltcorn/markup */ \"../saltcorn-markup/dist/index.js\");\nconst pluralize = __webpack_require__(/*! pluralize */ \"../../node_modules/pluralize/pluralize.js\");\nconst plugin_helper_1 = __webpack_require__(/*! ../../plugin-helper */ \"../saltcorn-data/dist/plugin-helper.js\");\nconst { InvalidConfiguration, isNode, isWeb, mergeConnectedObjects, hashState, } = __webpack_require__(/*! ../../utils */ \"../saltcorn-data/dist/utils.js\");\nconst { getState } = __webpack_require__(/*! ../../db/state */ \"../saltcorn-data/dist/db/state.js\");\nconst { jsexprToWhere, eval_expression, add_free_variables_to_joinfields, freeVariables, } = __webpack_require__(/*! ../../models/expression */ \"../saltcorn-data/dist/models/expression.js\");\nconst { extractFromLayout, extractViewToCreate, } = __webpack_require__(/*! ../../diagram/node_extract_utils */ \"../saltcorn-data/dist/diagram/node_extract_utils.js\");\n/**\n * @param {object} req\n * @returns {Workflow}\n */\nconst configuration_workflow = (req) => new workflow_1.default({\n steps: [\n {\n name: req.__(\"Views\"),\n form: async (context) => {\n const table = table_1.default.findOne(context.table_id);\n const show_views = await view_1.default.find_table_views_where(context.table_id, ({ state_fields, viewtemplate, viewrow }) => viewtemplate.runMany &&\n viewrow.name !== context.viewname &&\n state_fields.some((sf) => sf.name === \"id\"));\n const create_views = await view_1.default.find_table_views_where(context.table_id, ({ state_fields, viewrow }) => viewrow.name !== context.viewname &&\n state_fields.every((sf) => !sf.required));\n const show_view_opts = show_views.map((v) => v.select_option);\n const create_view_opts = create_views.map((v) => v.select_option);\n return new form_1.default({\n fields: [\n {\n name: \"show_view\",\n label: req.__(\"Single item view\"),\n type: \"String\",\n sublabel: req.__(\"The underlying individual view of each table row\") +\n \". \" +\n a({\n \"data-dyn-href\": `\\`/viewedit/config/\\${show_view}\\``,\n target: \"_blank\",\n }, req.__(\"Configure\")),\n required: true,\n attributes: {\n options: show_view_opts,\n },\n },\n {\n name: \"empty_view\",\n label: req.__(\"Empty view\"),\n type: \"String\",\n sublabel: req.__(\"A view that will be shown only if there are no tables rows to show\"),\n attributes: {\n options: create_view_opts,\n },\n },\n {\n input_type: \"section_header\",\n label: \"Creating a new view\",\n },\n {\n name: \"view_to_create\",\n label: req.__(\"Use view to create\"),\n sublabel: req.__(\"If user has write permission. Leave blank to have no link to create a new item\") +\n \". \" +\n a({\n \"data-dyn-href\": `\\`/viewedit/config/\\${view_to_create}\\``,\n \"data-show-if\": \"showIfFormulaInputs($('select[name=view_to_create]'), 'view_to_create')\",\n target: \"_blank\",\n }, req.__(\"Configure\")),\n type: \"String\",\n attributes: {\n options: create_view_opts,\n },\n },\n {\n name: \"create_view_display\",\n label: req.__(\"Display create view as\"),\n type: \"String\",\n required: true,\n attributes: {\n options: [\"Link\", \"Embedded\", \"Popup\"],\n },\n showIf: {\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n {\n name: \"create_view_showif\",\n label: req.__(\"Show if formula\"),\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n table: table.name,\n user: true,\n expression_type: \"boolean\",\n },\n sublabel: req.__(\"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>.\"),\n showIf: {\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n {\n name: \"create_view_label\",\n label: req.__(\"Label for create\"),\n sublabel: req.__(\"Label in link or button to create. Leave blank for a default label\"),\n type: \"String\",\n attributes: { asideNext: true },\n showIf: {\n create_view_display: [\"Link\", \"Popup\"],\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n {\n name: \"create_view_location\",\n label: req.__(\"Location\"),\n sublabel: req.__(\"Location of link to create new row\"),\n required: true,\n attributes: {\n options: [\n \"Bottom left\",\n \"Bottom right\",\n \"Top left\",\n \"Top right\",\n ],\n },\n type: \"String\",\n showIf: {\n create_view_display: [\"Link\", \"Popup\"],\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n {\n name: \"create_view_location\",\n label: req.__(\"Location\"),\n sublabel: req.__(\"Location of view to create new row\"),\n required: true,\n attributes: {\n options: [\"Bottom\", \"Top\"],\n },\n type: \"String\",\n showIf: {\n create_view_display: [\"Embedded\"],\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n {\n name: \"create_link_style\",\n label: req.__(\"Link Style\"),\n type: \"String\",\n required: true,\n attributes: {\n asideNext: true,\n options: [\n { name: \"\", label: \"Link\" },\n { name: \"btn btn-primary\", label: \"Primary button\" },\n { name: \"btn btn-secondary\", label: \"Secondary button\" },\n { name: \"btn btn-success\", label: \"Success button\" },\n { name: \"btn btn-danger\", label: \"Danger button\" },\n {\n name: \"btn btn-outline-primary\",\n label: \"Primary outline button\",\n },\n {\n name: \"btn btn-outline-secondary\",\n label: \"Secondary outline button\",\n },\n ],\n },\n showIf: {\n create_view_display: [\"Link\", \"Popup\"],\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n {\n name: \"create_link_size\",\n label: req.__(\"Link size\"),\n type: \"String\",\n required: true,\n attributes: {\n options: [\n { name: \"\", label: \"Standard\" },\n { name: \"btn-lg\", label: \"Large\" },\n { name: \"btn-sm\", label: \"Small\" },\n { name: \"btn-sm btn-xs\", label: \"X-Small\" },\n { name: \"btn-block\", label: \"Block\" },\n { name: \"btn-block btn-lg\", label: \"Large block\" },\n ],\n },\n showIf: { create_view_display: [\"Link\", \"Popup\"] },\n },\n ...(table.ownership_field_id\n ? [\n {\n name: \"always_create_view\",\n label: req.__(\"Always show create view\"),\n sublabel: req.__(\"If off, only show create view if the query state is about the current user\"),\n type: \"Bool\",\n showIf: {\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n ]\n : []),\n ],\n });\n },\n },\n {\n name: req.__(\"Order and layout\"),\n form: async (context) => {\n const table = table_1.default.findOne({ id: context.table_id });\n const fields = table.getFields();\n const { child_field_list, child_relations } = await table.get_child_relations();\n return new form_1.default({\n fields: [\n {\n name: \"order_field\",\n label: req.__(\"Order by\"),\n type: \"String\",\n required: true,\n attributes: {\n asideNext: true,\n options: fields.map((f) => f.name),\n },\n },\n {\n name: \"descending\",\n label: req.__(\"Descending\"),\n type: \"Bool\",\n required: true,\n },\n {\n name: \"groupby\",\n label: req.__(\"Group by\"),\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n table: table.name,\n user: true,\n expression_type: \"value\",\n },\n sublabel: \"Formula for the group headings\",\n class: \"validate-expression\",\n },\n {\n name: \"rows_per_page\",\n label: req.__(\"Items per page\"),\n type: \"Integer\",\n attributes: {\n min: 1,\n },\n required: true,\n default: 20,\n },\n {\n name: \"view_decoration\",\n label: req.__(\"View decoration\"),\n type: \"String\",\n attributes: { options: [\"None\", \"Card\", \"Accordion\", \"Tabs\"] },\n required: true,\n },\n /*{\n name: \"in_card\",\n label: req.__(\"Each in card?\"),\n type: \"Bool\",\n required: true,\n },*/\n {\n name: \"masonry_columns\",\n label: req.__(\"Masonry columns\"),\n type: \"Bool\",\n showIf: { view_decoration: \"Card\" },\n required: true,\n },\n {\n name: \"title_formula\",\n label: req.__(\"Title formula\"),\n class: \"validate-expression\",\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n table: table.name,\n user: true,\n expression_type: \"value\",\n },\n showIf: { view_decoration: [\"Card\", \"Accordion\", \"Tabs\"] },\n },\n {\n name: \"initial_open_accordions\",\n label: req.__(\"Initially open\"),\n type: \"String\",\n fieldview: \"radio_group\",\n attributes: { options: [\"None\", \"All\", \"First\"], inline: true },\n required: true,\n showIf: { view_decoration: \"Accordion\" },\n },\n {\n name: \"lazy_accordions\",\n label: req.__(\"Lazy load views\"),\n type: \"Bool\",\n showIf: { view_decoration: \"Accordion\" },\n },\n {\n name: \"hide_pagination\",\n label: req.__(\"Hide pagination\"),\n type: \"Bool\",\n required: true,\n },\n {\n name: \"local_state\",\n label: req.__(\"Local state\"),\n type: \"Bool\",\n sublabel: req.__(\"Isolate state of each repeated view\"),\n required: true,\n },\n {\n input_type: \"section_header\",\n label: \"Row restrictions\",\n },\n {\n name: \"include_fml\",\n label: req.__(\"Row inclusion formula\"),\n class: \"validate-expression\",\n sublabel: req.__(\"Only include rows where this formula is true. \") +\n req.__(\"In scope:\") +\n \" \" +\n [\n ...fields.map((f) => f.name),\n \"user\",\n \"year\",\n \"month\",\n \"day\",\n \"today()\",\n ]\n .map((s) => code(s))\n .join(\", \"),\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n table: table.name,\n user: true,\n expression_type: \"boolean\",\n },\n help: {\n topic: \"Inclusion Formula\",\n context: { table_name: table.name },\n },\n },\n {\n name: \"exclusion_relation\",\n label: req.__(\"Exclusion relations\"),\n sublabel: req.__(\"Do not include row if this relation has a match\"),\n type: \"String\",\n required: false,\n attributes: { options: child_field_list },\n },\n {\n name: \"exclusion_where\",\n label: req.__(\"Exclusion where\"),\n class: \"validate-expression\",\n type: \"String\",\n showIf: { exclusion_relation: child_field_list },\n },\n {\n input_type: \"section_header\",\n label: \"Number of columns (1-4) by screen width\",\n },\n {\n name: \"cols_sm\",\n label: req.__(\"Small\"),\n type: \"Integer\",\n attributes: {\n asideNext: true,\n min: 1,\n max: 4,\n },\n required: true,\n default: 1,\n },\n {\n name: \"cols_md\",\n label: req.__(\"Medium\"),\n type: \"Integer\",\n attributes: {\n min: 1,\n max: 4,\n },\n required: true,\n default: 1,\n },\n {\n name: \"cols_lg\",\n label: req.__(\"Large\"),\n type: \"Integer\",\n attributes: {\n asideNext: true,\n min: 1,\n max: 4,\n },\n required: true,\n default: 1,\n },\n {\n name: \"cols_xl\",\n label: req.__(\"Extra-large\"),\n type: \"Integer\",\n attributes: {\n min: 1,\n max: 4,\n },\n required: true,\n default: 1,\n },\n ],\n });\n },\n },\n ],\n});\n/**\n * @param {number} table_id\n * @param {*} viewname\n * @param {object} opts\n * @param {*} opts.show_view\n * @returns {Promise<Field>}\n */\nconst get_state_fields = async (table_id, viewname, { show_view }) => {\n const table = table_1.default.findOne(table_id);\n const table_fields = table.fields;\n return table_fields\n .filter((f) => !f.primary_key)\n .map((f) => {\n const sf = new field_1.default(f);\n sf.required = false;\n return sf;\n });\n};\n/**\n * @param {number} table_id\n * @param {string} viewname\n * @param {object} opts\n * @param {string} opts.show_view\n * @param {name} opts.order_field\n * @param {boolean} opts.descending\n * @param {string} [opts.view_to_create]\n * @param {string} opts.create_view_display\n * @param {boolean} opts.in_card\n * @param {string} opts.masonry_columns\n * @param {number} [opts.rows_per_page = 20]\n * @param {boolean} opts.hide_pagination\n * @param {string} [opts.create_view_label]\n * @param {string} [opts.create_view_location]\n * @param {boolean} opts.always_create_view\n * @param {*} opts.cols\n * @param {object} state\n * @param {*} extraArgs\n * @returns {Promise<div>}\n */\nconst run = async (table_id, viewname, { show_view, order_field, descending, view_to_create, create_view_display, in_card, //legacy\nview_decoration, initial_open_accordions = \"None\", title_formula, masonry_columns, rows_per_page = 20, hide_pagination, create_view_label, create_view_location, create_link_style, create_link_size, create_view_showif, always_create_view, include_fml, exclusion_relation, exclusion_where, empty_view, groupby, lazy_accordions, local_state, ...cols }, state, extraArgs, { countRowsQuery, runManyQuery, }) => {\n const table = table_1.default.findOne({ id: table_id });\n const fields = table.getFields();\n (0, plugin_helper_1.readState)(state, fields);\n const stateHash = hashState(state, show_view);\n const appState = getState();\n const locale = extraArgs.req.getLocale();\n const __ = isNode()\n ? (s) => appState.i18n.__({ phrase: s, locale }) || s\n : (s) => s;\n if (!show_view)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: Single item view not specified`);\n const sview = view_1.default.findOne({ name: show_view });\n if (!sview)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find view ${show_view}`);\n const q = (0, plugin_helper_1.stateFieldsToQuery)({ state, fields });\n let qextra = {};\n if (!q.orderBy) {\n qextra.orderBy = order_field;\n if (descending)\n qextra.orderDesc = true;\n }\n qextra.limit = q.limit || rows_per_page;\n const current_page = parseInt(state[`_${stateHash}_page`]) || 1;\n const user_id = extraArgs && extraArgs.req.user ? extraArgs.req.user.id : null;\n if (include_fml)\n qextra.where = jsexprToWhere(include_fml, {\n ...state,\n user_id,\n user: extraArgs?.req?.user,\n }, table.fields);\n if (exclusion_relation) {\n const [reltable, relfld] = exclusion_relation.split(\".\");\n const relTable = table_1.default.findOne({ name: reltable });\n const relWhere = exclusion_where\n ? jsexprToWhere(exclusion_where, {\n user_id,\n user: extraArgs?.req?.user,\n }, relTable.fields)\n : {};\n const relRows = await relTable.getRows(relWhere);\n if (!qextra.where)\n qextra.where = {};\n // TODO sqlite not in\n qextra.where.id = { not: { in: relRows.map((r) => r[relfld]) } };\n }\n qextra.joinFields = {};\n add_free_variables_to_joinfields(freeVariables(title_formula), qextra.joinFields, fields);\n add_free_variables_to_joinfields(freeVariables(groupby || \"\"), qextra.joinFields, fields);\n const { req, res, ...selectOpts } = extraArgs;\n const sresp = await runManyQuery(state, qextra, selectOpts);\n let paginate = \"\";\n if (sresp.length === 0 && empty_view) {\n const emptyView = view_1.default.findOne({ name: empty_view });\n if (!emptyView)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find empty view ${empty_view}`);\n return div({\n class: \"d-inline\",\n \"data-sc-embed-viewname\": emptyView.name,\n }, await emptyView.run(state, extraArgs));\n }\n let totalRows;\n if (!hide_pagination && (sresp.length === qextra.limit || current_page > 1)) {\n totalRows = await countRowsQuery(state);\n if (totalRows > qextra.limit || current_page > 1) {\n paginate = pagination({\n current_page,\n pages: Math.ceil(totalRows / qextra.limit),\n get_page_link: (n) => `gopage(${n}, ${qextra.limit}, '${stateHash}', {}, this)`,\n });\n }\n }\n const [vpos, hpos] = (create_view_location || \"Bottom left\").split(\" \");\n const istop = vpos === \"Top\";\n const isright = hpos === \"right\";\n const role = extraArgs && extraArgs.req && extraArgs.req.user\n ? extraArgs.req.user.role_id\n : 100;\n var create_link = \"\";\n const about_user = fields.some((f) => f.reftable_name === \"users\" && state[f.name] && state[f.name] === user_id);\n const create_link_showif_pass = create_view_showif\n ? eval_expression(create_view_showif, state, extraArgs.req.user, \"Create view show if formula\")\n : undefined;\n if (view_to_create) {\n const create_view = await view_1.default.findOne({ name: view_to_create });\n const ownership_field = table.ownership_field_id &&\n table.fields.find((f) => f.id === table.ownership_field_id);\n if (create_link_showif_pass !== false &&\n create_view &&\n (create_link_showif_pass ||\n role <= table.min_role_write ||\n (role < 100 &&\n table.ownership_field_id &&\n (about_user ||\n always_create_view ||\n create_view?.configuration?.fixed?.[`preset_${ownership_field?.name}`] === \"LoggedIn\")))) {\n if (create_view_display === \"Embedded\") {\n if (!create_view)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find embedded view to create ${view_to_create}`);\n create_link = await create_view.run(state, extraArgs);\n }\n else {\n const target = `/view/${encodeURIComponent(view_to_create)}${(0, plugin_helper_1.stateToQueryString)(state)}`;\n const hrefVal = isWeb(extraArgs.req) || create_view_display === \"Popup\"\n ? target\n : `javascript:execLink('${target}');`;\n create_link = (0, plugin_helper_1.link_view)(hrefVal, __(create_view_label) || `Add ${pluralize(table.name, 1)}`, create_view_display === \"Popup\" ? { reload_view: viewname } : false, create_link_style, create_link_size);\n }\n }\n }\n const create_link_div = isright\n ? div({ class: \"float-end\" }, create_link)\n : create_link;\n const setCols = (sz) => `col-${sz}-${Math.round(12 / cols[`cols_${sz}`])}`;\n const wrapScEmbed = (r, neverLazy) => div({\n class: \"d-inline\",\n \"data-sc-embed-viewname\": show_view,\n \"data-sc-view-source\": local_state\n ? false\n : `/view/${show_view}?${table.pk_name}=${r.row[table.pk_name]}`,\n \"data-sc-local-state\": local_state\n ? `/view/${show_view}?${table.pk_name}=${r.row[table.pk_name]}`\n : false,\n }, view_decoration === \"Accordion\" && lazy_accordions && !neverLazy\n ? \"\"\n : r.html);\n const showRowInner = (r, ix) => (!view_decoration && in_card) || view_decoration === \"Card\"\n ? div({ class: `card shadow ${masonry_columns ? \"mt-2\" : \"mt-4 h-100\"}` }, title_formula\n ? div({ class: \"card-header\" }, eval_expression(title_formula, r.row, extraArgs.req.user, \"Card title formula\"))\n : undefined, div({ class: \"card-body\" }, wrapScEmbed(r)))\n : view_decoration === \"Tabs\"\n ? div({\n class: [\"tab-pane fade\", ix == 0 && \"show active\"],\n id: `feedtab${viewname.replaceAll(\" \", \"_\")}_${ix}`,\n role: \"tabpanel\",\n \"aria-labelledby\": `feedtab${viewname.replaceAll(\" \", \"_\")}_${ix}-tab`,\n }, wrapScEmbed(r))\n : view_decoration === \"Accordion\"\n ? div({ class: \"accordion-item\" }, h2({ class: \"accordion-header\", id: `a${stateHash}head${ix}` }, button({\n class: [\n \"accordion-button\",\n (initial_open_accordions === \"None\" ||\n (initial_open_accordions === \"First\" && ix > 0)) &&\n \"collapsed\",\n ],\n type: \"button\",\n \"data-bs-toggle\": \"collapse\",\n \"data-bs-target\": `#a${stateHash}tab${ix}`,\n \"aria-expanded\": \"false\",\n \"aria-controls\": `a${stateHash}tab${ix}`,\n }, (title_formula\n ? eval_expression(title_formula, r.row, extraArgs.req.user, `Accordion title formula`)\n : \"\") || \"Missing title\")), div({\n class: [\n \"accordion-collapse\",\n \"collapse\",\n !(initial_open_accordions === \"None\" ||\n (initial_open_accordions === \"First\" && ix > 0)) && \"show\",\n ],\n id: `a${stateHash}tab${ix}`,\n \"aria-labelledby\": `a${stateHash}head${ix}`,\n \"data-bs-parent\": `#top${stateHash}`,\n }, div({ class: [\"accordion-body\"] }, wrapScEmbed(r, !(initial_open_accordions === \"None\" ||\n (initial_open_accordions === \"First\" && ix > 0))))))\n : wrapScEmbed(r);\n const showRow = (r) => div({\n class: [setCols(\"sm\"), setCols(\"md\"), setCols(\"lg\"), setCols(\"xl\")],\n }, showRowInner(r, 0));\n const is_in_card = (!view_decoration && in_card) || view_decoration === \"Card\";\n const correct_order = ([main, pagin, create]) => istop ? [create, main, pagin] : [main, pagin, create];\n if (groupby) {\n const groups = {};\n for (const r of sresp) {\n const group = eval_expression(groupby, r.row, extraArgs.req.user, \"Group by expression\");\n if (!groups[group])\n groups[group] = [];\n groups[group].push(r);\n }\n return div({\n \"data-sc-state-hash\": stateHash,\n \"data-sc-rows-per-page\": String(qextra.limit),\n \"data-sc-total-rows\": totalRows !== undefined ? String(totalRows) : false,\n }, correct_order([\n Object.entries(groups).map(([group, sr]) => h3({ class: \"feed-group-header\" }, group) +\n (is_in_card && masonry_columns\n ? div({ class: \"card-columns\" }, sr.map(showRowInner))\n : view_decoration === \"Accordion\"\n ? div({\n class: [\n \"accordion\",\n lazy_accordions && \"lazy-accoordion\",\n ],\n id: `top${stateHash}`,\n }, sr.map(showRowInner))\n : div({\n class: [\n \"row\",\n !masonry_columns &&\n is_in_card &&\n `row-cols-md-${cols[`cols_md`]} g-4 mb-3`,\n ],\n }, sr.map(showRow)))),\n paginate,\n create_link_div,\n ]));\n }\n const tabHeader = ({ row }, ix) => {\n const title = (title_formula\n ? eval_expression(title_formula, row, extraArgs.req.user, `Tab title formula`)\n : \"\") || \"Missing title\";\n return li({ class: \"nav-item\" }, a({\n class: [\"nav-link\", ix == 0 && \"active\"],\n \"data-bs-toggle\": \"tab\",\n href: `#feedtab${viewname.replaceAll(\" \", \"_\")}_${ix}`,\n id: `feedtab${viewname.replaceAll(\" \", \"_\")}_${ix}-tab`,\n role: \"tab\",\n \"aria-controls\": \"home\",\n \"aria-selected\": \"true\",\n }, text(title)));\n };\n return div({\n \"data-sc-state-hash\": stateHash,\n \"data-sc-rows-per-page\": String(qextra.limit),\n \"data-sc-total-rows\": totalRows !== undefined ? String(totalRows) : false,\n }, correct_order([\n is_in_card && masonry_columns\n ? div({ class: \"card-columns\" }, sresp.map(showRowInner))\n : view_decoration === \"Tabs\"\n ? div(ul({ class: \"nav nav-tabs\", role: \"tablist\" }, sresp.map(tabHeader)), div({ class: \"tab-content\" }, sresp.map(showRowInner)))\n : view_decoration === \"Accordion\"\n ? div({\n class: [\"accordion\", lazy_accordions && \"lazy-accoordion\"],\n id: `top${stateHash}`,\n }, sresp.map(showRowInner))\n : div({\n class: [\n \"row\",\n !masonry_columns &&\n is_in_card &&\n `row-cols-md-${cols.cols_md} row-cols-sm-${cols.cols_sm} row-cols-sm-${cols.cols_lg} row-cols-cl-${cols.cols_xl} g-4 mb-3`,\n ],\n }, sresp.map(showRow)),\n paginate,\n create_link_div,\n ]));\n};\nmodule.exports = {\n /** @type {string} */\n name: \"Feed\",\n /** @type {string} */\n description: \"Show multiple rows by displaying a chosen view for each row, stacked or in columns\",\n configuration_workflow,\n run,\n get_state_fields,\n /** @type {boolean} */\n /**\n * @param {object} opts\n * @param {*} opts.create_view_label\n * @returns {string[]|Object[]}\n */\n getStringsForI18n({ create_view_label }) {\n if (create_view_label)\n return [create_view_label];\n else\n return [];\n },\n queries: ({ table_id, viewname, configuration: { show_view }, req, res, }) => ({\n async countRowsQuery(state) {\n const table = table_1.default.findOne({ id: table_id });\n const fields = table.getFields();\n const where = (0, plugin_helper_1.stateFieldsToWhere)({ fields, state, table });\n return await table.countRows(where, {\n forUser: req?.user,\n forPublic: !req?.user,\n });\n },\n async runManyQuery(state, qextra, selectOpts0) {\n // remove where\n const { where, ...selectOpts } = selectOpts0;\n const sview = view_1.default.findOne({ name: show_view });\n const extraArgs = { req, res, ...selectOpts };\n return await sview.runMany(state, {\n ...extraArgs,\n ...qextra,\n });\n },\n }),\n connectedObjects: async (configuration) => {\n const fromLayout = extractFromLayout(configuration.layout);\n const toCreate = extractViewToCreate(configuration);\n const result = toCreate ? mergeConnectedObjects(fromLayout, toCreate) : fromLayout;\n if (configuration.show_view) {\n const view = view_1.default.findOne({ name: configuration.show_view });\n if (view)\n (result.embeddedViews = result.embeddedViews || []).push(view);\n }\n if (configuration.empty_view) {\n const view = view_1.default.findOne({ name: configuration.empty_view });\n if (view)\n (result.embeddedViews = result.embeddedViews || []).push(view);\n }\n return result;\n },\n};\n//# sourceMappingURL=feed.js.map\n\n//# sourceURL=webpack://saltcorn/../saltcorn-data/dist/base-plugin/viewtemplates/feed.js?\n}");
83
-
84
- /***/ },
85
-
86
- /***/ "../saltcorn-data/dist/base-plugin/viewtemplates/filter.js"
87
- /*!*****************************************************************!*\
88
- !*** ../saltcorn-data/dist/base-plugin/viewtemplates/filter.js ***!
89
- \*****************************************************************/
90
- (module, __unused_webpack_exports, __webpack_require__) {
91
-
92
- "use strict";
93
- eval("{\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\n/**\n * @category saltcorn-data\n * @module base-plugin/viewtemplates/filter\n * @subcategory base-plugin\n */\nconst table_1 = __importDefault(__webpack_require__(/*! ../../models/table */ \"../saltcorn-data/dist/models/table.js\"));\nconst view_1 = __importDefault(__webpack_require__(/*! ../../models/view */ \"../saltcorn-data/dist/models/view.js\"));\nconst field_1 = __importDefault(__webpack_require__(/*! ../../models/field */ \"../saltcorn-data/dist/models/field.js\"));\nconst workflow_1 = __importDefault(__webpack_require__(/*! ../../models/workflow */ \"../saltcorn-data/dist/models/workflow.js\"));\nconst trigger_1 = __importDefault(__webpack_require__(/*! ../../models/trigger */ \"../saltcorn-data/dist/models/trigger.js\"));\nconst user_1 = __importDefault(__webpack_require__(/*! ../../models/user */ \"../saltcorn-data/dist/models/user.js\"));\nconst page_1 = __importDefault(__webpack_require__(/*! ../../models/page */ \"../saltcorn-data/dist/models/page.js\"));\nconst crash_1 = __importDefault(__webpack_require__(/*! ../../models/crash */ \"../saltcorn-data/dist/models/crash.js\"));\nconst PageGroup = __webpack_require__(/*! ../../models/page_group */ \"../saltcorn-data/dist/models/page_group.js\");\nconst tags_1 = __webpack_require__(/*! @saltcorn/markup/tags */ \"../saltcorn-markup/dist/tags.js\");\nconst renderLayout = __webpack_require__(/*! @saltcorn/markup/layout */ \"../saltcorn-markup/dist/layout.js\");\nconst plugin_helper_1 = __webpack_require__(/*! ../../plugin-helper */ \"../saltcorn-data/dist/plugin-helper.js\");\nconst viewable_fields_1 = __webpack_require__(/*! ../../viewable_fields */ \"../saltcorn-data/dist/viewable_fields.js\");\nconst { search_bar } = __webpack_require__(/*! @saltcorn/markup/helpers */ \"../saltcorn-markup/dist/helpers.js\");\nconst { eachView, translateLayout, getStringsForI18n, traverse, } = __webpack_require__(/*! ../../models/layout */ \"../saltcorn-data/dist/models/layout.js\");\nconst { InvalidConfiguration, objectToQueryString, removeEmptyStrings, asyncMap, getSessionId, mergeIntoWhere, isWeb, renderServerSide, interpolate, } = __webpack_require__(/*! ../../utils */ \"../saltcorn-data/dist/utils.js\");\nconst { jsexprToWhere } = __webpack_require__(/*! ../../models/expression */ \"../saltcorn-data/dist/models/expression.js\");\nconst Library = __webpack_require__(/*! ../../models/library */ \"../saltcorn-data/dist/models/library.js\");\nconst node_extract_utils_1 = __webpack_require__(/*! ../../diagram/node_extract_utils */ \"../saltcorn-data/dist/diagram/node_extract_utils.js\");\nconst { getState } = __webpack_require__(/*! ../../db/state */ \"../saltcorn-data/dist/db/state.js\");\nconst { get_expression_function, eval_expression, } = __webpack_require__(/*! ../../models/expression */ \"../saltcorn-data/dist/models/expression.js\");\nconst db = __webpack_require__(/*! ../../db/index */ \"../saltcorn-data/dist/db/index.js\");\n/**\n * @returns {Workflow}\n */\nconst configuration_workflow = (req) => new workflow_1.default({\n steps: [\n {\n name: \"Layout\",\n builder: async (context) => {\n const table = table_1.default.findOne(context.table_id || context.exttable_name);\n const fields = [...table.getFields()];\n const { child_field_list, child_relations } = await table.get_child_relations();\n const { parent_field_list } = await table.get_parent_relations(true);\n const my_parent_field_list = parent_field_list\n .map((pfield) => {\n const kpath = pfield.split(\".\");\n if (kpath.length === 2) {\n const [jFieldNm, lblField] = kpath;\n const jfld = fields.find((f) => f.name === jFieldNm);\n if (jfld)\n return `${jFieldNm}.${jfld.reftable_name}->${lblField}`;\n }\n if (kpath.length === 3) {\n const [jFieldNm, throughField, lblField] = kpath;\n const jfld = fields.find((f) => f.name === jFieldNm);\n if (!jfld)\n return;\n const throughTable = table_1.default.findOne({\n name: jfld.reftable_name,\n });\n const throughFld = throughTable.fields.find((f) => f.name === throughField);\n return `${jFieldNm}.${jfld.reftable_name}->${throughField}.${throughFld.reftable_name}->${lblField}`;\n }\n })\n .filter((f) => f);\n const roles = await user_1.default.get_roles();\n for (const cr of child_relations) {\n const cfields = await cr.table.getFields();\n cfields.forEach((cf) => {\n if (cf.name !== cr.key_field.name)\n fields.push(new field_1.default({\n ...cf,\n label: `${cr.table.name}.${cr.key_field.name}→${cf.name}`,\n name: `${cr.table.name}.${cr.key_field.name}.${cf.name}`,\n }));\n });\n }\n const stateActions = Object.entries(getState().actions).filter(([k, v]) => !v.disableInBuilder && !v.disableIf?.());\n const actions1 = [\"Clear\", ...stateActions.map(([k, v]) => k)];\n const actions = trigger_1.default.action_options({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n forBuilder: true,\n builtInLabel: \"Filter Actions\",\n builtIns: [\"Clear\"],\n });\n const triggerActions = trigger_1.default.trigger_actions({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n });\n const actionConstraints = {};\n const stateActionsObj = getState().actions;\n for (const action of actions1) {\n if (stateActionsObj[action]?.requireRow)\n actionConstraints[action] = { requireRow: true };\n }\n const actionConfigForms = {\n Clear: [\n {\n name: \"omit_fields\",\n label: \"Omit fields\",\n sublabel: \"Comma separated list of fields not to clear\",\n type: \"String\",\n },\n ],\n };\n const actionDescriptions = {};\n for (const [name, action] of stateActions) {\n if (action.configFields) {\n actionConfigForms[name] = await (0, plugin_helper_1.getActionConfigFields)(action, table, { mode: \"filter\", req });\n }\n if (action.description)\n actionDescriptions[name] = action.description;\n }\n const workflowActions = trigger_1.default.trigger_actions({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n onlyWorkflows: true,\n });\n for (const name of workflowActions) {\n actionConfigForms[name] = [\n {\n name: \"initial_context\",\n label: \"Additional context\",\n type: \"String\",\n class: \"validate-expression\",\n },\n ];\n }\n const own_link_views = await view_1.default.find_table_views_where(context.table_id || context.exttable_name, ({ viewrow }) => viewrow.name !== context.viewname);\n const own_view_names = new Set();\n const views = own_link_views.map((v) => {\n own_view_names.add(v.name);\n return {\n label: v.name,\n name: v.name,\n viewtemplate: v.viewtemplate,\n };\n });\n const all_views = await view_1.default.find({}, { cached: true });\n for (const v of all_views) {\n if (!own_view_names.has(v.name)) {\n views.push({\n label: v.name,\n name: v.name,\n viewtemplate: v.viewtemplate,\n });\n }\n }\n for (const field of fields) {\n const presets = field.presets;\n field.preset_options = presets ? Object.keys(presets) : [];\n }\n const library = (await Library.find({})).filter((l) => l.suitableFor(\"filter\"));\n //const fieldViewConfigForms = await calcfldViewConfig(fields, false);\n const { field_view_options, handlesTextStyle } = (0, plugin_helper_1.calcfldViewOptions)(fields, \"filter\");\n const pages = (await page_1.default.find({}, { cached: true })).map((p) => ({\n name: p.name,\n }));\n const groups = (await PageGroup.find({}, { cached: true })).map((g) => ({\n name: g.name,\n }));\n var agg_field_opts = {};\n agg_field_opts[table.name] = table.fields\n .filter((f) => !f.calculated || f.stored)\n .map((f) => ({\n name: f.name,\n label: f.label,\n ftype: f.type?.name || f.type,\n table_name: table.name,\n table_id: table.id,\n }));\n const agg_fieldview_options = {};\n Object.values(getState().types).forEach((t) => {\n agg_fieldview_options[t.name] = Object.entries(t.fieldviews)\n .filter(([k, v]) => !v.isEdit && !v.isFilter)\n .map(([k, v]) => k);\n });\n const has_select2 = Object.keys(getState().keyFieldviews).includes(\"select2\");\n const { on_done_redirect, ...current_filter_state } = req.query;\n return {\n fields: fields\n .filter((f) => !f.calculated || f.stored)\n .map((f) => f.toBuilder),\n tableName: table.name,\n parent_field_list: my_parent_field_list,\n child_field_list: [table.name],\n agg_field_opts,\n agg_fieldview_options,\n current_filter_state,\n roles,\n builtInActions: [\"Clear\"],\n actions,\n triggerActions,\n actionConstraints,\n views,\n pages,\n page_groups: groups,\n images: [], //temp fix till we rebuild builder\n library,\n field_view_options,\n actionConfigForms,\n actionDescriptions,\n //fieldViewConfigForms,\n mode: \"filter\",\n has_select2,\n has_copilot_generate: !!getState().functions.copilot_generate_layout,\n };\n },\n },\n ],\n});\n/** @returns {object[]} */\nconst get_state_fields = () => [];\n/**\n *\n * @returns {Promise<object>}\n */\nconst initial_config = async () => ({ layout: {}, columns: [] });\n/**\n * @param {number} table_id\n * @param {string} viewname\n * @param {object} opts\n * @param {object[]} opts.columns\n * @param {object} opts.layout\n * @param {object} state\n * @param {object} extra\n * @returns {Promise<Layout>}\n */\nconst run = async (table_id, viewname, { columns, layout }, state, extra, { distinctValuesQuery, optionsQuery }) => {\n //console.log(columns);\n //console.log(layout);\n if (!columns || !layout)\n return \"View not yet built\";\n const table = table_1.default.findOne(table_id);\n const fields = table.getFields();\n (0, plugin_helper_1.readState)(state, fields);\n const formFieldNames = (columns || [])\n .map((c) => c.field_name)\n .filter((n) => n);\n const { distinct_values, role } = await distinctValuesQuery(state);\n const badges = [];\n Object.entries(state).forEach(([k, v]) => {\n if (typeof v === \"undefined\")\n return;\n if (k[0] !== \"_\") {\n let showv = v?.toString?.() || v;\n if (distinct_values[k]) {\n const realv = distinct_values[k].find((dv) => dv.value === v);\n if (realv)\n showv = realv.label;\n }\n badges.push({\n text: `${(0, tags_1.text_attr)(k)}:${(0, tags_1.text_attr)(showv)}`,\n onclick: `unset_state_field('${(0, tags_1.text_attr)(k)}', this)`,\n });\n }\n });\n const evalCtx = { ...state };\n fields.forEach((f) => {\n //so it will be in scope in formula\n if (typeof evalCtx[f.name] === \"undefined\")\n evalCtx[f.name] = undefined;\n });\n evalCtx.session_id = getSessionId(extra.req);\n await traverse(layout, {\n aggregation: async (segment) => {\n const { stat, agg_field, agg_fieldview, aggwhere } = segment;\n const where = (0, plugin_helper_1.stateFieldsToWhere)({ fields, state, table, prefix: \"a.\" });\n if (aggwhere) {\n const ctx = {\n ...state,\n user_id: extra.req.user?.id || null,\n user: extra.req.user,\n };\n let where1 = jsexprToWhere(aggwhere, ctx, fields);\n mergeIntoWhere(where, where1 || {});\n }\n const { val } = await table.aggregationQuery({\n val: {\n field: agg_field,\n aggregate: stat,\n },\n }, {\n where,\n forPublic: !extra.req.user || extra.req.user.role_id === 100,\n forUser: extra.req.user,\n });\n const fld = table.getField(agg_field);\n segment.type = \"blank\";\n if (stat.toLowerCase() === \"array_agg\" && Array.isArray(val))\n segment.contents = val.map((v) => (0, tags_1.text)(v.toString())).join(\", \");\n else if (agg_fieldview) {\n const outcomeType = stat === \"Percent true\" || stat === \"Percent false\"\n ? \"Float\"\n : stat === \"Count\" || stat === \"CountUnique\"\n ? \"Integer\"\n : fld.type?.name;\n const type = getState().types[outcomeType];\n if (type?.fieldviews[agg_fieldview]) {\n const readval = type.read(val);\n segment.contents = type.fieldviews[agg_fieldview].run(readval, extra.req, segment?.configuration || {});\n }\n }\n else\n segment.contents = (0, tags_1.text)(val);\n },\n field: async (segment) => {\n const { field_name, fieldview, configuration } = segment;\n let field = fields.find((fld) => fld.name === field_name);\n if (!field) {\n if (field_name.includes(\".\")) {\n const kpath = field_name.split(\".\");\n if (kpath.length === 3) {\n const [jtNm, jFieldNm, lblField] = kpath;\n const jtable = table_1.default.findOne({ name: jtNm });\n if (!jtable)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find join table ${jtNm}`);\n const jfields = jtable.fields;\n field = jfields.find((f) => f.name === lblField);\n }\n }\n if (!field)\n return;\n }\n field.fieldview = fieldview;\n if (field.is_fkey && !field.fieldviewObj)\n field.fieldviewObj = getState().keyFieldviews[field.fieldview];\n Object.assign(field.attributes, configuration);\n await field.fill_fkey_options(false, undefined, extra.req.user\n ? { ...state, user_id: extra.req.user, user: extra.req.user }\n : state, isWeb(extra.req) ? undefined : optionsQuery, undefined, state[field.name] || undefined, extra.req.user || { role_id: 100 });\n segment.field = field;\n },\n link: (segment) => {\n //console.log(\"link:\", segment, state);\n if (segment.transfer_state) {\n segment.url += `?` + objectToQueryString(state || {});\n }\n if (segment.view_state_fml) {\n const extra_state = segment.view_state_fml\n ? eval_expression(segment.view_state_fml, evalCtx, extra.req.user, `Extra state formula for link`)\n : {};\n segment.url +=\n (segment.transfer_state ? \"\" : `?`) +\n objectToQueryString(extra_state || {});\n }\n },\n container(segment) {\n if (segment.showIfFormula) {\n const f = get_expression_function(segment.showIfFormula, fields);\n if (!f(state, extra.req.user)) {\n segment.contents = \"\";\n segment.type = \"blank\";\n delete segment.showIfFormula; //avoid double eval\n }\n }\n },\n tabs(segment) {\n const to_delete = new Set();\n (segment.showif || []).forEach((sif, ix) => {\n if (sif) {\n const showit = eval_expression(sif, evalCtx, extra.req.user, \"Tabs show if formula\");\n if (!showit)\n to_delete.add(ix);\n }\n });\n segment.titles = segment.titles.filter((v, ix) => !to_delete.has(ix));\n segment.contents = segment.contents.filter((v, ix) => !to_delete.has(ix));\n },\n async action(segment) {\n if (segment.action_style === \"on_page_load\") {\n segment.type = \"blank\";\n segment.style = {};\n if (segment.minRole && segment.minRole != 100) {\n const minRole = +segment.minRole;\n const userRole = extra?.req?.user?.role_id || 100;\n if (minRole < userRole)\n return;\n }\n if (extra?.isPreview)\n return;\n try {\n const actionResult = await (0, plugin_helper_1.run_action_column)({\n col: { ...segment },\n referrer: extra.req?.get?.(\"Referrer\"),\n req: extra.req,\n row: state,\n table,\n });\n if (actionResult?.set_fields) {\n Object.keys(actionResult.set_fields).forEach((k) => {\n if (actionResult.set_fields[k] === state[k])\n delete actionResult.set_fields[k];\n });\n }\n if (actionResult)\n segment.contents = (0, tags_1.script)((0, tags_1.domReady)(`common_done(${JSON.stringify(actionResult)}, \"${viewname}\")`));\n else\n segment.contents = \"\";\n }\n catch (e) {\n segment.contents = \"\";\n crash_1.default.create(e, extra.req);\n }\n }\n },\n blank: (segment) => {\n if (segment.isHTML &&\n typeof segment.contents === \"string\" &&\n segment.contents.includes(\"{{\")) {\n segment.contents = interpolate(segment.contents, evalCtx, extra.req?.user, \"Filter HTML element interpolation\");\n }\n },\n });\n await eachView(layout, async (segment, inLazy) => {\n const view = await view_1.default.findOne({ name: segment.view });\n if (!view)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find view ${segment.view}`);\n const extra_state = segment.extra_state_fml\n ? eval_expression(segment.extra_state_fml, evalCtx, extra.req.user, `Extra state formula for view ${view.name}`)\n : {};\n if (segment.state === \"local\") {\n const state1 = { ...extra_state };\n const qs = (0, plugin_helper_1.stateToQueryString)(state1, true);\n segment.contents = (0, tags_1.div)({\n class: \"d-inline\",\n \"data-sc-embed-viewname\": view.name,\n \"data-sc-local-state\": `/view/${view.name}${qs}`,\n \"data-sc-view-source\": `/view/${view.name}${qs}`,\n }, inLazy\n ? \"\"\n : view.renderLocally()\n ? await view.run(state1, extra)\n : await renderServerSide(view.name, state1));\n }\n else {\n const state1 = { ...state, ...extra_state };\n const qs = (0, plugin_helper_1.stateToQueryString)(state1, true);\n segment.contents = (0, tags_1.div)({\n class: \"d-inline\",\n \"data-sc-embed-viewname\": view.name,\n \"data-sc-view-source\": `/view/${view.name}${qs}`,\n }, inLazy\n ? \"\"\n : view.renderLocally()\n ? await view.run(state1, extra)\n : await renderServerSide(view.name, state1));\n }\n }, state);\n await page_1.default.renderEachEmbeddedPageInLayout(layout, state, extra);\n translateLayout(layout, extra.req.getLocale());\n const blockDispatch = {\n field(segment) {\n const { field_name, fieldview, configuration, field } = segment;\n if (!field)\n return \"\";\n //console.log({ fieldview, field });\n if (fieldview && field.type && field.type === \"Key\") {\n const fv = getState().keyFieldviews[fieldview];\n if (fv && (fv.isEdit || fv.isFilter)) {\n segment.options = distinct_values[field_name];\n return fv.run(field_name, state[field_name], {\n onChange: `set_state_field('${encodeURIComponent(field_name)}', this.value, this)`,\n ...field.attributes,\n isFilter: true,\n ...configuration,\n }, \"\", false, field, state);\n }\n }\n if (fieldview &&\n field.type &&\n field.type.fieldviews &&\n field.type.fieldviews[fieldview]) {\n const fv = field.type.fieldviews[fieldview];\n if (fv.isEdit || fv.isFilter)\n return fv.run(field_name, state[field_name], {\n onChange: `set_state_field('${encodeURIComponent(field_name)}', this.value, this)`,\n isFilter: true,\n ...field.attributes,\n ...configuration,\n }, \"\", false, field, state);\n }\n return \"\";\n },\n search_bar({ has_dropdown, contents, show_badges, autofocus }, go) {\n const rendered_contents = go(contents);\n const stVar = `_fts_${table.santized_name}`;\n return search_bar(stVar, state[stVar], {\n stateField: stVar,\n has_dropdown,\n autofocus,\n contents: rendered_contents,\n badges: show_badges ? badges : null,\n });\n },\n dropdown_filter(segment) {\n const { field_name, neutral_label, full_width, label_formula, apply_select2, } = segment;\n const dvs = distinct_values[field_name] || [];\n dvs.sort((a, b) => (a.label?.toLowerCase?.() || a.label) >\n (b.label?.toLowerCase?.() || b.label)\n ? 1\n : -1);\n const options = dvs.map(({ label, value, jsvalue }, ix) => (0, tags_1.option)({\n value,\n selected: `${state[field_name]}` === `${or_if_undef(jsvalue, value)}` ||\n (!value && !state[field_name]) ||\n (jsvalue === true && state[field_name] === \"on\") ||\n (jsvalue === false && state[field_name] === \"off\"),\n class: !value && !label ? \"text-muted\" : undefined,\n }, !value && !label && ix === 0 && neutral_label\n ? neutral_label\n : label_formula\n ? eval_expression(label_formula, { [field_name]: value }, extra.req.user || { role_id: 100 }, \"Dropdown label formula\")\n : label));\n return ((0, tags_1.select)({\n name: `ddfilter${field_name}`,\n class: \"form-control form-select d-inline-maybe scfilter selectizable\",\n style: full_width ? undefined : \"width: unset;\",\n required: true,\n onchange: `this.value=='' ? unset_state_field('${encodeURIComponent(field_name)}', this): set_state_field('${encodeURIComponent(field_name)}', this.value, this)`,\n }, options) +\n (apply_select2\n ? (0, tags_1.script)((0, tags_1.domReady)(`$('select[name=ddfilter${field_name}]').select2();`))\n : \"\"));\n },\n action(segment) {\n const { block, action_label, action_style, action_size, action_icon, action_name, action_row_variable, action_class, configuration, confirm, } = segment;\n let label = action_label || action_name;\n if (label === \" \")\n label = \"\";\n const confirmStr = confirm\n ? `if(confirm('${extra.req.__(\"Are you sure?\")}'))`\n : \"\";\n if (action_name === \"Clear\") {\n if (action_style === \"btn-link\")\n return (0, tags_1.a)({\n onclick: `${confirmStr}clear_state('${configuration?.omit_fields || \"\"}', this)`,\n class: [action_class],\n href: \"javascript:void(0)\",\n }, action_icon\n ? (0, tags_1.i)({ class: action_icon }) + (label ? \"&nbsp;\" : \"\")\n : false, label);\n else\n return (0, tags_1.button)({\n onClick: `${confirmStr}clear_state('${configuration?.omit_fields || \"\"}', this)`,\n class: [\n `btn ${action_style || \"btn-primary\"}`,\n action_size,\n action_class,\n ],\n }, action_icon\n ? (0, tags_1.i)({ class: action_icon }) + (label ? \"&nbsp;\" : \"\")\n : false, label);\n }\n else {\n const withState = action_row_variable === \"each_matching_row\" ||\n action_row_variable === \"state\";\n const url = {\n javascript: `${confirmStr}{${segment.spinner ? \"spin_action_link(this);\" : \"\"}view_post('${viewname}', 'run_action', {rndid:'${segment.rndid}'}, ` +\n `null, ${withState});}`,\n };\n return (0, viewable_fields_1.action_link)(url, extra.req, segment);\n }\n },\n toggle_filter({ field_name, value, preset_value, label, size, style, }) {\n const field = fields.find((f) => f.name === field_name);\n const isBool = field && field.type.name === \"Bool\";\n const use_value = preset_value && field?.presets\n ? field.presets[preset_value]({\n user: extra.req.user,\n req: extra.req,\n field,\n })\n : value;\n const active = isBool\n ? {\n on: state[field_name],\n off: state[field_name] === false,\n \"?\": state[field_name] === null,\n }[use_value]\n : eq_string(state[field_name], use_value);\n return (0, tags_1.button)({\n class: [\n \"btn\",\n active\n ? `btn-${style || \"primary\"}`\n : `btn-outline-${style || \"primary\"}`,\n size && size,\n ],\n onClick: active || use_value === undefined\n ? `unset_state_field('${encodeURIComponent(field_name)}', this)`\n : `set_state_field('${encodeURIComponent(field_name)}', '${use_value || \"\"}', this)`,\n }, label || value || preset_value);\n },\n };\n return (0, tags_1.div)({ class: \"form-namespace\" }, renderLayout({\n blockDispatch,\n layout,\n role,\n req: extra.req,\n hints: getState().getLayout(extra.req.user).hints || {},\n }));\n};\n/**\n * @param {object|undefined} x\n * @param {object|undefined} y\n * @returns {object}\n */\nconst or_if_undef = (x, y) => (typeof x === \"undefined\" ? y : x);\n/**\n * @param {string} x\n * @param {string} y\n * @returns {boolean}\n */\nconst eq_string = (x, y) => `${x}` === `${y}`;\nconst run_action = async (table_id, viewname, config, body, { req, res }, { actionQuery }) => {\n const table = table_1.default.findOne(table_id);\n if (!table)\n throw new InvalidConfiguration(`View '${viewname}:run_action' incorrectly configured: ` +\n `Unable to find table with id '${table_id}'`);\n const state = req?.query\n ? (0, plugin_helper_1.readState)(removeEmptyStrings(req.query), table.getFields(), req)\n : {};\n const result = await actionQuery(state, body?.rndid);\n if (result.json.error) {\n crash_1.default.create({ message: result.json.error, stack: \"\" }, req);\n }\n return result;\n};\n/**\n * combine multiple action results into one object\n * for 'reload_page, goto, popup' take the first\n * all other types are combined into arrays\n * @param results array of action results\n */\nconst combineResults = (results) => {\n const messageLimit = 5;\n const downloadLimit = 5;\n let numMsgs = 0, suppressedMsgs = 0, suppressedErrors = 0;\n let numDownloads = 0, suppressedDownloads = 0;\n const result = { json: { success: \"ok\" } };\n const initOrPush = (newElement, memberName) => {\n if (result.json[memberName])\n result.json[memberName].push(newElement);\n else\n result.json[memberName] = [newElement];\n };\n const initOnce = (newElement, memberName) => {\n if (typeof result[memberName] === \"undefined\")\n result.json[memberName] = newElement;\n };\n for (const r of results) {\n if (!r)\n continue;\n if (r.reload_page)\n initOnce(r.reload_page, \"reload_page\");\n if (r.goto)\n initOnce(r.goto, \"goto\");\n if (r.popup)\n initOnce(r.popup, \"popup\");\n if (r.notify) {\n if (numMsgs < messageLimit) {\n initOrPush(r.notify, \"notify\");\n ++numMsgs;\n }\n else\n ++suppressedMsgs;\n }\n if (r.error) {\n if (numMsgs < messageLimit) {\n initOrPush(r.error, \"error\");\n ++numMsgs;\n }\n else\n ++suppressedErrors;\n }\n if (r.download) {\n if (numDownloads < downloadLimit) {\n initOrPush(r.download, \"download\");\n ++numDownloads;\n }\n else\n suppressedDownloads++;\n }\n if (r.eval_js)\n initOrPush(r.eval_js, \"eval_js\");\n }\n let suppressedMsg = \"\";\n if (suppressedMsgs > 0)\n suppressedMsg = `${suppressedMsgs} messages`;\n if (suppressedErrors > 0)\n suppressedMsg += `${suppressedMsg ? \", \" : \"\"}${suppressedErrors} errors`;\n if (suppressedMsg)\n result.json.suppressed = `And '${suppressedMsg}' were not shown`;\n return result;\n};\nmodule.exports = {\n /** @type {string} */\n name: \"Filter\",\n /** @type {string} */\n description: \"Elements that limit the rows shown in other views on the same page. Filter views do not show any rows on their own.\",\n get_state_fields,\n configuration_workflow,\n run,\n initial_config,\n /**\n * @param {object} opts\n * @param {*} opts.layout\n * @returns {string[]}\n */\n getStringsForI18n({ layout }) {\n return getStringsForI18n(layout);\n },\n connectedObjects: async (configuration) => {\n return (0, node_extract_utils_1.extractFromLayout)(configuration.layout);\n },\n routes: { run_action },\n queries: ({ table_id, viewname, configuration: { columns }, req, res, exttable_name, }) => ({\n async optionsQuery(reftable_name, type, attributes, whereWithExisting, user) {\n return await field_1.default.select_options_query(reftable_name, type === \"File\" ? attributes.select_file_where : whereWithExisting, attributes, undefined, user);\n },\n async actionQuery(state, rndid) {\n const col = columns.find((c) => c.type === \"Action\" && c.rndid === rndid && rndid);\n const table = table_1.default.findOne(table_id);\n try {\n return await db.withTransaction(async () => {\n if (col.action_row_variable === \"each_matching_row\") {\n const fields = table.getFields();\n const { joinFields, aggregations } = (0, plugin_helper_1.picked_fields_to_query)(columns, fields, undefined, req, table);\n const where = (0, plugin_helper_1.stateFieldsToWhere)({\n fields,\n state,\n table,\n prefix: \"a.\",\n });\n const q = (0, plugin_helper_1.stateFieldsToQuery)({\n state,\n fields,\n prefix: \"a.\",\n noSortAndPaging: true,\n });\n if (col.action_row_limit)\n q.limit = col.action_row_limit;\n let rows = await table.getJoinedRows({\n where,\n joinFields,\n aggregations,\n ...q,\n forPublic: !req.user || req.user.role_id === 100,\n forUser: req.user,\n });\n const referrer = req?.get?.(\"Referrer\");\n return combineResults(await asyncMap(rows, async (row) => {\n return await (0, plugin_helper_1.run_action_column)({\n col,\n req,\n table,\n res,\n referrer,\n row,\n });\n }));\n }\n else {\n const row = col.action_row_variable === \"state\" ? { ...state } : null;\n const result = await (0, plugin_helper_1.run_action_column)({\n col,\n req,\n table,\n res,\n referrer: req?.get?.(\"Referrer\"),\n ...(row ? { row } : {}),\n });\n return { json: { success: \"ok\", ...(result || {}) } };\n }\n });\n }\n catch (e) {\n console.error(e);\n return { json: { error: e.message || e } };\n }\n },\n async distinctValuesQuery(state) {\n const table = table_1.default.findOne(table_id || exttable_name);\n const fields = table.getFields();\n let distinct_values = {};\n const role = req.user ? req.user.role_id : 100;\n for (const col of columns) {\n if (col.type === \"DropDownFilter\") {\n const field = fields.find((f) => f.name === col.field_name);\n if (table.external || table.provider_name) {\n distinct_values[col.field_name] = (await table.distinctValues(col.field_name, {}, {\n forPublic: !req.user,\n forUser: req.user,\n })).map((x) => ({ label: x, value: x }));\n }\n else if (field) {\n distinct_values[col.field_name] = await field.distinct_values(req, jsexprToWhere(col.where, {\n ...state,\n user_id: req.user ? req.user.id : undefined,\n user: req.user,\n }, fields), !col.all_options);\n }\n else if (col.field_name.split(\"->\").length === 3) {\n //`${jFieldNm}.${jfld.reftable_name}->${throughField}.${throughFld.reftable_name}->${lblField}`;\n const [jFieldNm, throughPart, finalPart] = col.field_name.split(\".\");\n const [thoughTblNm, throughField] = throughPart.split(\"->\");\n const [jtNm, lblField] = finalPart.split(\"->\");\n const target = table.getField(`${jFieldNm}.${throughField}.${lblField}`);\n if (target)\n distinct_values[col.field_name] = await target.distinct_values(req, jsexprToWhere(col.where), !col.all_options);\n }\n else if (col.field_name.includes(\"->\")) {\n const [jFieldNm, krest] = col.field_name.split(\".\");\n const [jtNm, lblField] = krest.split(\"->\");\n const jtable = table_1.default.findOne({ name: jtNm });\n if (!jtable)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find join table ${jtNm}`);\n const jfields = await jtable.getFields();\n const jfield = jfields.find((f) => f.name === lblField);\n if (jfield)\n distinct_values[col.field_name] = await jfield.distinct_values(req, jsexprToWhere(col.where), !col.all_options);\n }\n else if (col.field_name.includes(\".\")) {\n const kpath = col.field_name.split(\".\");\n if (kpath.length === 3) {\n const [jtNm, jFieldNm, lblField] = kpath;\n const jtable = table_1.default.findOne({ name: jtNm });\n if (!jtable)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find join table ${jtNm}`);\n const jfields = jtable.fields;\n const jfield = jfields.find((f) => f.name === lblField);\n if (jfield)\n distinct_values[col.field_name] = await jfield.distinct_values(req, jsexprToWhere(col.where), !col.all_options);\n }\n else if (kpath.length === 2) {\n const target = table.getField(col.field_name);\n if (target)\n distinct_values[col.field_name] = await target.distinct_values(req, jsexprToWhere(col.where), !col.all_options);\n }\n }\n const dvs = distinct_values[col.field_name];\n if (dvs && dvs[0]) {\n if (dvs[0].value !== \"\") {\n dvs.unshift({ label: \"\", value: \"\" });\n }\n }\n }\n }\n return { distinct_values, role };\n },\n }),\n};\n//# sourceMappingURL=filter.js.map\n\n//# sourceURL=webpack://saltcorn/../saltcorn-data/dist/base-plugin/viewtemplates/filter.js?\n}");
94
-
95
- /***/ },
96
-
97
- /***/ "../saltcorn-data/dist/base-plugin/viewtemplates/list.js"
98
- /*!***************************************************************!*\
99
- !*** ../saltcorn-data/dist/base-plugin/viewtemplates/list.js ***!
100
- \***************************************************************/
101
- (module, __unused_webpack_exports, __webpack_require__) {
102
-
103
- "use strict";
104
- eval("{\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\n/**\n * @category saltcorn-data\n * @module base-plugin/viewtemplates/list\n * @subcategory base-plugin\n */\nconst field_1 = __importDefault(__webpack_require__(/*! ../../models/field */ \"../saltcorn-data/dist/models/field.js\"));\nconst table_1 = __importDefault(__webpack_require__(/*! ../../models/table */ \"../saltcorn-data/dist/models/table.js\"));\nconst form_1 = __importDefault(__webpack_require__(/*! ../../models/form */ \"../saltcorn-data/dist/models/form.js\"));\nconst view_1 = __importDefault(__webpack_require__(/*! ../../models/view */ \"../saltcorn-data/dist/models/view.js\"));\nconst workflow_1 = __importDefault(__webpack_require__(/*! ../../models/workflow */ \"../saltcorn-data/dist/models/workflow.js\"));\nconst crash_1 = __importDefault(__webpack_require__(/*! ../../models/crash */ \"../saltcorn-data/dist/models/crash.js\"));\nconst trigger_1 = __importDefault(__webpack_require__(/*! ../../models/trigger */ \"../saltcorn-data/dist/models/trigger.js\"));\nconst page_1 = __importDefault(__webpack_require__(/*! ../../models/page */ \"../saltcorn-data/dist/models/page.js\"));\nconst user_1 = __importDefault(__webpack_require__(/*! ../../models/user */ \"../saltcorn-data/dist/models/user.js\"));\nconst file_1 = __importDefault(__webpack_require__(/*! ../../models/file */ \"../saltcorn-data/dist/models/file.js\"));\nconst FieldRepeat = __webpack_require__(/*! ../../models/fieldrepeat */ \"../saltcorn-data/dist/models/fieldrepeat.js\");\nconst PageGroup = __webpack_require__(/*! ../../models/page_group */ \"../saltcorn-data/dist/models/page_group.js\");\nconst Library = __webpack_require__(/*! ../../models/library */ \"../saltcorn-data/dist/models/library.js\");\nconst { Relation, RelationType } = __webpack_require__(/*! @saltcorn/common-code */ \"../common-code/dist/index.js\");\nconst { mkTable, h, post_btn, link } = __webpack_require__(/*! @saltcorn/markup */ \"../saltcorn-markup/dist/index.js\");\nconst { text, script, button, div, a, code, i, } = __webpack_require__(/*! @saltcorn/markup/tags */ \"../saltcorn-markup/dist/tags.js\");\nconst { eachView, traverse, getStringsForI18n, translateLayout, } = __webpack_require__(/*! ../../models/layout */ \"../saltcorn-data/dist/models/layout.js\");\nconst pluralize = __webpack_require__(/*! pluralize */ \"../../node_modules/pluralize/pluralize.js\");\nconst { removeEmptyStrings, removeDefaultColor, applyAsync, mergeIntoWhere, mergeConnectedObjects, hashState, dollarizeObject, getSessionId, } = __webpack_require__(/*! ../../utils */ \"../saltcorn-data/dist/utils.js\");\nconst plugin_helper_1 = __webpack_require__(/*! ../../plugin-helper */ \"../saltcorn-data/dist/plugin-helper.js\");\nconst { get_viewable_fields, parse_view_select, get_viewable_fields_from_layout, action_url, } = __webpack_require__(/*! ../../viewable_fields */ \"../saltcorn-data/dist/viewable_fields.js\");\nconst { getState } = __webpack_require__(/*! ../../db/state */ \"../saltcorn-data/dist/db/state.js\");\nconst { get_async_expression_function, jsexprToWhere, freeVariables, get_expression_function, eval_expression, } = __webpack_require__(/*! ../../models/expression */ \"../saltcorn-data/dist/models/expression.js\");\nconst db = __webpack_require__(/*! ../../db */ \"../saltcorn-data/dist/db/index.js\");\nconst { get_existing_views } = __webpack_require__(/*! ../../models/discovery */ \"../saltcorn-data/dist/models/discovery.js\");\nconst { InvalidConfiguration, isWeb } = __webpack_require__(/*! ../../utils */ \"../saltcorn-data/dist/utils.js\");\nconst { check_view_columns } = __webpack_require__(/*! ../../plugin-testing */ \"../saltcorn-data/dist/mobile-mocks/saltcorn/plugin-testing.js\");\nconst { extractFromColumns, extractViewToCreate, } = __webpack_require__(/*! ../../diagram/node_extract_utils */ \"../saltcorn-data/dist/diagram/node_extract_utils.js\");\nconst { validID } = __webpack_require__(/*! @saltcorn/markup/layout_utils */ \"../saltcorn-markup/dist/layout_utils.js\");\nconst create_db_view = async (context, req) => {\n const table = table_1.default.findOne({ id: context.table_id });\n const fields = table.getFields();\n const { joinFields, aggregations } = (0, plugin_helper_1.picked_fields_to_query)(context.columns, fields, undefined, req, table);\n const { sql } = await table.getJoinedQuery({\n where: {},\n joinFields,\n aggregations,\n });\n const schema = db.getTenantSchemaPrefix();\n // is there already a table with this name ? if yes add _sqlview\n const extable = table_1.default.findOne({ name: context.viewname });\n const sql_view_name = `${schema}\"${db.sqlsanitize(context.viewname)}${extable ? \"_sqlview\" : \"\"}\"`;\n await db.query(`drop view if exists ${sql_view_name};`);\n await db.query(`create or replace view ${sql_view_name} as ${sql};`);\n};\nconst on_delete = async (table_id, viewname, { default_state }) => {\n if (!db.isSQLite) {\n const sqlviews = (await get_existing_views()).map((v) => v.table_name);\n const vnm = db.sqlsanitize(viewname);\n const schema = db.getTenantSchemaPrefix();\n if (sqlviews.includes(vnm))\n await db.query(`drop view if exists ${schema}\"${vnm}\";`);\n if (sqlviews.includes(vnm + \"_sqlview\"))\n await db.query(`drop view if exists ${schema}\"${vnm + \"_sqlview\"}\";`);\n }\n};\nconst configuration_workflow = (req) => new workflow_1.default({\n onDone: async (ctx) => {\n if (ctx.default_state._create_db_view) {\n await create_db_view(ctx, req);\n }\n return ctx;\n },\n steps: [\n {\n name: req.__(\"Columns\"),\n builder: async (context) => {\n const table = table_1.default.findOne(context.table_id || context.exttable_name);\n const fields = table.getFields();\n const boolfields = fields.filter((f) => f.type && f.type.name === \"Bool\");\n const stateActions = Object.entries(getState().actions).filter(([k, v]) => !v.disableInBuilder && !v.disableIf?.());\n const builtInActions = [\n \"Delete\",\n \"GoBack\",\n ...boolfields.map((f) => `Toggle ${f.name}`),\n ];\n const triggerActions = trigger_1.default.trigger_actions({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n });\n const actions = trigger_1.default.action_options({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n forBuilder: true,\n builtInLabel: \"List Actions\",\n builtIns: builtInActions,\n });\n for (const field of fields) {\n if (field.type === \"Key\") {\n field.reftable = table_1.default.findOne({\n name: field.reftable_name,\n });\n if (field.reftable)\n await field.reftable.getFields();\n }\n }\n const actionConfigForms = {};\n const actionDescriptions = {};\n for (const [name, action] of stateActions) {\n if (action.configFields) {\n actionConfigForms[name] = await (0, plugin_helper_1.getActionConfigFields)(action, table, { mode: \"list\", req });\n }\n if (action.description)\n actionDescriptions[name] = action.description;\n }\n const workflowActions = trigger_1.default.trigger_actions({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n onlyWorkflows: true,\n });\n for (const name of workflowActions) {\n actionConfigForms[name] = [\n {\n name: \"initial_context\",\n label: \"Additional context\",\n type: \"String\",\n class: \"validate-expression\",\n },\n ];\n }\n //const fieldViewConfigForms = await calcfldViewConfig(fields, false);\n const { field_view_options, handlesTextStyle } = (0, plugin_helper_1.calcfldViewOptions)(fields, \"list\");\n if (table.name === \"users\") {\n fields.push(new field_1.default({\n name: \"verification_url\",\n label: \"Verification URL\",\n type: \"String\",\n }));\n field_view_options.verification_url = [\"as_text\", \"as_link\"];\n }\n const rel_field_view_options = await (0, plugin_helper_1.calcrelViewOptions)(table, \"list\");\n const roles = await user_1.default.get_roles();\n const { parent_field_list } = await table.get_parent_relations(true, true);\n const { child_field_list, child_relations } = await table.get_child_relations(true);\n var agg_field_opts = {};\n child_relations.forEach(({ table, key_field, through }) => {\n const aggKey = (through ? `${through.name}->` : \"\") +\n `${table.name}.${key_field.name}`;\n agg_field_opts[aggKey] = table.fields\n .filter((f) => !f.calculated || f.stored)\n .map((f) => ({\n name: f.name,\n label: f.label,\n ftype: f.type.name || f.type,\n table_name: table.name,\n table_id: table.id,\n }));\n });\n const agg_fieldview_options = {};\n Object.values(getState().types).forEach((t) => {\n agg_fieldview_options[t.name] = Object.entries(t.fieldviews)\n .filter(([k, v]) => !v.isEdit && !v.isFilter)\n .map(([k, v]) => k);\n });\n const pages = (await page_1.default.find({}, { cached: true })).map((p) => ({\n name: p.name,\n }));\n const groups = (await PageGroup.find({}, { cached: true })).map((g) => ({\n name: g.name,\n }));\n const images = await file_1.default.find({ mime_super: \"image\" });\n const library = (await Library.find({})).filter((l) => l.suitableFor(\"list\"));\n const myviewrow = view_1.default.findOne({ name: context.viewname });\n // generate layout for legacy views\n if (!context.layout?.list_columns) {\n const newCols = [];\n const actionDropdown = [];\n const typeMap = {\n Field: \"field\",\n JoinField: \"join_field\",\n ViewLink: \"view_link\",\n Link: \"link\",\n Action: \"action\",\n Text: \"blank\",\n DropdownMenu: \"dropdown_menu\",\n Aggregation: \"aggregation\",\n };\n (context.columns || []).forEach((col) => {\n const newCol = {\n alignment: col.alignment || \"Default\",\n col_width: col.col_width || \"\",\n showif: col.showif || \"\",\n header_label: col.header_label || \"\",\n col_width_units: col.col_width_units || \"px\",\n contents: {\n ...col,\n configuration: { ...col },\n type: typeMap[col.type],\n },\n };\n delete newCol.contents._columndef;\n delete newCol.contents.configuration._columndef;\n delete newCol.contents.configuration.type;\n switch (col.type) {\n case \"Action\":\n newCol.contents.isFormula = {\n action_label: !!col.action_label_formula,\n };\n break;\n case \"ViewLink\":\n newCol.contents.isFormula = {\n label: !!col.view_label_formula,\n };\n break;\n case \"Link\":\n newCol.contents.isFormula = {\n url: !!col.link_url_formula,\n text: !!col.link_text_formula,\n };\n newCol.contents.text = col.link_text;\n newCol.contents.url = col.link_url;\n break;\n }\n if (col.in_dropdown)\n actionDropdown.push({ ...col, type: typeMap[col.type] });\n else\n newCols.push(newCol);\n });\n if (actionDropdown.length) {\n newCols.push({\n contents: {\n type: \"dropdown_menu\",\n label: \"Action\",\n action_size: \"btn-xs\",\n action_style: \"btn-outline-secondary\",\n contents: { above: actionDropdown },\n },\n });\n }\n context.layout = {\n besides: newCols,\n list_columns: true,\n };\n }\n const { on_done_redirect, ...current_filter_state } = req.query;\n return {\n tableName: table.name,\n fields: fields.map((f) => f.toBuilder),\n images,\n actions,\n triggerActions,\n builtInActions,\n actionConfigForms,\n actionDescriptions,\n current_filter_state,\n //fieldViewConfigForms,\n field_view_options: {\n ...field_view_options,\n ...rel_field_view_options,\n },\n parent_field_list,\n child_field_list,\n agg_field_opts,\n agg_fieldview_options,\n min_role: (myviewrow || {}).min_role,\n roles,\n library,\n pages,\n allowMultipleElementsPerColumn: true,\n page_groups: groups,\n allowMultiStepAction: true,\n handlesTextStyle,\n mode: \"list\",\n has_copilot_generate: !!getState().functions.copilot_generate_layout,\n ownership: !!table.ownership_field_id ||\n !!table.ownership_formula ||\n table.name === \"users\",\n };\n },\n },\n {\n name: req.__(\"Create new row\"),\n onlyWhen: async (context) => {\n const create_views = await view_1.default.find_table_views_where(context.table_id || context.exttable_name, ({ state_fields, viewrow }) => viewrow.name !== context.viewname &&\n state_fields.every((sf) => !sf.required));\n return create_views.length > 0;\n },\n form: async (context) => {\n const table = table_1.default.findOne(context.table_id\n ? { id: context.table_id }\n : { name: context.exttable_name });\n const create_views = await view_1.default.find_table_views_where(context.table_id || context.exttable_name, ({ state_fields, viewrow }) => viewrow.name !== context.viewname &&\n state_fields.every((sf) => !sf.required));\n const create_view_opts = create_views.map((v) => v.select_option);\n return new form_1.default({\n blurb: req.__(\"Specify how to create a new row\"),\n fields: [\n {\n name: \"view_to_create\",\n label: req.__(\"Use view to create\"),\n sublabel: req.__(\"If user has write permission. Leave blank to have no link to create a new item\") +\n \". \" +\n a({\n \"data-dyn-href\": `\\`/viewedit/config/\\${view_to_create}\\``,\n \"data-show-if\": \"showIfFormulaInputs($('select[name=view_to_create]'), 'view_to_create')\",\n target: \"_blank\",\n }, req.__(\"Configure\")),\n type: \"String\",\n attributes: {\n options: create_view_opts,\n },\n },\n {\n name: \"create_view_display\",\n label: req.__(\"Display create view as\"),\n type: \"String\",\n required: true,\n attributes: {\n options: \"Link,Embedded,Popup\",\n },\n showIf: {\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n {\n name: \"create_view_showif\",\n label: req.__(\"Show if formula\"),\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n table: table.name,\n user: true,\n expression_type: \"boolean\",\n },\n sublabel: req.__(\"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>.\"),\n showIf: {\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n {\n name: \"create_view_label\",\n label: req.__(\"Label for create\"),\n sublabel: req.__(\"Label in link or button to create. Leave blank for a default label\"),\n attributes: { asideNext: true },\n type: \"String\",\n showIf: {\n create_view_display: [\"Link\", \"Popup\"],\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n {\n name: \"create_view_location\",\n label: req.__(\"Location\"),\n sublabel: req.__(\"Location of link to create new row\"),\n required: true,\n attributes: {\n options: [\n \"Bottom left\",\n \"Bottom right\",\n \"Top left\",\n \"Top right\",\n ],\n },\n type: \"String\",\n showIf: {\n create_view_display: [\"Link\", \"Popup\"],\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n {\n name: \"create_view_location\",\n label: req.__(\"Location\"),\n sublabel: req.__(\"Location of view to create new row\"),\n required: true,\n attributes: {\n options: [\"Bottom\", \"Top\"],\n },\n type: \"String\",\n showIf: {\n create_view_display: [\"Embedded\"],\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n {\n name: \"create_link_style\",\n label: req.__(\"Link Style\"),\n type: \"String\",\n required: true,\n attributes: {\n asideNext: true,\n options: [\n { name: \"\", label: \"Link\" },\n { name: \"btn btn-primary\", label: \"Primary button\" },\n { name: \"btn btn-secondary\", label: \"Secondary button\" },\n { name: \"btn btn-success\", label: \"Success button\" },\n { name: \"btn btn-danger\", label: \"Danger button\" },\n {\n name: \"btn btn-outline-primary\",\n label: \"Primary outline button\",\n },\n {\n name: \"btn btn-outline-secondary\",\n label: \"Secondary outline button\",\n },\n ],\n },\n showIf: {\n create_view_display: [\"Link\", \"Popup\"],\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n {\n name: \"create_link_size\",\n label: req.__(\"Link size\"),\n type: \"String\",\n required: true,\n attributes: {\n options: [\n { name: \"\", label: \"Standard\" },\n { name: \"btn-lg\", label: \"Large\" },\n { name: \"btn-sm\", label: \"Small\" },\n { name: \"btn-sm btn-xs\", label: \"X-Small\" },\n { name: \"btn-block\", label: \"Block\" },\n { name: \"btn-block btn-lg\", label: \"Large block\" },\n ],\n },\n showIf: {\n create_view_display: [\"Link\", \"Popup\"],\n view_to_create: create_view_opts.map((o) => o.name),\n },\n },\n ],\n });\n },\n },\n {\n name: req.__(\"Default state\"),\n contextField: \"default_state\",\n form: async (context) => {\n const table = table_1.default.findOne(context.table_id || context.exttable_name);\n const table_fields = table\n .getFields()\n .filter((f) => !f.calculated || f.stored);\n const formfields = table_fields.map((f) => {\n return {\n name: f.name,\n label: f.label,\n type: f.type,\n reftable_name: f.reftable_name,\n attributes: f.attributes,\n fieldview: f.type && f.type.name === \"Bool\" ? \"tristate\" : undefined,\n required: false,\n };\n });\n const form = new form_1.default({\n fields: formfields,\n blurb: req.__(\"Default search form values when first loaded\"),\n });\n await form.fill_fkey_options(true);\n form.fields.forEach((ff) => {\n if (ff.reftable_name === \"users\" && ff.options) {\n // key to user\n ff.options.push({\n label: \"LoggedIn\",\n value: \"Preset:LoggedIn\",\n });\n }\n });\n return form;\n },\n },\n {\n name: req.__(\"Options\"),\n contextField: \"default_state\", //legacy...\n form: async (context) => {\n const table = table_1.default.findOne(context.table_id || context.exttable_name);\n const triggerActions = trigger_1.default.trigger_actions({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n });\n if (context.default_state?._row_click_url_formula &&\n !context.default_state?._row_click_type) {\n //legacy\n context.default_state._row_click_type = \"Link\";\n }\n const table_fields = table\n .getFields()\n .filter((f) => !f.calculated || f.stored);\n const formfields = [];\n const { child_field_list, child_relations } = await table.get_child_relations();\n const joinFields = context.columns?.filter((jf) => jf.type === \"JoinField\");\n const tree_options = table_fields\n .filter((f) => f.reftable_name === table.name)\n .map((f) => f.name);\n formfields.push({\n name: \"_order_field\",\n label: req.__(\"Default order by\"),\n type: \"String\",\n default: table.pk_name,\n attributes: {\n asideNext: true,\n options: [\n ...table_fields.map((f) => f.name),\n ...(joinFields\n ? joinFields.map((jf) => jf.join_field)\n : []),\n ],\n },\n });\n formfields.push({\n name: \"_descending\",\n label: req.__(\"Descending?\"),\n type: \"Bool\",\n required: true,\n });\n formfields.push({\n name: \"_group_by\",\n label: req.__(\"Group by\"),\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n table: table.name,\n user: true,\n expression_type: \"value\",\n },\n help: { topic: \"Group by formula\" },\n sublabel: \"Formula for the group headings\",\n class: \"validate-expression\",\n });\n if (!db.isSQLite)\n formfields.push({\n name: \"_tree_field\",\n label: req.__(\"Tree field\"),\n sublabel: req.__(\"A field that is Key to own table\"),\n type: \"String\",\n attributes: {\n options: tree_options,\n },\n });\n formfields.push({\n name: \"include_fml\",\n label: req.__(\"Row inclusion formula\"),\n class: \"validate-expression\",\n sublabel: req.__(\"Only include rows where this formula is true. \") +\n req.__(\"In scope:\") +\n \" \" +\n [\n ...table.fields.map((f) => f.name),\n \"user\",\n \"year\",\n \"month\",\n \"day\",\n \"today()\",\n ]\n .map((s) => code(s))\n .join(\", \"),\n input_type: \"code\",\n attributes: {\n mode: \"application/javascript\",\n singleline: true,\n table: table.name,\n user: true,\n expression_type: \"boolean\",\n },\n help: {\n topic: \"Inclusion Formula\",\n context: { table_name: table.name },\n },\n });\n formfields.push({\n name: \"exclusion_relation\",\n label: req.__(\"Exclusion relations\"),\n sublabel: req.__(\"Do not include row if this relation has a match\"),\n type: \"String\",\n required: false,\n attributes: { options: child_field_list },\n });\n formfields.push({\n name: \"exclusion_where\",\n label: req.__(\"Exclusion where\"),\n class: \"validate-expression\",\n type: \"String\",\n showIf: { exclusion_relation: child_field_list },\n });\n formfields.push({\n name: \"_rows_per_page\",\n label: req.__(\"Rows per page\"),\n type: \"Integer\",\n default: 20,\n attributes: { min: 0 },\n });\n formfields.push({\n name: \"_hide_pagination\",\n label: req.__(\"Hide pagination\"),\n type: \"Bool\",\n });\n formfields.push({\n name: \"_full_page_count\",\n label: req.__(\"Full page count\"),\n type: \"Bool\",\n sublabel: req.__(\"Disable for to increase performance for large tables\"),\n default: true,\n });\n formfields.push({\n name: \"_row_click_type\",\n label: req.__(\"Row click event\"),\n sublabel: req.__(\"What happens when a row is clicked\"),\n type: \"String\",\n required: true,\n attributes: {\n options: [\n \"Nothing\",\n \"Link\",\n \"Link new tab\",\n \"Popup\",\n \"Anchor link\",\n \"Action\",\n ],\n },\n });\n formfields.push({\n name: \"_row_click_action\",\n label: req.__(\"Row click action\"),\n sublabel: req.__(\"Run this action when row is clicked\"),\n type: \"String\",\n required: true,\n attributes: { options: triggerActions },\n showIf: { _row_click_type: \"Action\" },\n });\n formfields.push({\n name: \"_row_click_url_formula\",\n label: req.__(\"Row click URL\"),\n sublabel: req.__(\"Formula. Navigate to this URL when row is clicked\") +\n \". \" +\n req.__(\"Example: <code>`/view/TheOtherView?id=${id}`</code>\"),\n type: \"String\",\n class: \"validate-expression\",\n showIf: {\n _row_click_type: [\"Link\", \"Link new tab\", \"Popup\", \"Anchor link\"],\n },\n });\n formfields.push({\n name: \"_header_filters\",\n label: req.__(\"Header filters\"),\n type: \"Bool\",\n });\n formfields.push({\n name: \"_header_filters_toggle\",\n label: req.__(\"Toggle header filters\"),\n type: \"Bool\",\n showIf: { _header_filters: true, _header_filters_dropdown: false },\n });\n formfields.push({\n name: \"_header_filters_dropdown\",\n label: req.__(\"Dropdown header filters\"),\n type: \"Bool\",\n showIf: { _header_filters: true, _header_filters_toggle: false },\n });\n formfields.push({\n name: \"transpose\",\n label: req.__(\"Transpose\"),\n sublabel: req.__(\"Display one column per line\"),\n type: \"Bool\",\n tab: \"Layout options\",\n });\n formfields.push({\n name: \"transpose_width\",\n label: req.__(\"Vertical column width\"),\n type: \"Integer\",\n showIf: { transpose: true },\n tab: \"Layout options\",\n });\n formfields.push({\n name: \"transpose_width_units\",\n label: req.__(\"Vertical width units\"),\n type: \"String\",\n fieldview: \"radio_group\",\n attributes: {\n inline: true,\n options: [\"px\", \"%\", \"vw\", \"em\", \"rem\", \"cm\"],\n },\n tab: \"Layout options\",\n showIf: { transpose: true },\n });\n formfields.push({\n name: \"_omit_header\",\n label: req.__(\"Omit header\"),\n sublabel: req.__(\"Do not display the header\"),\n type: \"Bool\",\n tab: \"Layout options\",\n });\n formfields.push({\n name: \"hide_null_columns\",\n label: req.__(\"Hide null columns\"),\n sublabel: req.__(\"Do not display a column if it contains entirely missing values\"),\n type: \"Bool\",\n tab: \"Layout options\",\n });\n formfields.push({\n name: \"_hover_rows\",\n label: req.__(\"Hoverable rows\"),\n type: \"Bool\",\n sublabel: req.__(\"Highlight row under cursor\"),\n tab: \"Layout options\",\n });\n formfields.push({\n name: \"_striped_rows\",\n label: req.__(\"Striped rows\"),\n type: \"Bool\",\n sublabel: req.__(\"Add zebra stripes to rows\"),\n tab: \"Layout options\",\n });\n formfields.push({\n name: \"_card_rows\",\n label: req.__(\"Card rows\"),\n type: \"Bool\",\n sublabel: req.__(\"Each row in a card. Not supported by all themes\"),\n tab: \"Layout options\",\n });\n formfields.push({\n name: \"_borderless\",\n label: req.__(\"Remove border\"),\n type: \"Bool\",\n sublabel: req.__(\"No lines between tables\"),\n tab: \"Layout options\",\n });\n formfields.push({\n name: \"_cell_valign\",\n label: req.__(\"Vertical alignment\"),\n type: \"String\",\n required: true,\n attributes: {\n options: \"Middle,Top,Bottom\",\n },\n tab: \"Layout options\",\n });\n formfields.push({\n name: \"_responsive_collapse\",\n label: req.__(\"Responsve collapse\"),\n type: \"Bool\",\n sublabel: req.__(\"Horizontal display on smaller screens\"),\n tab: \"Layout options\",\n });\n formfields.push({\n name: \"_collapse_breakpoint_px\",\n label: req.__(\"Collapse breakpoint (px)\"),\n type: \"Integer\",\n tab: \"Layout options\",\n default: 760,\n showIf: { _responsive_collapse: true },\n });\n formfields.push({\n name: \"_table_layout\",\n label: req.__(\"Table layout\"),\n input_type: \"select\",\n options: [\"Auto\", \"Fixed\"],\n tab: \"Layout options\",\n });\n formfields.push({\n name: \"_sticky_header\",\n label: req.__(\"Sticky headers\"),\n type: \"Bool\",\n tab: \"Layout options\",\n });\n formfields.push({\n name: \"_row_color_formula\",\n label: req.__(\"Row color formula\"),\n sublabel: req.__(\"Formula for row background color. Ex.: <code>age>65 ?'#aaffaa': null</code>\"),\n type: \"String\",\n tab: \"Functionality\",\n class: \"validate-expression\",\n });\n if (!db.isSQLite && !table.external)\n formfields.push({\n name: \"_create_db_view\",\n label: req.__(\"Create database view\"),\n sublabel: req.__(\"Create an SQL view in the database with the fields in this list\"),\n help: {\n topic: \"Create SQL view\",\n },\n type: \"Bool\",\n tab: \"Database options\",\n });\n const form = new form_1.default({\n fields: formfields,\n tabs: { tabsStyle: \"Accordion\" },\n });\n await form.fill_fkey_options(true);\n return form;\n },\n },\n ],\n});\nconst get_state_fields = async (table_id, viewname, { columns }) => {\n const table = table_1.default.findOne(table_id);\n if (!table)\n return [];\n const table_fields = table.fields;\n let state_fields = [];\n state_fields.push({ name: \"_fts\", label: \"Anywhere\", input_type: \"text\" });\n (columns || []).forEach((column) => {\n if (column.type === \"Field\") {\n const tbl_fld = table_fields.find((f) => f.name == column.field_name);\n if (tbl_fld && !tbl_fld.primary_key) {\n const f = new field_1.default(tbl_fld);\n f.required = false;\n if (column.header_label)\n f.label = column.header_label;\n state_fields.push(f);\n }\n }\n });\n return state_fields;\n};\nconst set_join_fieldviews = async ({ table, columns, fields }) => {\n //legacy\n for (const segment of columns) {\n const { join_field, join_fieldview } = segment;\n if (!join_fieldview)\n continue;\n const field = table.getField(join_field);\n if (field && field.type === \"File\")\n segment.field_type = \"File\";\n else if (field?.type.name && field?.type?.fieldviews[join_fieldview])\n segment.field_type = field.type.name;\n }\n};\nconst initial_config = async ({ table_id, exttable_name }) => {\n const table = table_1.default.findOne(table_id ? { id: table_id } : { name: exttable_name });\n const fields = table\n .getFields()\n .filter((f) => !f.primary_key || f?.attributes?.NonSerial);\n const columns = [];\n const layoutCols = [];\n fields.forEach((f) => {\n if (!f.type)\n return;\n if (f.type === \"File\") {\n const col = {\n type: \"field\",\n fieldview: \"Link\",\n field_name: f.name,\n };\n columns.push({ ...col, type: \"Field\" });\n layoutCols.push({ contents: col, header_label: f.label });\n }\n else if (f.is_fkey) {\n const col = {\n type: \"join_field\",\n fieldview: \"as_text\",\n join_field: `${f.name}.${f.attributes?.summary_field || \"id\"}`,\n };\n columns.push({ ...col, type: \"JoinField\" });\n layoutCols.push({ contents: col, header_label: f.label });\n }\n else {\n const fieldview = f.type?.fieldviews?.show\n ? \"show\"\n : f.type?.fieldviews?.as_text\n ? \"as_text\"\n : undefined;\n const col = {\n type: \"field\",\n fieldview,\n field_name: f.name,\n };\n columns.push({ ...col, type: \"Field\" });\n layoutCols.push({ contents: col, header_label: f.label });\n }\n });\n return { columns, layout: { list_columns: true, besides: layoutCols } };\n};\nconst run = async (table_id, viewname, { columns, layout, view_to_create, create_view_display, create_view_label, default_state, create_view_location, create_link_style, create_link_size, create_view_showif, }, stateWithId, extraOpts, { listQuery }) => {\n const table = table_1.default.findOne(typeof table_id === \"string\" ? { name: table_id } : { id: table_id });\n const pk_name = table.pk_name;\n const fields = table.getFields();\n const appState = getState();\n const locale = extraOpts.req.getLocale();\n const __ = (s) => isWeb(extraOpts.req) ? appState.i18n.__({ phrase: s, locale }) || s : s;\n //move fieldview cfg into configuration subfield in each column\n for (const col of columns) {\n if (col.type === \"Field\") {\n const field = fields.find((f) => f.name === col.field_name);\n if (!field)\n continue;\n const fieldviews = field.type === \"Key\"\n ? appState.keyFieldviews\n : field.type?.fieldviews || {};\n if (!fieldviews)\n continue;\n const fv = fieldviews[col.fieldview];\n if (fv && fv.configFields) {\n const cfgForm = await applyAsync(fv.configFields, field);\n col.configuration = {};\n for (const formField of cfgForm || []) {\n col.configuration[formField.name] = col[formField.name];\n }\n }\n }\n }\n const role = extraOpts && extraOpts.req && extraOpts.req.user\n ? extraOpts.req.user.role_id\n : 100;\n await set_join_fieldviews({ table, columns, fields });\n (0, plugin_helper_1.readState)(stateWithId, fields, extraOpts.req);\n const id = stateWithId[pk_name];\n let state = { ...stateWithId };\n if (extraOpts?.removeIdFromstate)\n delete state[pk_name];\n const statehash = hashState(state, viewname);\n const { rows, rowCount } = await listQuery(state, statehash, extraOpts.isPreview);\n const viewResults = {};\n var views = {};\n const getView = async (name, relation) => {\n if (views[name])\n return views[name];\n const view_select = parse_view_select(name, relation);\n const view = view_1.default.findOne({ name: view_select.viewname });\n if (!view)\n return false;\n if (view.table_id === table.id)\n view.table = table;\n else\n view.table = table_1.default.findOne({ id: view.table_id });\n view.view_select = view_select;\n views[name] = view;\n return view;\n };\n await eachView(layout, async (segment) => {\n const view = await getView(segment.view, segment.relation);\n if (!view)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find view ${segment.view}`);\n view.check_viewtemplate();\n let stateMany, getRowState;\n const get_extra_state = (row) => segment.extra_state_fml\n ? eval_expression(segment.extra_state_fml, {\n ...dollarizeObject(state),\n session_id: getSessionId(extraOpts.req),\n ...row,\n }, extraOpts.req.user, `Extra state formula for view ${view.name}`)\n : {};\n if (view.view_select.type === \"RelationPath\") {\n const relation = new Relation(segment.relation, view.table_id ? table_1.default.findOne({ id: view.table_id }).name : undefined, (0, plugin_helper_1.displayType)(await view.get_state_fields()));\n switch (relation.type) {\n case RelationType.OWN:\n if (segment.extra_state_fml)\n stateMany = {\n or: rows.map((row) => ({\n [pk_name]: row[pk_name],\n ...get_extra_state(row),\n })),\n };\n else\n stateMany = {\n [pk_name]: { in: rows.map((row) => row[pk_name]) },\n };\n getRowState = (row) => ({\n [pk_name]: row[pk_name],\n ...get_extra_state(row),\n });\n break;\n case RelationType.CHILD_LIST:\n stateMany = {\n or: rows.map((row) => ({\n [relation.path[0].inboundKey]: row[table.pk_name],\n ...get_extra_state(row),\n })),\n };\n getRowState = (row) => ({\n [relation.path[0].inboundKey]: row[table.pk_name],\n ...get_extra_state(row),\n });\n break;\n case RelationType.PARENT_SHOW:\n const refTable = table_1.default.findOne({ id: view.table_id });\n stateMany = {\n or: rows.map((row) => ({\n [refTable.pk_name]: row[relation.targetTblName],\n ...get_extra_state(row),\n })),\n };\n getRowState = (row) => ({\n [refTable.pk_name]: row[relation.targetTblName],\n ...get_extra_state(row),\n });\n break;\n case RelationType.INDEPENDENT:\n case RelationType.NONE:\n stateMany = segment.extra_state_fml\n ? {\n or: rows.map((row) => get_extra_state(row)),\n }\n : {};\n getRowState = (row) => get_extra_state(row);\n break;\n default:\n throw new Error(`View in List: invalid relation type ${relation.type}`);\n }\n }\n //todo:\n // other rel types\n if (view.viewtemplateObj?.runMany) {\n const runs = await view.runMany(stateMany, extraOpts);\n viewResults[segment.view + segment.relation] = (row) => runs.find((rh) => rh.row[pk_name] == row[pk_name])?.html;\n }\n else if (view.viewtemplateObj?.renderRows) {\n const rendered = await view.viewtemplateObj.renderRows(view.table, view.name, view.configuration, extraOpts, rows, state);\n viewResults[segment.view + segment.relation] = (row) => rendered\n .map((html, ix) => ({\n html,\n row: rows[ix],\n }))\n .find((rh) => rh.row[pk_name] == row[pk_name])?.html;\n }\n else {\n const results = [];\n for (const row of rows) {\n const rowState = getRowState(row);\n const qs = (0, plugin_helper_1.stateToQueryString)(rowState, true);\n const rendered = div({\n class: \"d-inline\",\n \"data-sc-embed-viewname\": view.name,\n \"data-sc-local-state\": `/view/${view.name}${qs}`,\n }, await view.run(rowState, extraOpts));\n results.push({\n html: rendered,\n row,\n });\n }\n viewResults[segment.view + segment.relation] = (row) => results.find((rh) => rh.row[pk_name] == row[pk_name])?.html;\n }\n });\n const is_row_click = (default_state?._row_click_url_formula ||\n default_state?._row_click_action) &&\n default_state?._row_click_type !== \"Nothing\";\n const use_layout = (layout?.besides || []).filter((col) => {\n if (col?.showif) {\n const fvs = freeVariables(col.showif);\n const used_fields = [...fvs].map((fv) => fv.split(/(\\?\\.|\\.|Ⱶ)/)[0]);\n if (used_fields.some((fnm) => fnm === \"row\" || fields.map((f) => f.name).includes(fnm)))\n return true;\n //save to eval\n return eval_expression(col.showif, {}, extraOpts.req?.user, \"List column showif expression\");\n }\n else\n return true;\n });\n const tfields = layout?.list_columns\n ? get_viewable_fields_from_layout(viewname, statehash, table, fields, columns, false, extraOpts.req, __, state, viewname, use_layout, viewResults, is_row_click, !!default_state?._tree_field)\n : get_viewable_fields(viewname, statehash, table, fields, columns, false, extraOpts.req, __, state, viewname, is_row_click, !!default_state?._tree_field);\n let rows_per_page = (default_state && default_state._rows_per_page) || 20;\n rows_per_page = extraOpts.isPreview\n ? Math.min(50, rows_per_page)\n : rows_per_page;\n const current_page = parseInt(state[`_${statehash}_page`]) || 1;\n var page_opts = extraOpts && extraOpts.onRowSelect\n ? { onRowSelect: extraOpts.onRowSelect, selectedId: id }\n : { selectedId: id };\n page_opts.pk_name = table.pk_name;\n if (default_state?._row_click_url_formula &&\n default_state?._row_click_type !== \"Nothing\" &&\n default_state?._row_click_type !== \"Action\") {\n let fUrl = get_expression_function(default_state._row_click_url_formula, fields);\n if (default_state?._row_click_type === \"Link new tab\")\n page_opts.onRowSelect = (row) => `window.open('${fUrl(row, extraOpts.req.user)}', '_blank').focus();`;\n else if (default_state?._row_click_type === \"Popup\")\n page_opts.onRowSelect = (row) => `ajax_modal('${fUrl(row, extraOpts.req.user)}')`;\n else if (default_state?._row_click_type === \"Anchor link\") {\n page_opts.onRowSelect = (row) => fUrl(row, extraOpts.req.user);\n page_opts.rowAnchorLink = true;\n }\n else\n page_opts.onRowSelect = (row) => `location.href='${fUrl(row, extraOpts.req.user)}'`;\n }\n else if (default_state?._row_click_type === \"Action\") {\n page_opts.onRowSelect = (row) => {\n const actionUrl = action_url(viewname, table, default_state?._row_click_action, row, default_state?._row_click_action, \"action_name\");\n if (actionUrl.javascript)\n return actionUrl.javascript;\n };\n }\n page_opts.class = \"\";\n if ((!default_state?._hide_pagination &&\n rows &&\n rows.length === rows_per_page) ||\n current_page > 1) {\n const nrows = rowCount;\n if (nrows > rows_per_page || current_page > 1) {\n page_opts.pagination = {\n current_page,\n pages: Math.ceil(nrows / rows_per_page),\n noMaxPage: default_state?._full_page_count === false,\n get_page_link: (n) => `gopage(${n}, ${rows_per_page}, '${statehash}', {}, this)`,\n };\n }\n }\n if (default_state?._omit_header) {\n page_opts.noHeader = true;\n }\n if (default_state?._hover_rows) {\n page_opts.class += \"table-hover \";\n }\n if (default_state?._striped_rows) {\n page_opts.class += \"table-striped \";\n }\n if (default_state?._card_rows) {\n page_opts.class += \"table-card-rows \";\n }\n if (default_state?._borderless) {\n page_opts.class += \"table-borderless \";\n }\n if (default_state?._table_layout) {\n page_opts.table_layout = default_state?._table_layout;\n }\n if (default_state?._sticky_header) {\n page_opts.sticky_header = default_state?._sticky_header;\n }\n if (default_state?._tree_field) {\n page_opts.level_indicator = true;\n }\n if (default_state?._responsive_collapse) {\n page_opts.responsiveCollapse = true;\n page_opts.collapse_breakpoint_px = default_state._collapse_breakpoint_px;\n page_opts.tableId = `${validID(viewname)}_${statehash}`;\n }\n page_opts.class += `table-valign-${(default_state?._cell_valign || \"Middle\").toLowerCase()} `;\n page_opts.transpose = (default_state || {}).transpose;\n page_opts.header_filters = (default_state || {})._header_filters;\n page_opts.header_filters_toggle = (default_state || {})._header_filters_toggle;\n page_opts.header_filters_dropdown = (default_state || {})._header_filters_dropdown;\n if (page_opts.header_filters_toggle || page_opts.header_filters_dropdown) {\n page_opts.header_filters_open = new Set(Object.keys(state).filter((k) => !k.startsWith(\"_\") ||\n k.startsWith(\"_from\") ||\n k.startsWith(\"_to\") ||\n k.startsWith(\"_lt\") ||\n k.startsWith(\"_gt\")));\n }\n page_opts.transpose_width = (default_state || {}).transpose_width;\n page_opts.transpose_width_units = (default_state || {}).transpose_width_units;\n if (default_state?._row_color_formula)\n page_opts.row_color_function = get_expression_function(default_state?._row_color_formula, fields);\n const [vpos, hpos] = (create_view_location || \"Bottom left\").split(\" \");\n const istop = vpos === \"Top\";\n const isright = hpos === \"right\";\n var create_link = \"\";\n const user_id = extraOpts && extraOpts.req.user ? extraOpts.req.user.id : null;\n const create_link_showif_pass = create_view_showif\n ? eval_expression(create_view_showif, state, extraOpts.req.user, \"Create view show if formula\")\n : undefined;\n if (create_link_showif_pass !== false &&\n view_to_create &&\n (create_link_showif_pass ||\n role <= table.min_role_write ||\n table.ownership_field_id)) {\n const create_view = view_1.default.findOne({ name: view_to_create });\n const ownership_field = table.ownership_field_id &&\n table.fields.find((f) => f.id === table.ownership_field_id);\n const about_user = fields.some((f) => f.reftable_name === \"users\" &&\n state[f.name] &&\n state[f.name] === user_id);\n if (create_link_showif_pass ||\n role <= table.min_role_write ||\n (role < 100 &&\n ((ownership_field?.reftable_name === \"users\" && about_user) ||\n create_view?.configuration?.fixed?.[`preset_${ownership_field?.name}`] === \"LoggedIn\"))) {\n if (create_view_display === \"Embedded\") {\n if (!create_view)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find embedded view to create ${view_to_create}`);\n create_link = await create_view.run(state, extraOpts);\n }\n else {\n const target = `/view/${encodeURIComponent(view_to_create)}${(0, plugin_helper_1.stateToQueryString)(state)}`;\n const hrefVal = isWeb(extraOpts.req) || create_view_display === \"Popup\"\n ? target\n : `javascript:execLink('${target}');`;\n create_link = (0, plugin_helper_1.link_view)(hrefVal, __(create_view_label) || `Add ${pluralize(table.name, 1)}`, create_view_display === \"Popup\" ? { reload_view: viewname } : false, create_link_style, create_link_size);\n }\n }\n }\n const create_link_div = isright\n ? div({ class: \"float-end\" }, create_link)\n : create_link;\n let tableHtml;\n if (default_state?._group_by) {\n const groups = {};\n for (const row of rows) {\n const group = eval_expression(default_state?._group_by, row, extraOpts.req.user, \"Group by expression\");\n if (!groups[group])\n groups[group] = [];\n groups[group].push(row);\n }\n page_opts.grouped = true;\n tableHtml = mkTable(default_state?.hide_null_columns\n ? remove_null_cols(tfields, rows)\n : tfields, groups, page_opts);\n }\n else {\n tableHtml = mkTable(default_state?.hide_null_columns\n ? remove_null_cols(tfields, rows)\n : tfields, rows, page_opts);\n }\n const previewNote = extraOpts.isPreview && default_state?._rows_per_page > 50\n ? i(\"Preview limited to 50 rows\")\n : \"\";\n return div({\n \"data-sc-state-hash\": statehash,\n \"data-sc-rows-per-page\": String(rows_per_page),\n \"data-sc-total-rows\": rowCount !== undefined ? String(rowCount) : false,\n }, istop ? create_link_div + tableHtml + previewNote : tableHtml + previewNote + create_link_div);\n};\nconst remove_null_cols = (tfields, rows) => tfields.filter((tfield) => {\n const key = tfield.row_key || tfield.key;\n if (!(typeof key === \"string\" || Array.isArray(key)))\n return true; //unable to tell if should be removed\n const is_not_null_simple = (row) => row[key] !== null && typeof row[key] !== \"undefined\";\n const is_not_null_array = (row) => row[key[0]] !== null &&\n typeof row[key[0]] !== \"undefined\" &&\n typeof row[key[0]][key[1]] !== \"undefined\";\n if (Array.isArray(key))\n return rows.some(is_not_null_array);\n else\n return rows.some(is_not_null_simple);\n});\nconst run_action = async (table_id, viewname, { columns, layout, default_state }, body, { req, res }, { getRowQuery }) => {\n return await db.withTransaction(async () => {\n const col = columns.find((c, index) => c.type === \"Action\" &&\n (c.rndid == body.rndid ||\n (c.action_name === body.action_name &&\n body.action_name &&\n (body.column_index ? body.column_index === index : true))));\n const table = table_1.default.findOne({ id: table_id });\n const row = await getRowQuery(body[table.pk_name]);\n if (!col && body.action_name === default_state?._row_click_action) {\n const trigger = trigger_1.default.findOne({ name: body.action_name });\n const result = await trigger.runWithoutRow({\n row,\n table,\n Table: table_1.default,\n referrer: req?.get?.(\"Referrer\"),\n user: req.user,\n req,\n });\n return { json: { success: \"ok\", ...(result || {}) } };\n }\n const state_action = getState().actions[col.action_name];\n col.configuration = col.configuration || {};\n if (state_action) {\n const cfgFields = await (0, plugin_helper_1.getActionConfigFields)(state_action, table, {\n mode: \"list\",\n req,\n });\n cfgFields.forEach(({ name }) => {\n if (typeof col.configuration[name] === \"undefined\")\n col.configuration[name] = col[name];\n });\n }\n const result = await (0, plugin_helper_1.run_action_column)({\n col,\n req,\n table,\n row,\n res,\n referrer: req?.get?.(\"Referrer\"),\n });\n return { json: { success: \"ok\", ...(result || {}) } };\n }, (e) => {\n crash_1.default.create(e, req);\n return { json: { error: e.message || e } };\n });\n};\nconst createBasicView = async ({ table, viewname, template_view, template_table, all_views_created, }) => {\n const configuration = await (0, plugin_helper_1.initial_config_all_fields)(false)({\n table_id: table.id,\n });\n delete configuration.layout;\n let template_table_views = {};\n if (template_view) {\n (await view_1.default.find({\n table_id: template_table.id,\n })).forEach((v) => {\n template_table_views[v.name] = v.viewtemplate;\n });\n template_view.configuration.columns.forEach((c) => {\n if (c.type === \"Field\") {\n const field = template_table.getField(c.field_name);\n c.field_type = field.type.name || field.type;\n }\n });\n configuration.columns.forEach((mycol) => {\n if (mycol.type === \"Field\") {\n const field_name = mycol.field_name;\n const field = table.getField(field_name);\n const field_type = field.type.name || field.type;\n const matched = template_view.configuration.columns.find((c) => c.field_type === field_type);\n if (matched) {\n if (matched.configuration)\n Object.assign(mycol, matched.configuration);\n Object.assign(mycol, matched);\n mycol.field_name = field_name;\n }\n }\n });\n }\n const comppk = table.composite_pk_names;\n //show link\n if (all_views_created.Show) {\n if (template_view) {\n //find link to show view\n const col = template_view.configuration.columns.find((c) => c.type === \"ViewLink\" &&\n template_table_views[c.view?.split?.(\":\")[1]] === \"Show\");\n if (col) {\n configuration.columns.push({\n ...col,\n view: `Own:${all_views_created.Show}`,\n view_name: all_views_created.Show,\n relation: undefined,\n view_label: col.view_label || col.label,\n });\n }\n }\n else if (comppk)\n configuration.columns.push({\n link_url: \"`/view/\" +\n all_views_created.Show +\n \"?\" +\n comppk.map((pkNm) => pkNm + \"=${\" + pkNm + \"}\").join(\"&\") +\n \"`\", //\"Street=${Street}&Number=${Number}\"\n link_text: \"Show\",\n type: \"Link\",\n block: false,\n link_src: \"URL\",\n nofollow: false,\n link_url_formula: true,\n header_label: \"Show\",\n });\n else\n configuration.columns.push({\n type: \"ViewLink\",\n view: `Own:${all_views_created.Show}`,\n view_name: all_views_created.Show,\n link_style: \"\",\n view_label: \"Show\",\n header_label: \"Show\",\n });\n }\n //edit link\n if (all_views_created.Edit) {\n configuration.view_to_create = all_views_created.Edit;\n if (template_view) {\n //find link to show view\n const col = template_view.configuration.columns.find((c) => c.type === \"ViewLink\" &&\n template_table_views[c.view?.split?.(\":\")[1]] === \"Edit\");\n if (col) {\n configuration.columns.push({\n ...col,\n view: `Own:${all_views_created.Edit}`,\n view_name: all_views_created.Edit,\n relation: undefined,\n view_label: col.view_label || col.label,\n });\n }\n }\n else if (comppk)\n configuration.columns.push({\n link_url: \"`/view/\" +\n all_views_created.Edit +\n \"?\" +\n comppk.map((pkNm) => pkNm + \"=${\" + pkNm + \"}\").join(\"&\") +\n \"`\", //\"Street=${Street}&Number=${Number}\"\n link_text: \"Edit\",\n type: \"Link\",\n block: false,\n link_src: \"URL\",\n nofollow: false,\n link_url_formula: true,\n header_label: \"Edit\",\n });\n else\n configuration.columns.push({\n type: \"ViewLink\",\n view: `Own:${all_views_created.Edit}`,\n view_name: all_views_created.Edit,\n link_style: \"\",\n view_label: \"Edit\",\n header_label: \"Edit\",\n });\n }\n if (template_view) {\n const matched = template_view.configuration.columns.filter((c) => c.type === \"Action\");\n if (matched.length)\n configuration.columns.push(...matched);\n }\n else\n configuration.columns.push({\n type: \"Action\",\n action_name: \"Delete\",\n action_style: \"btn-primary\",\n });\n const copy_cfg = (keys, parent_field) => {\n if (parent_field && !configuration[parent_field])\n configuration[parent_field] = {};\n for (const key of Array.isArray(keys)\n ? keys\n : keys.split(\" \").filter(Boolean)) {\n if (parent_field)\n configuration[parent_field][key] =\n template_view.configuration[parent_field][key];\n else\n configuration[key] = template_view.configuration[key];\n }\n };\n // create new row options\n if (template_view && all_views_created.Edit) {\n copy_cfg(\"create_view_display create_view_location create_link_style create_link_size\");\n }\n // list layout settings\n if (template_view && template_view.configuration.default_state) {\n copy_cfg(\"_rows_per_page _full_page_count _hide_pagination transpose transpose_width transpose_width_units _omit_header hide_null_columns _hover_rows _striped_rows _card_rows _borderless _cell_valign _header_filters _header_filters_toggle _header_filters_dropdown _responsive_collapse _sticky_header _collapse_breakpoint_px _row_color_formula _table_layout\", \"default_state\");\n }\n //console.log(\"new cols\", configuration.columns);\n return configuration;\n};\nmodule.exports = {\n name: \"List\",\n description: \"Display multiple rows from a table in a grid with columns you specify\",\n configuration_workflow,\n run,\n view_quantity: \"Many\",\n get_state_fields,\n initial_config,\n on_delete,\n routes: { run_action },\n default_state_form: ({ default_state }) => {\n if (!default_state)\n return default_state;\n const { _omit_state_form, _create_db_view, _order_field, _descending, include_fml, exclusion_relation, exclusion_where, _rows_per_page, _full_page_count, _group_by, _tree_field, _hide_pagination, _row_click_url_formula, _row_click_url_type, _row_click_type, _row_click_url_action, transpose, transpose_width, transpose_width_units, _omit_header, hide_null_columns, _hover_rows, _striped_rows, _card_rows, _borderless, _cell_valign, _table_layout, _responsive_collapse, _collapse_breakpoint_px, _header_filters, _header_filters_toggle, _header_filters_dropdown, _row_color_formula, _sticky_header, ...ds } = default_state;\n return ds && removeDefaultColor(removeEmptyStrings(ds));\n },\n getStringsForI18n({ columns, layout, create_view_label }) {\n const strings = [];\n const maybeAdd = (s) => {\n if (s)\n strings.push(s);\n };\n for (const column of columns) {\n maybeAdd(column.header_label);\n maybeAdd(column.link_text);\n maybeAdd(column.view_label);\n maybeAdd(column.label);\n maybeAdd(column.action_label);\n }\n for (const lseg of layout?.besides || []) {\n maybeAdd(lseg.header_label);\n }\n maybeAdd(create_view_label);\n return strings;\n },\n createBasicView,\n queries: ({ table_id, exttable_name, name, // viewname\n configuration: { columns, layout, default_state }, req, }) => ({\n async listQuery(state, stateHash, isPreview) {\n const table = table_1.default.findOne(typeof exttable_name === \"string\"\n ? { name: exttable_name }\n : { id: table_id });\n const pk_name = table.pk_name;\n const fields = table.getFields();\n const { joinFields, aggregations } = (0, plugin_helper_1.picked_fields_to_query)(columns, fields, layout, req, table);\n const where = (0, plugin_helper_1.stateFieldsToWhere)({\n fields,\n state,\n table,\n prefix: \"a.\",\n });\n const whereForCount = (0, plugin_helper_1.stateFieldsToWhere)({\n fields,\n state,\n table,\n });\n const q = (0, plugin_helper_1.stateFieldsToQuery)({\n state,\n fields,\n prefix: \"a.\",\n stateHash,\n });\n const rows_per_page = (default_state && default_state._rows_per_page) || 20;\n if (!q.limit)\n q.limit = isPreview ? Math.min(50, rows_per_page) : rows_per_page;\n const sort_from_state = !!q.orderBy;\n if (!q.orderBy && default_state?._order_field)\n q.orderBy = default_state?._order_field;\n if (!q.orderDesc && !sort_from_state)\n q.orderDesc = default_state && default_state._descending;\n if (default_state?._group_by)\n (0, plugin_helper_1.add_free_variables_to_joinfields)(freeVariables(default_state._group_by || \"\"), joinFields, fields);\n const role = req && req.user ? req.user.role_id : 100;\n if (default_state?.include_fml) {\n const ctx = { ...state, user_id: req.user?.id || null, user: req.user };\n let where1 = jsexprToWhere(default_state.include_fml, ctx, fields);\n mergeIntoWhere(where, where1 || {});\n mergeIntoWhere(whereForCount, where1 || {});\n }\n if (default_state?.exclusion_relation) {\n const [reltable, relfld] = default_state.exclusion_relation.split(\".\");\n const relTable = table_1.default.findOne({ name: reltable });\n const relWhere = default_state.exclusion_where\n ? jsexprToWhere(default_state.exclusion_where, {\n user_id: req?.user?.id,\n user: req?.user,\n }, relTable.fields)\n : {};\n const relRows = await relTable.getRows(relWhere);\n if (relRows.length > 0) {\n const mergeObj = !db.isSQLite\n ? {\n [table.pk_name]: {\n not: { in: relRows.map((r) => r[relfld]) },\n },\n }\n : {\n not: { or: relRows.map((r) => ({ id: r[relfld] })) },\n };\n mergeIntoWhere(where, mergeObj);\n mergeIntoWhere(whereForCount, mergeObj);\n }\n }\n let rows;\n if (default_state?._tree_field) {\n const tree_rows = await table.getRows(where, {\n ...q,\n forPublic: !req.user || req.user.role_id === 100,\n forUser: req.user,\n tree_field: default_state?._tree_field,\n pk_name,\n disable_ownership_postqfilter: true,\n });\n //console.log(\"tree rows\", tree_rows);\n const joined_rows = await table.getJoinedRows({\n where: {\n [pk_name]: { in: tree_rows.map((r) => r[pk_name]) },\n },\n joinFields,\n aggregations,\n ...q,\n forPublic: !req.user || req.user.role_id === 100,\n forUser: req.user,\n });\n const joined_map = {};\n joined_rows.forEach((r) => (joined_map[r[pk_name]] = r));\n rows = tree_rows.map((r) => {\n const r2 = joined_map[r[pk_name]];\n r2._level = r._level;\n return r2;\n });\n }\n else\n rows = await table.getJoinedRows({\n where,\n joinFields,\n aggregations,\n ...q,\n forPublic: !req.user || req.user.role_id === 100,\n forUser: req.user,\n });\n //console.log(\"rows\", rows);\n const rowCount = default_state?._hide_pagination\n ? undefined\n : q.limit && rows.length < q.limit\n ? rows.length\n : await table.countRows(whereForCount, {\n forPublic: !req.user,\n forUser: req.user,\n ...(default_state?._full_page_count === false\n ? { limit: (q.offset || 0) + 4 * (q.limit || 100) + 1 }\n : {}),\n });\n return { rows, rowCount };\n },\n async getRowQuery(id) {\n const table = table_1.default.findOne({ id: table_id });\n if (table.ownership_formula) {\n const freeVars = freeVariables(table.ownership_formula);\n const joinFields = {};\n (0, plugin_helper_1.add_free_variables_to_joinfields)(freeVars, joinFields, table.fields);\n return await table.getJoinedRow({\n where: { [table.pk_name]: id },\n joinFields,\n forUser: req.user || { role_id: 100 },\n forPublic: !req.user,\n });\n }\n else\n return await table.getRow({ [table.pk_name]: id }, { forUser: req.user, forPublic: !req.user });\n },\n }),\n configCheck: async (view) => {\n return await check_view_columns(view, view.configuration.columns);\n },\n connectedObjects: async (configuration) => {\n const fromColumns = extractFromColumns(configuration.columns);\n const toCreate = extractViewToCreate(configuration);\n return toCreate\n ? mergeConnectedObjects(fromColumns, toCreate)\n : fromColumns;\n },\n};\n//# sourceMappingURL=list.js.map\n\n//# sourceURL=webpack://saltcorn/../saltcorn-data/dist/base-plugin/viewtemplates/list.js?\n}");
105
-
106
- /***/ },
107
-
108
- /***/ "../saltcorn-data/dist/base-plugin/viewtemplates/listshowlist.js"
109
- /*!***********************************************************************!*\
110
- !*** ../saltcorn-data/dist/base-plugin/viewtemplates/listshowlist.js ***!
111
- \***********************************************************************/
112
- (module, __unused_webpack_exports, __webpack_require__) {
113
-
114
- "use strict";
115
- eval("{\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\n/**\n * @category saltcorn-data\n * @module base-plugin/viewtemplates/listshowlist\n * @subcategory base-plugin\n */\nconst table_1 = __importDefault(__webpack_require__(/*! ../../models/table */ \"../saltcorn-data/dist/models/table.js\"));\nconst form_1 = __importDefault(__webpack_require__(/*! ../../models/form */ \"../saltcorn-data/dist/models/form.js\"));\nconst view_1 = __importDefault(__webpack_require__(/*! ../../models/view */ \"../saltcorn-data/dist/models/view.js\"));\nconst workflow_1 = __importDefault(__webpack_require__(/*! ../../models/workflow */ \"../saltcorn-data/dist/models/workflow.js\"));\nconst { text, div, h4, h6, a } = __webpack_require__(/*! @saltcorn/markup/tags */ \"../saltcorn-markup/dist/tags.js\");\nconst { renderForm, tabs } = __webpack_require__(/*! @saltcorn/markup */ \"../saltcorn-markup/dist/index.js\");\nconst plugin_helper_1 = __webpack_require__(/*! ../../plugin-helper */ \"../saltcorn-data/dist/plugin-helper.js\");\nconst viewable_fields_1 = __webpack_require__(/*! ../../viewable_fields */ \"../saltcorn-data/dist/viewable_fields.js\");\nconst utils_1 = __importDefault(__webpack_require__(/*! ../../utils */ \"../saltcorn-data/dist/utils.js\"));\nconst { InvalidConfiguration, extractPagings } = utils_1.default;\nconst configuration_workflow = (req) => new workflow_1.default({\n steps: [\n {\n name: req.__(\"Views\"),\n form: async (context) => {\n const list_views = await view_1.default.find_table_views_where(context.table_id, ({ state_fields, viewrow, viewtemplate }) => viewtemplate.view_quantity === \"Many\" &&\n viewrow.name !== context.viewname &&\n state_fields.every((sf) => !sf.required));\n const list_view_opts = list_views.map((v) => v.name);\n const show_views = await view_1.default.find_table_views_where(context.table_id, ({ state_fields, viewrow }) => viewrow.name !== context.viewname &&\n state_fields.some((sf) => sf.name === \"id\"));\n const show_view_opts = show_views.map((v) => v.name);\n return new form_1.default({\n fields: [\n {\n name: \"list_view\",\n label: req.__(\"List View\"),\n type: \"String\",\n sublabel: req.__(\"A list view shown on the left, to select rows\") +\n \". \" +\n a({\n \"data-dyn-href\": `\\`/viewedit/config/\\${list_view}\\``,\n target: \"_blank\",\n }, req.__(\"Configure\")),\n required: false,\n attributes: {\n options: list_view_opts,\n },\n },\n {\n name: \"show_view\",\n label: req.__(\"Show View\"),\n type: \"String\",\n sublabel: req.__(\"The view to show the selected row\") +\n \". \" +\n a({\n \"data-dyn-href\": `\\`/viewedit/config/\\${show_view}\\``,\n target: \"_blank\",\n }, req.__(\"Configure\")),\n required: false,\n attributes: {\n options: show_view_opts,\n },\n },\n {\n name: \"list_width\",\n label: req.__(\"List width\"),\n sublabel: req.__(\"Number of columns (1-12) allocated to the list view\"),\n type: \"Integer\",\n default: 6,\n attributes: {\n min: 1,\n max: 12,\n },\n },\n ],\n });\n },\n },\n {\n name: req.__(\"Subtables\"),\n contextField: \"subtables\",\n form: async (context) => {\n const tbl = table_1.default.findOne({ id: context.table_id });\n var fields = [];\n const child_views = await (0, plugin_helper_1.get_child_views)(tbl, context.viewname);\n for (const { relation, related_table, views } of child_views) {\n for (const view of views) {\n fields.push({\n name: `ChildList:${view.name}.${related_table.name}.${relation.name}`,\n label: `${view.name} of ${relation.label} on ${related_table.name}`,\n type: \"Bool\",\n });\n }\n }\n const parent_views = await (0, plugin_helper_1.get_parent_views)(tbl, context.viewname);\n for (const { relation, related_table, views } of parent_views) {\n for (const view of views) {\n fields.push({\n name: `ParentShow:${view.name}.${related_table.name}.${relation.name}`,\n label: `${view.name} of ${relation.name} on ${related_table.name}`,\n type: \"Bool\",\n });\n }\n }\n return new form_1.default({\n fields: fields,\n blurb: req.__(\"Which related tables would you like to show in sub-lists below the selected item?\"),\n });\n },\n },\n ],\n});\nconst get_state_fields = async (table_id, viewname, { list_view, show_view }) => {\n const id = {\n name: \"id\",\n type: \"Integer\",\n required: false,\n };\n if (list_view) {\n const lview = await view_1.default.findOne({ name: list_view });\n if (lview) {\n const lview_sfs = await lview.get_state_fields();\n return [id, ...lview_sfs];\n }\n else\n return [id];\n }\n else\n return [id];\n};\nconst run = async (table_id, viewname, { list_view, show_view, list_width, subtables }, state, extraArgs, { getRowQuery }) => {\n const table = table_1.default.findOne({ id: table_id });\n const fields = table.getFields();\n (0, plugin_helper_1.readState)(state, fields);\n var lresp;\n if (list_view) {\n const lview = await view_1.default.findOne({ name: list_view });\n if (!lview)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find view ${list_view}`);\n const state1 = lview.combine_state_and_default_state(state);\n lresp = await lview.run(state1, {\n ...extraArgs,\n onRowSelect: (v) => `select_id('${v.id}', this)`,\n removeIdFromstate: true,\n req: extraArgs.req,\n });\n }\n var sresp = \"\";\n if (show_view) {\n const sview = await view_1.default.findOne({ name: show_view });\n if (!sview)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find view ${show_view}`);\n sresp = await sview.run(state, extraArgs);\n }\n var reltbls = {};\n var myrow;\n const { uniques } = (0, viewable_fields_1.splitUniques)(fields, state, true);\n if (Object.keys(uniques).length > 0) {\n var id;\n if (state.id)\n id = state.id;\n else {\n myrow = getRowQuery(uniques);\n if (!myrow)\n return `Not found`;\n id = myrow.id;\n }\n for (const relspec of Object.keys(subtables || {})) {\n if (subtables[relspec]) {\n const [reltype, rel] = relspec.split(\":\");\n switch (reltype) {\n case \"ChildList\":\n case \"OneToOneShow\":\n const [vname, reltblnm, relfld] = rel.split(\".\");\n const tab_name = reltblnm;\n const subview = await view_1.default.findOne({ name: vname });\n if (!subview)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find view ${vname}`);\n else {\n const allPagings = extractPagings(state);\n const subresp = await subview.run({ [relfld]: id, ...allPagings }, extraArgs);\n reltbls[tab_name] = subresp;\n }\n break;\n case \"ParentShow\":\n const [pvname, preltblnm, prelfld] = rel.split(\".\");\n if (!myrow)\n myrow = await getRowQuery({ id });\n if (!myrow)\n continue;\n const ptab_name = prelfld;\n const psubview = await view_1.default.findOne({ name: pvname });\n if (!psubview)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find view ${pvname}`);\n else {\n const psubresp = await psubview.run({ id: myrow[prelfld] }, extraArgs);\n reltbls[ptab_name] = psubresp;\n }\n break;\n default:\n break;\n }\n }\n }\n }\n const relTblResp = Object.keys(reltbls).length === 1\n ? [h6(Object.keys(reltbls)[0]), reltbls[Object.keys(reltbls)[0]]]\n : tabs(reltbls);\n if (lresp) {\n if (list_width === 12)\n return lresp;\n return div({ class: \"row\" }, div({ class: `col-sm-${list_width || 6}` }, lresp), div({ class: `col-sm-${12 - (list_width || 6)}` }, div({\n class: \"d-inline\",\n \"data-sc-embed-viewname\": show_view,\n }, sresp), relTblResp));\n }\n else {\n return div(div({\n class: \"d-inline\",\n \"data-sc-embed-viewname\": show_view,\n }, sresp), relTblResp);\n }\n};\nmodule.exports = {\n /** @type {string} */\n name: \"ListShowList\",\n /** @type {string} */\n description: \"Combine an optional list view on the left with displays on the right of a single selected row, with views of related rows from different tables underneath\",\n configuration_workflow,\n run,\n get_state_fields,\n queries: ({ table_id, viewname, configuration: { columns, default_state }, req, }) => ({\n async getRowQuery(uniques) {\n const table = table_1.default.findOne({ id: table_id });\n return await table.getJoinedRow({\n where: uniques,\n forUser: req.user,\n forPublic: !req.user,\n });\n },\n }),\n connectedObjects: async ({ list_view, show_view, subtables }) => {\n const subViews = [];\n for (const relspec of Object.keys(subtables || {})) {\n if (subtables[relspec]) {\n const [reltype, rel] = relspec.split(\":\");\n switch (reltype) {\n case \"ChildList\":\n case \"OneToOneShow\":\n const [vname, reltblnm, relfld] = rel.split(\".\");\n const view = view_1.default.findOne({ name: vname });\n if (view)\n subViews.push(view);\n break;\n case \"ParentShow\":\n const [pvname, preltblnm, prelfld] = rel.split(\".\");\n const pView = view_1.default.findOne({ name: pvname });\n if (pView)\n subViews.push(pView);\n break;\n default:\n break;\n }\n }\n }\n const listView = view_1.default.findOne({ name: list_view });\n if (listView)\n subViews.push(listView);\n const showView = view_1.default.findOne({ name: show_view });\n if (showView)\n subViews.push(showView);\n return {\n embeddedViews: subViews,\n };\n },\n};\n//# sourceMappingURL=listshowlist.js.map\n\n//# sourceURL=webpack://saltcorn/../saltcorn-data/dist/base-plugin/viewtemplates/listshowlist.js?\n}");
116
-
117
- /***/ },
118
-
119
- /***/ "../saltcorn-data/dist/base-plugin/viewtemplates/room.js"
120
- /*!***************************************************************!*\
121
- !*** ../saltcorn-data/dist/base-plugin/viewtemplates/room.js ***!
122
- \***************************************************************/
123
- (module, __unused_webpack_exports, __webpack_require__) {
124
-
125
- "use strict";
126
- eval("{\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\n/**\n * @category saltcorn-data\n * @module base-plugin/viewtemplates/room\n * @subcategory base-plugin\n */\nconst table_1 = __importDefault(__webpack_require__(/*! ../../models/table */ \"../saltcorn-data/dist/models/table.js\"));\nconst view_1 = __importDefault(__webpack_require__(/*! ../../models/view */ \"../saltcorn-data/dist/models/view.js\"));\nconst form_1 = __importDefault(__webpack_require__(/*! ../../models/form */ \"../saltcorn-data/dist/models/form.js\"));\nconst workflow_1 = __importDefault(__webpack_require__(/*! ../../models/workflow */ \"../saltcorn-data/dist/models/workflow.js\"));\nconst { text, div, h4, hr, button, form, input, i, script, domReady, } = __webpack_require__(/*! @saltcorn/markup/tags */ \"../saltcorn-markup/dist/tags.js\");\nconst { pagination } = __webpack_require__(/*! @saltcorn/markup/helpers */ \"../saltcorn-markup/dist/helpers.js\");\nconst { renderForm, tabs, link } = __webpack_require__(/*! @saltcorn/markup */ \"../saltcorn-markup/dist/index.js\");\nconst { mkTable } = __webpack_require__(/*! @saltcorn/markup */ \"../saltcorn-markup/dist/index.js\");\nconst plugin_helper_1 = __webpack_require__(/*! ../../plugin-helper */ \"../saltcorn-data/dist/plugin-helper.js\");\nconst { InvalidConfiguration } = __webpack_require__(/*! ../../utils */ \"../saltcorn-data/dist/utils.js\");\nconst { getState } = __webpack_require__(/*! ../../db/state */ \"../saltcorn-data/dist/db/state.js\");\nconst db = __webpack_require__(/*! ../../db */ \"../saltcorn-data/dist/db/index.js\");\nconst viewable_fields_1 = __webpack_require__(/*! ../../viewable_fields */ \"../saltcorn-data/dist/viewable_fields.js\");\nconst { extractFromLayout } = __webpack_require__(/*! ../../diagram/node_extract_utils */ \"../saltcorn-data/dist/diagram/node_extract_utils.js\");\nconst layout_1 = __importDefault(__webpack_require__(/*! ../../models/layout */ \"../saltcorn-data/dist/models/layout.js\"));\nconst { traverse } = layout_1.default;\n/**\n *\n * @param {object} req\n * @returns {Workflow}\n */\nconst configuration_workflow = (req) => new workflow_1.default({\n steps: [\n {\n name: req.__(\"Views\"),\n form: async (context) => {\n /*\n we need:\n - message string\n - message show view?\n - message sender field\n - participant field: key to user in table with fkey to this\n */\n const roomtable = table_1.default.findOne(context.table_id);\n const { child_relations } = await roomtable.get_child_relations();\n //const msg_table_options = child_relations.map(cr=>cr.table.name)\n const participant_field_options = [];\n const msg_relation_options = [];\n const msgsender_field_options = {};\n const msgview_options = {};\n const msgform_options = {};\n const participant_max_read_options = [];\n const msg_own_options = [];\n for (const { table, key_field } of child_relations) {\n const fields = table.getFields();\n for (const f of fields) {\n if (f.reftable_name === \"users\") {\n participant_field_options.push(`${table.name}.${key_field.name}.${f.name}`);\n msg_relation_options.push(`${table.name}.${key_field.name}`);\n msgsender_field_options[`${table.name}.${key_field.name}`] = [\n ...(msgsender_field_options[`${table.name}.${key_field.name}`] || []),\n f.name,\n ];\n const views = await view_1.default.find_possible_links_to_table(table);\n msgview_options[`${table.name}.${key_field.name}`] = views.map((v) => v.name);\n msgform_options[`${table.name}.${key_field.name}`] = views.map((v) => v.name);\n }\n else if (f.reftable_name) {\n participant_max_read_options.push(`${table.name}.${key_field.name}.${f.name}`);\n }\n }\n }\n return new form_1.default({\n fields: [\n {\n name: \"msg_relation\",\n label: req.__(\"Message relation\"),\n type: \"String\",\n sublabel: req.__(\"The relationship to the table of individual messages\"),\n required: true,\n attributes: {\n options: msg_relation_options,\n },\n },\n {\n name: \"msgsender_field\",\n label: req.__(\"Message sender field\"),\n type: \"String\",\n sublabel: req.__(\"The field for the sender user id on the table for messages\"),\n required: true,\n attributes: {\n calcOptions: [\"msg_relation\", msgsender_field_options],\n },\n },\n {\n name: \"msgview\",\n label: req.__(\"Message show view\"),\n type: \"String\",\n sublabel: req.__(\"The view to show an individual message\"),\n required: true,\n attributes: {\n calcOptions: [\"msg_relation\", msgview_options],\n },\n },\n {\n name: \"msgform\",\n label: req.__(\"New message form view\"),\n type: \"String\",\n sublabel: req.__(\"The view to enter a new message\"),\n required: true,\n attributes: {\n calcOptions: [\"msg_relation\", msgform_options],\n },\n },\n {\n name: \"participant_field\",\n label: req.__(\"Participant field\"),\n type: \"String\",\n sublabel: req.__(\"The field for the participant user id\"),\n required: false,\n attributes: {\n options: participant_field_options,\n },\n },\n {\n name: \"participant_maxread_field\",\n label: req.__(\"Participant max read id field\"),\n type: \"String\",\n sublabel: req.__(\"The field for the participant's last read message, of type Key to message table\"),\n attributes: {\n options: participant_max_read_options,\n },\n },\n ],\n });\n },\n },\n ],\n});\n/**\n * @returns {object[]}\n */\nconst get_state_fields = () => [\n {\n name: \"id\",\n type: \"Integer\",\n required: true,\n primary_key: true,\n },\n];\nconst limit = 10;\n/**\n * @param {string} table_id\n * @param {string} viewname\n * @param {object} optsOne\n * @param {string} optsOne.participant_field,\n * @param {string} optsOne.msg_relation\n * @param {*} optsOne.msgsender_field\n * @param {string} optsOne.msgview\n * @param {string} optsOne.msgform\n * @param {string} optsOne.participant_maxread_field\n * @param {object} state\n * @param {object} optsTwo\n * @param {object} optsTwo.req\n * @param {object} optsTwo.res\n * @returns {Promise<div>}\n * @throws {InvalidConfiguration}\n */\nconst run = async (table_id, viewname, { participant_field, msg_relation, msgsender_field, msgview, msgform, participant_maxread_field, }, state, { req, res }, { getRowQuery, updateQuery, optionsQuery }) => {\n const table = table_1.default.findOne({ id: table_id });\n const fields = table.getFields();\n (0, plugin_helper_1.readState)(state, fields);\n if (!state.id)\n return \"Need room id\";\n const appState = getState();\n const locale = req.getLocale();\n const role = req && req.user ? req.user.role_id : 100;\n const __ = (s) => appState.i18n.__({ phrase: s, locale }) || s;\n if (!msgview || !msgform || !msgsender_field || !msg_relation)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: must supply Message views, Message sender and Participant fields`);\n const [msgtable_name, msgkey_to_room] = msg_relation.split(\".\");\n let partRow, part_table_name, canWrite;\n if (participant_field) {\n const [part_table_name1, part_key_to_room, part_user_field] = participant_field.split(\".\");\n part_table_name = part_table_name1;\n // check we participate\n partRow = await getRowQuery(state.id, part_table_name, part_user_field, part_key_to_room);\n if (!partRow)\n return \"You are not a participant in this room\";\n canWrite = true;\n }\n else {\n // check we have and write access\n const canRead = role <= table.min_role_read;\n if (!canRead)\n return \"You do not have access to this room\";\n canWrite = role <= table.min_role_write;\n }\n const v = await view_1.default.findOne({ name: msgview });\n const vresps = await v.runMany({ [msgkey_to_room]: state.id }, { req, res, orderBy: \"id\", orderDesc: true, limit });\n vresps.reverse();\n const n_retrieved = vresps.length;\n const msglist = vresps.map((r) => r.html).join(\"\");\n const formview = await view_1.default.findOne({ name: msgform });\n if (!formview)\n throw new InvalidConfiguration(\"Message form view does not exist\");\n const { columns, layout } = formview.configuration;\n const msgtable = table_1.default.findOne({ name: msgtable_name });\n const min_read_id = Math.min.apply(Math, vresps.map((r) => r.row.id));\n if (participant_maxread_field) {\n const [part_table_name1, part_key_to_room1, part_maxread_field] = participant_maxread_field.split(\".\");\n const max_read_id = Math.max.apply(Math, vresps.map((r) => r.row.id));\n if (vresps.length > 0 && partRow && part_table_name)\n await updateQuery(partRow, part_table_name, max_read_id, part_maxread_field);\n }\n const formObj = await (0, viewable_fields_1.getForm)(msgtable, viewname, columns, layout, null, req);\n formObj.class = `room-${state.id}`;\n formObj.hidden(\"room_id\");\n formObj.values = { room_id: state.id };\n await formObj.fill_fkey_options(false, optionsQuery, req.user || { role_id: 100 });\n await transformForm({\n form: formObj,\n table: msgtable,\n req,\n res,\n viewname: msgform,\n });\n return div(n_retrieved === limit &&\n button({\n class: \"btn btn-outline-secondary mb-1 fetch_older\",\n onclick: `room_older('${viewname}',${state.id},this)`,\n \"data-lt-msg-id\": min_read_id,\n }, req.__(\"Show older messages\")), div({ class: `msglist-${state.id}`, \"data-user-id\": req.user?.id }, msglist), canWrite && renderForm(formObj, req.csrfToken()), script({\n src: `/static_assets/${db.connectObj.version_tag}/socket.io.min.js`,\n }) + script(domReady(`init_room(\"${viewname}\", ${state.id})`)));\n};\nconst transformForm = async ({ form, table, req, res, viewname }) => {\n const row = {};\n await traverse(form.layout, {\n async action(segment) {\n if (segment.action_style === \"on_page_load\") {\n segment.type = \"blank\";\n segment.style = {};\n if (segment.minRole && segment.minRole != 100) {\n const minRole = +segment.minRole;\n const userRole = req?.user?.role_id || 100;\n if (minRole < userRole)\n return;\n }\n if (req.method === \"POST\")\n return;\n //run action\n try {\n const actionResult = await (0, plugin_helper_1.run_action_column)({\n col: { ...segment },\n referrer: req?.get?.(\"Referrer\"),\n req,\n res,\n table,\n row,\n });\n if (actionResult)\n segment.contents = script(domReady(`common_done(${JSON.stringify(actionResult)}, \"${viewname}\")`));\n }\n catch (e) {\n getState().log(5, `Error in Edit ${viewname} on page load action: ${e.message}`);\n e.message = `Error in evaluating Run on Page Load action in view ${viewname}: ${e.message}`;\n throw e;\n }\n }\n else if (![\"Sign up\", ...viewable_fields_1.edit_build_in_actions].includes(segment.action_name) &&\n !segment.action_name.startsWith(\"Login\")) {\n let url = (0, viewable_fields_1.action_url)(viewname, table, segment.action_name, row, segment.rndid, \"rndid\", segment.confirm, segment.spinner);\n if (url.javascript) {\n //redo to include dynamic row\n const confirmStr = segment.confirm\n ? `if(confirm('Are you sure?'))`\n : \"\";\n url.javascript = `${confirmStr}view_post(this, 'run_action', {rndid:'${segment.rndid}', ...get_form_record(this)});`;\n }\n segment.action_link = (0, viewable_fields_1.action_link)(url, req, segment);\n }\n },\n });\n};\n/**\n * @param {*} table_id\n * @param {*} viewname\n * @param {object} optsOne\n * @param {string} optsOne.participant_field\n * @param {string} optsOne.participant_maxread_field\n * @param {body} body\n * @param {object} optsTwo\n * @param {object} optsTwo.req\n * @param {object} optsTwo.res\n * @returns {Promise<void>}\n */\nconst ack_read = async (table_id, viewname, { participant_field, participant_maxread_field }, body, { req, res }, { ackReadQuery }) => {\n if (!participant_maxread_field || !participant_field)\n return {\n json: {\n success: \"ok\",\n },\n };\n return await ackReadQuery(participant_field, participant_maxread_field, body);\n};\n/**\n * @param {*} table_id\n * @param {*} viewname\n * @param {object} optsOne.\n * @param {string} optsOne.participant_field\n * @param {string} optsOne.msg_relation\n * @param {*} optsOne.msgsender_field\n * @param {string} optsOne.msgview\n * @param {*} optsOne.msgform\n * @param {*} optsOne.participant_maxread_field\n * @param {object} body\n * @param {object} optsTwo\n * @param {object} optsTwo.req\n * @param {object} optsTwo.res\n * @returns {Promise<object>}\n */\nconst fetch_older_msg = async (table_id, viewname, { participant_field, msg_relation, msgsender_field, msgview, msgform, participant_maxread_field, }, body, { req, res }, { fetchOlderMsgQuery }) => {\n const partRow = await fetchOlderMsgQuery(participant_field, body);\n if (!partRow)\n return {\n json: {\n error: \"Not participating\",\n },\n };\n const [msgtable_name, msgkey_to_room] = msg_relation.split(\".\");\n const v = await view_1.default.findOne({ name: msgview });\n const vresps = await v.runMany({ [msgkey_to_room]: +body.room_id }, {\n req,\n res,\n orderBy: \"id\",\n orderDesc: true,\n limit,\n where: { id: { lt: +body.lt_msg_id } },\n });\n vresps.reverse();\n const n_retrieved = vresps.length;\n const min_read_id = Math.min.apply(Math, vresps.map((r) => r.row.id));\n const msglist = vresps.map((r) => r.html).join(\"\");\n return {\n json: {\n success: \"ok\",\n prepend: msglist,\n remove_fetch_older: n_retrieved < limit,\n new_fetch_older_lt: min_read_id,\n },\n };\n};\n/**\n * @param {*} table_id\n * @param {string} viewname\n * @param {object} optsOne\n * @param {string} optsOne.participant_field\n * @param {string} optsOne.msg_relation\n * @param {*} optsOne.msgsender_field\n * @param {string} optsOne.msgview\n * @param {string} optsOne.msgform\n * @param {string} optsOne.participant_maxread_field\n * @param {*} body\n * @param {object} optsTwo\n * @param {object} optsTwo.req\n * @param {object} optsTwo.res\n * @returns {Promise<object>}\n */\nconst submit_msg_ajax = async (table_id, viewname, { participant_field, msg_relation, msgsender_field, msgview, msgform, participant_maxread_field, }, body, { req, res }, { submitAjaxQuery }) => {\n const queryResult = await submitAjaxQuery(msg_relation, participant_field, body, msgform, msgsender_field, participant_maxread_field);\n if (!queryResult.json.error) {\n const msgid = queryResult.json.msgid;\n const v = await view_1.default.findOne({ name: msgview });\n const myhtml = await v.run({ id: msgid.success }, { req, res });\n const newreq = { ...req, user: { ...req.user, id: 0 } };\n const theirhtml = await v.run({ id: msgid.success }, { req: newreq, res });\n const tenant = db.getTenantSchema();\n getState().emitRoom(tenant, viewname, +body.room_id, {\n append: theirhtml,\n not_for_user_id: req.user?.id,\n pls_ack_msg_id: msgid.success,\n });\n return {\n json: {\n success: \"ok\",\n append: myhtml,\n },\n };\n }\n else {\n return queryResult;\n }\n};\n/**\n * @param {*} table_id\n * @param {string} viewname\n * @param {object} opts\n * @param {*} opts.participant_field\n * @param {string} opts.msg_relation,\n * @param {string} opts.msgsender_field,\n * @param {string} opts.msgview,\n * @param {*} opts.msgform,\n * @param {*} opts.participant_maxread_field,\n * @returns {object[]}\n */\nconst virtual_triggers = (table_id, viewname, { participant_field, msg_relation, msgsender_field, msgview, msgform, participant_maxread_field, }) => {\n if (!msg_relation)\n return [];\n const [msgtable_name, msgkey_to_room] = msg_relation.split(\".\");\n const msgtable = table_1.default.findOne({ name: msgtable_name });\n if (!msgsender_field)\n return [];\n return [\n {\n when_trigger: \"Insert\",\n table_id: msgtable.id,\n run: async (row) => {\n const state = getState();\n if (row[msgsender_field])\n return; // TODO how else to avoid double emit\n const v = await view_1.default.findOne({ name: msgview });\n const html = await v.run({ id: row.id }, {\n req: {\n getLocale: () => state.getConfig(\"default_locale\", \"en\"),\n user: { id: 0 },\n __: (s) => s,\n },\n res: {},\n });\n const tenant = db.getTenantSchema();\n state.emitRoom(tenant, viewname, row[msgkey_to_room], {\n append: html,\n pls_ack_msg_id: row.id,\n });\n },\n },\n ];\n};\nconst run_action = async (table_id, viewname, { msgform }, body, { req, res }) => {\n const view = view_1.default.findOne({ name: msgform });\n const result = await view.runRoute(\"run_action\", req.body, res, { req, res });\n return result;\n};\nmodule.exports = {\n /** @type {string} */\n name: \"Room\",\n /** @type {string} */\n description: \"Real-time space for chat\",\n configuration_workflow,\n run,\n get_state_fields,\n /** @type {boolean} */\n routes: { submit_msg_ajax, ack_read, fetch_older_msg, run_action },\n /** @type {boolean} */\n noAutoTest: true,\n /**\n * @param {object} opts\n * @param {object} opts.participant_field\n * @param {string} room_id\n * @param {object} user\n * @returns {Promise<object>}\n */\n authorize_join: async ({ table_id, min_role, configuration: { participant_field } }, room_id, user) => {\n if (!user || user.role_id > min_role)\n return false;\n if (!participant_field) {\n const table = table_1.default.findOne({ id: table_id });\n return user.role_id <= table.min_role_read;\n }\n else {\n const [part_table_name, part_key_to_room, part_user_field] = participant_field.split(\".\");\n // TODO check we participate\n const parttable = table_1.default.findOne({ name: part_table_name });\n const partRow = await parttable.getRow({\n [part_user_field]: user.id,\n [part_key_to_room]: room_id,\n });\n return !!partRow;\n }\n },\n virtual_triggers,\n /** @returns {object[]} */\n getStringsForI18n() {\n return [];\n },\n queries: ({ table_id, viewname, configuration: { columns, default_state }, req, }) => ({\n async getRowQuery(state_id, part_table_name, part_user_field, part_key_to_room) {\n const parttable = table_1.default.findOne({ name: part_table_name });\n return await parttable.getRow({\n [part_user_field]: req.user ? req.user.id : 0,\n [part_key_to_room]: +state_id,\n });\n },\n async updateQuery(partRow, part_table_name, max_read_id, part_maxread_field) {\n const parttable = table_1.default.findOne({ name: part_table_name });\n await parttable.updateRow({ [part_maxread_field]: max_read_id }, partRow.id);\n },\n async submitAjaxQuery(msg_relation, participant_field, body, msgform, msgsender_field, participant_maxread_field) {\n const table = table_1.default.findOne({ id: table_id });\n const [msgtable_name, msgkey_to_room] = msg_relation.split(\".\");\n const role = req && req.user ? req.user.role_id : 100;\n let partRow, parttable;\n if (participant_field) {\n const [part_table_name, part_key_to_room, part_user_field] = participant_field.split(\".\");\n parttable = table_1.default.findOne({ name: part_table_name });\n // check we participate\n partRow = await parttable.getRow({\n [part_user_field]: req.user ? req.user.id : 0,\n [part_key_to_room]: +body.room_id,\n });\n if (!partRow)\n return {\n json: {\n error: \"Not participating\",\n },\n };\n }\n else {\n // check we have and write access\n const canRead = role <= table.min_role_read;\n if (!canRead)\n return {\n json: {\n error: \"Not participating\",\n },\n };\n }\n const formview = await view_1.default.findOne({ name: msgform });\n if (!formview)\n throw new InvalidConfiguration(\"Message form view does not exist\");\n const { columns, layout, fixed } = formview.configuration;\n const msgtable = table_1.default.findOne({ name: msgtable_name });\n const formObj = await (0, viewable_fields_1.getForm)(msgtable, viewname, columns, layout, null, req);\n formObj.validate(req.body || {});\n if (!formObj.hasErrors) {\n const use_fixed = await (0, viewable_fields_1.fill_presets)(msgtable, req, fixed);\n const row = {\n ...formObj.values,\n ...use_fixed,\n [msgkey_to_room]: body.room_id,\n [msgsender_field]: req.user.id,\n };\n const msgid = await msgtable.tryInsertRow(row, req.user);\n if (participant_maxread_field && partRow) {\n const [part_table_name1, part_key_to_room1, part_maxread_field] = participant_maxread_field.split(\".\");\n await parttable.updateRow({ [part_maxread_field]: msgid.success }, partRow.id);\n }\n return {\n json: { msgid },\n };\n }\n else {\n return {\n json: {\n error: formObj.errors,\n },\n };\n }\n },\n async ackReadQuery(participant_field, participant_maxread_field, body) {\n const [part_table_name, part_key_to_room, part_user_field] = participant_field.split(\".\");\n const [part_table_name1, part_key_to_room1, part_maxread_field] = participant_maxread_field.split(\".\");\n const parttable = table_1.default.findOne({ name: part_table_name });\n // check we participate\n const partRow = await parttable.getRow({\n [part_user_field]: req.user ? req.user.id : 0,\n [part_key_to_room]: +body.room_id,\n });\n if (!partRow)\n return {\n json: {\n error: \"Not participating\",\n },\n };\n await parttable.updateRow({ [part_maxread_field]: body.id }, partRow.id);\n return {\n json: {\n success: \"ok\",\n },\n };\n },\n async fetchOlderMsgQuery(participant_field, body) {\n const [part_table_name, part_key_to_room, part_user_field] = participant_field.split(\".\");\n const parttable = table_1.default.findOne({ name: part_table_name });\n // check we participate\n return await parttable.getRow({\n [part_user_field]: req.user ? req.user.id : 0,\n [part_key_to_room]: +body.room_id,\n });\n },\n async optionsQuery(reftable_name, type, attributes, where) {\n const rows = await db.select(reftable_name, type === \"File\" ? attributes.select_file_where : where);\n return rows;\n },\n }),\n connectedObjects: async (configuration) => {\n const result = extractFromLayout(configuration.layout);\n if (configuration.msgview) {\n const view = view_1.default.findOne({ name: configuration.msgview });\n if (view)\n (result.embeddedViews = result.embeddedViews || []).push(view);\n }\n if (configuration.msgform) {\n const view = view_1.default.findOne({ name: configuration.msgform });\n if (view)\n (result.embeddedViews = result.embeddedViews || []).push(view);\n }\n return result;\n },\n};\n/*todo:\n\nfind_or_create_dm_room -dms only\n\n*/\n//# sourceMappingURL=room.js.map\n\n//# sourceURL=webpack://saltcorn/../saltcorn-data/dist/base-plugin/viewtemplates/room.js?\n}");
127
-
128
- /***/ },
129
-
130
- /***/ "../saltcorn-data/dist/base-plugin/viewtemplates/show.js"
131
- /*!***************************************************************!*\
132
- !*** ../saltcorn-data/dist/base-plugin/viewtemplates/show.js ***!
133
- \***************************************************************/
134
- (module, __unused_webpack_exports, __webpack_require__) {
135
-
136
- "use strict";
137
- eval("{\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nconst user_1 = __importDefault(__webpack_require__(/*! ../../models/user */ \"../saltcorn-data/dist/models/user.js\"));\nconst field_1 = __importDefault(__webpack_require__(/*! ../../models/field */ \"../saltcorn-data/dist/models/field.js\"));\nconst view_1 = __importDefault(__webpack_require__(/*! ../../models/view */ \"../saltcorn-data/dist/models/view.js\"));\nconst table_1 = __importDefault(__webpack_require__(/*! ../../models/table */ \"../saltcorn-data/dist/models/table.js\"));\nconst page_1 = __importDefault(__webpack_require__(/*! ../../models/page */ \"../saltcorn-data/dist/models/page.js\"));\nconst crash_1 = __importDefault(__webpack_require__(/*! ../../models/crash */ \"../saltcorn-data/dist/models/crash.js\"));\nconst workflow_1 = __importDefault(__webpack_require__(/*! ../../models/workflow */ \"../saltcorn-data/dist/models/workflow.js\"));\nconst trigger_1 = __importDefault(__webpack_require__(/*! ../../models/trigger */ \"../saltcorn-data/dist/models/trigger.js\"));\nconst file_1 = __importDefault(__webpack_require__(/*! ../../models/file */ \"../saltcorn-data/dist/models/file.js\"));\nconst PageGroup = __webpack_require__(/*! ../../models/page_group */ \"../saltcorn-data/dist/models/page_group.js\");\nconst { Relation } = __webpack_require__(/*! @saltcorn/common-code */ \"../common-code/dist/index.js\");\nconst { getState } = __webpack_require__(/*! ../../db/state */ \"../saltcorn-data/dist/db/state.js\");\nconst { eachView, traverse, getStringsForI18n, translateLayout, splitLayoutContainerFields, findLayoutBranchWith, } = __webpack_require__(/*! ../../models/layout */ \"../saltcorn-data/dist/models/layout.js\");\nconst { check_view_columns } = __webpack_require__(/*! ../../plugin-testing */ \"../saltcorn-data/dist/mobile-mocks/saltcorn/plugin-testing.js\");\nconst { div, text, span, a, text_attr, i, button, script, domReady, } = __webpack_require__(/*! @saltcorn/markup/tags */ \"../saltcorn-markup/dist/tags.js\");\nconst renderLayout = __webpack_require__(/*! @saltcorn/markup/layout */ \"../saltcorn-markup/dist/layout.js\");\nconst plugin_helper_1 = __webpack_require__(/*! ../../plugin-helper */ \"../saltcorn-data/dist/plugin-helper.js\");\nconst viewable_fields_1 = __webpack_require__(/*! ../../viewable_fields */ \"../saltcorn-data/dist/viewable_fields.js\");\nconst db = __webpack_require__(/*! ../../db */ \"../saltcorn-data/dist/db/index.js\");\nconst { asyncMap, structuredClone, InvalidConfiguration, mergeIntoWhere, isWeb, hashState, getSafeBaseUrl, dollarizeObject, getSessionId, interpolate, validSqlId, renderServerSide, } = __webpack_require__(/*! ../../utils */ \"../saltcorn-data/dist/utils.js\");\nconst { traverseSync } = __webpack_require__(/*! ../../models/layout */ \"../saltcorn-data/dist/models/layout.js\");\nconst { get_expression_function, eval_expression, freeVariables, freeVariablesInInterpolation, add_free_variables_to_aggregations, } = __webpack_require__(/*! ../../models/expression */ \"../saltcorn-data/dist/models/expression.js\");\nconst { get_base_url } = __webpack_require__(/*! ../../models/config */ \"../saltcorn-data/dist/models/config.js\");\nconst Library = __webpack_require__(/*! ../../models/library */ \"../saltcorn-data/dist/models/library.js\");\nconst { extractFromLayout } = __webpack_require__(/*! ../../diagram/node_extract_utils */ \"../saltcorn-data/dist/diagram/node_extract_utils.js\");\nconst _ = __webpack_require__(/*! underscore */ \"../../node_modules/underscore/modules/index-all.js\");\nconst configuration_workflow = (req) => new workflow_1.default({\n steps: [\n {\n name: req.__(\"Layout\"),\n builder: async (context) => {\n const table = table_1.default.findOne(context.table_id || context.exttable_name);\n const fields = table.getFields();\n const boolfields = fields.filter((f) => f.type && f.type.name === \"Bool\");\n const stateActions = Object.entries(getState().actions).filter(([k, v]) => !v.disableInBuilder && !v.disableIf?.());\n const builtInActions = [\n \"Delete\",\n \"GoBack\",\n ...boolfields.map((f) => `Toggle ${f.name}`),\n ];\n const triggerActions = trigger_1.default.trigger_actions({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n });\n const actions = trigger_1.default.action_options({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n forBuilder: true,\n builtInLabel: \"Show Actions\",\n builtIns: builtInActions,\n });\n for (const field of fields) {\n if (field.type === \"Key\") {\n field.reftable = table_1.default.findOne({\n name: field.reftable_name,\n });\n if (field.reftable)\n await field.reftable.getFields();\n }\n }\n const actionConfigForms = {\n Delete: [\n {\n name: \"after_delete_action\",\n label: req.__(\"After delete\"),\n type: \"String\",\n required: true,\n attributes: {\n options: [\"Go to URL\", \"Reload page\"],\n },\n },\n {\n name: \"after_delete_url\",\n label: req.__(\"URL after delete\"),\n type: \"String\",\n showIf: { after_delete_action: \"Go to URL\" },\n },\n ],\n };\n const actionDescriptions = {};\n for (const [name, action] of stateActions) {\n if (action.configFields) {\n actionConfigForms[name] = await (0, plugin_helper_1.getActionConfigFields)(action, table, { mode: \"show\", req });\n }\n if (action.description)\n actionDescriptions[name] = action.description;\n }\n const workflowActions = trigger_1.default.trigger_actions({\n tableTriggers: table.id,\n apiNeverTriggers: true,\n onlyWorkflows: true,\n });\n for (const name of workflowActions) {\n actionConfigForms[name] = [\n {\n name: \"initial_context\",\n label: \"Additional context\",\n type: \"String\",\n class: \"validate-expression\",\n },\n ];\n }\n //const fieldViewConfigForms = await calcfldViewConfig(fields, false);\n const { field_view_options, handlesTextStyle } = (0, plugin_helper_1.calcfldViewOptions)(fields, \"show\");\n if (table.name === \"users\") {\n fields.push(new field_1.default({\n name: \"verification_url\",\n label: \"Verification URL\",\n type: \"String\",\n }), new field_1.default({\n name: \"reset_password_url\",\n label: \"Reset Password URL\",\n type: \"String\",\n }));\n field_view_options.verification_url = [\"as_text\", \"as_link\"];\n field_view_options.reset_password_url = [\"as_text\", \"as_link\"];\n }\n const rel_field_view_options = await (0, plugin_helper_1.calcrelViewOptions)(table, \"show\");\n const roles = await user_1.default.get_roles();\n const { parent_field_list } = await table.get_parent_relations(true, true);\n const { child_field_list, child_relations } = await table.get_child_relations(true);\n var agg_field_opts = {};\n child_relations.forEach(({ table, key_field, through }) => {\n const aggKey = (through ? `${through.name}->` : \"\") +\n `${table.name}.${key_field.name}`;\n agg_field_opts[aggKey] = table.fields\n .filter((f) => !f.calculated || f.stored)\n .map((f) => ({\n name: f.name,\n label: f.label,\n ftype: f.type.name || f.type,\n table_name: table.name,\n table_id: table.id,\n }));\n });\n const agg_fieldview_options = {};\n Object.values(getState().types).forEach((t) => {\n agg_fieldview_options[t.name] = Object.entries(t.fieldviews)\n .filter(([k, v]) => !v.isEdit && !v.isFilter)\n .map(([k, v]) => k);\n });\n const pages = (await page_1.default.find({}, { cached: true })).map((p) => ({\n name: p.name,\n }));\n const groups = (await PageGroup.find({}, { cached: true })).map((g) => ({\n name: g.name,\n }));\n const images = await file_1.default.find({ mime_super: \"image\" });\n const library = (await Library.find({})).filter((l) => l.suitableFor(\"show\"));\n const myviewrow = view_1.default.findOne({ name: context.viewname });\n const { on_done_redirect, ...current_filter_state } = req.query;\n return {\n tableName: table.name,\n fields: fields.map((f) => f.toBuilder),\n images,\n actions,\n triggerActions,\n builtInActions,\n actionConfigForms,\n actionDescriptions,\n current_filter_state,\n //fieldViewConfigForms,\n field_view_options: {\n ...field_view_options,\n ...rel_field_view_options,\n },\n parent_field_list,\n child_field_list,\n agg_field_opts,\n agg_fieldview_options,\n min_role: (myviewrow || {}).min_role,\n roles,\n library,\n pages,\n page_groups: groups,\n allowMultiStepAction: true,\n handlesTextStyle,\n mode: \"show\",\n has_copilot_generate: !!getState().functions.copilot_generate_layout,\n ownership: !!table.ownership_field_id ||\n !!table.ownership_formula ||\n table.name === \"users\",\n };\n },\n },\n ],\n});\nconst get_state_fields = () => [\n {\n name: \"id\",\n type: \"Integer\",\n required: true,\n primary_key: true,\n },\n];\nconst initial_config = (0, plugin_helper_1.initial_config_all_fields)(false);\nconst run = async (table_id, viewname, { columns, layout, page_title, page_title_formula, }, state, extra, { showQuery }) => {\n if (!columns || !layout)\n return \"View not yet built\";\n const tbl = table_1.default.findOne(table_id);\n const fields = await tbl.getFields();\n if (tbl.name === \"users\") {\n fields.push(new field_1.default({\n name: \"verification_token\",\n label: \"Verification Token\",\n type: \"String\",\n }), new field_1.default({\n name: \"reset_password_token\",\n label: \"Reset Password Token\",\n type: \"String\",\n }));\n }\n const { rows, message } = await showQuery(state);\n if (message)\n return extra.req.__(message);\n if (rows.length > 1)\n rows.sort((a, b) => {\n let diff = 0;\n Object.keys(state).forEach((key) => {\n if (a[key] && b[key]) {\n if (typeof a[key] === \"string\" && typeof b[key] === \"string\") {\n diff += a[key].length - b[key].length;\n }\n }\n });\n return diff;\n });\n if (rows.length == 0)\n return extra.req.__(\"No row selected\");\n if (tbl.name === \"users\") {\n const base = get_base_url(extra.req);\n fields.push(new field_1.default({\n name: \"verification_url\",\n label: \"Verification URL\",\n type: \"String\",\n }), new field_1.default({\n name: \"reset_password_url\",\n label: \"Reset Password URL\",\n type: \"String\",\n }));\n for (const row of rows) {\n row.verification_url = `${base}auth/verify?token=${row.verification_token}&email=${encodeURIComponent(row.email)}`;\n row.reset_password_url = `${base}auth/reset?token=${row.reset_password_token}&email=${encodeURIComponent(row.email)}`;\n }\n }\n await set_load_actions_join_fieldviews({\n table: tbl,\n layout,\n fields,\n req: extra.req,\n res: extra.res,\n row: rows[0],\n isPreview: extra.isPreview,\n });\n const rendered = (await renderRows(tbl, viewname, { columns, layout }, extra, [rows[0]], state))[0];\n //legacy\n let page_title_preamble = \"\";\n if (page_title) {\n let the_title = page_title;\n if (page_title_formula) {\n const f = get_expression_function(page_title, fields);\n the_title = f(rows[0]);\n }\n page_title_preamble = `<!--SCPT:${text_attr(the_title)}-->`;\n }\n if (!extra.req.generate_email)\n return page_title_preamble + rendered;\n else {\n return rendered;\n }\n};\nconst set_load_actions_join_fieldviews = async ({ table, layout, fields, req, res, row, isPreview, }) => {\n await traverse(layout, {\n join_field: async (segment) => {\n const { join_field, fieldview } = segment;\n if (!fieldview)\n return;\n const field = table.getField(join_field);\n if (field && field.type === \"File\")\n segment.field_type = \"File\";\n else if (field?.type.name && field?.type?.fieldviews[fieldview]) {\n segment.field_type = field.type.name;\n segment.target_field_attributes = field.attributes;\n }\n },\n async action(segment) {\n if (segment.action_style === \"on_page_load\") {\n segment.type = \"blank\";\n segment.style = {};\n if (segment.minRole && segment.minRole != 100) {\n const minRole = +segment.minRole;\n const userRole = req?.user?.role_id || 100;\n if (minRole < userRole)\n return;\n }\n //run action\n if (isPreview)\n return;\n const actionResult = await (0, plugin_helper_1.run_action_column)({\n col: { ...segment },\n referrer: req?.get?.(\"Referrer\"),\n req,\n res,\n table,\n row,\n });\n segment.type = \"blank\";\n segment.style = {};\n if (actionResult)\n segment.contents = script(domReady(`common_done(${JSON.stringify(actionResult)})`));\n else\n segment.contents = \"\";\n }\n },\n });\n};\nconst renderRows = async (table, viewname, { columns, layout }, extra, rows, state) => {\n if (!columns || !layout)\n return \"View not yet built\";\n const fields = table.getFields();\n const role = extra.req.user ? extra.req.user.role_id : 100;\n var views = {};\n const getView = async (name, relation) => {\n if (views[name])\n return views[name];\n const view_select = (0, viewable_fields_1.parse_view_select)(name, relation);\n const view = view_1.default.findOne({ name: view_select.viewname });\n if (!view)\n return false;\n if (view.table_id === table.id)\n view.table = table;\n else\n view.table = table_1.default.findOne({ id: view.table_id });\n view.view_select = view_select;\n views[name] = view;\n return view;\n };\n await set_load_actions_join_fieldviews({\n table,\n layout,\n fields,\n req: extra.req,\n res: extra.res,\n });\n const owner_field = await table.owner_fieldname();\n const subviewExtra = { ...extra };\n if (extra.req?.generate_email) {\n // no mjml markup for for nested subviews, only for the top view\n subviewExtra.req = { ...extra.req, isSubView: true };\n }\n return await asyncMap(rows, async (row) => {\n const myLayout = rows.length > 1 ? structuredClone(layout) : layout;\n traverseSync(myLayout, {\n container(segment) {\n if (segment.showIfFormula) {\n const f = get_expression_function(segment.showIfFormula, fields);\n if (!f({ ...dollarizeObject(state || {}), ...row }, extra.req.user)) {\n segment.contents = \"\";\n segment.type = \"blank\";\n delete segment.showIfFormula; //avoid double eval\n }\n }\n },\n });\n await eachView(myLayout, async (segment, inLazy) => {\n // do all the parsing with data here? make a factory\n const view = await getView(segment.view, segment.relation);\n if (!view)\n throw new InvalidConfiguration(`View ${viewname} incorrectly configured: cannot find view ${segment.view}`);\n if (view.viewtemplateObj.renderRows &&\n view.view_select.type === \"Own\") {\n segment.contents = (await view.viewtemplateObj.renderRows(view.table, view.name, view.configuration, subviewExtra, [row], state))[0];\n }\n else {\n let state1 = {};\n const pk_name = table.pk_name;\n const get_row_val = (k) => {\n //handle expanded joinfields\n if (row[k] === null)\n return null;\n if (row[k]?.id === null)\n return null;\n return row[k]?.id || row[k];\n };\n const get_user_id = () => (extra.req.user ? extra.req.user.id : 0);\n if (view.view_select.type === \"RelationPath\" && view.table_id) {\n const targetTbl = table_1.default.findOne({ id: view.table_id });\n const relation = new Relation(segment.relation, targetTbl.name, (0, plugin_helper_1.displayType)(await view.get_state_fields()));\n state1 = (0, plugin_helper_1.pathToState)(relation, relation.isFixedRelation() ? get_user_id : get_row_val);\n }\n else {\n switch (view.view_select.type) {\n case \"Own\":\n state1 = { [pk_name]: get_row_val(pk_name) };\n break;\n case \"Independent\":\n state1 = {};\n break;\n case \"ChildList\":\n case \"OneToOneShow\":\n state1 = {\n [view.view_select.through\n ? `${view.view_select.throughTable}.${view.view_select.through}.${view.view_select.table_name}.${view.view_select.field_name}`\n : view.view_select.field_name]: get_row_val(pk_name),\n };\n break;\n case \"ParentShow\":\n //todo set by pk name of parent tablr\n state1 = {\n id: get_row_val(view.view_select.field_name),\n };\n break;\n }\n }\n const extra_state = segment.extra_state_fml\n ? eval_expression(segment.extra_state_fml, {\n ...dollarizeObject(state),\n session_id: getSessionId(extra.req),\n ...row,\n }, extra.req.user, `Extra state formula for view ${view.name}`)\n : {};\n const { id, ...outerState } = state;\n //console.log(segment);\n if (segment.state === \"local\") {\n const state2 = { ...state1, ...extra_state };\n const qs = (0, plugin_helper_1.stateToQueryString)(state2, true);\n if (view.name === viewname &&\n JSON.stringify(state) === JSON.stringify(state2))\n throw new InvalidConfiguration(`View ${view.name} embeds itself with same state; inifinite loop detected`);\n segment.contents = div({\n class: \"d-inline\",\n \"data-sc-embed-viewname\": view.name,\n \"data-sc-local-state\": `/view/${view.name}${qs}`,\n \"data-sc-view-source\": `/view/${view.name}${qs}`,\n }, inLazy\n ? \"\"\n : view.renderLocally()\n ? await view.run(state2, subviewExtra, view.isRemoteTable())\n : await renderServerSide(view.name, state2));\n }\n else {\n const state2 = { ...outerState, ...state1, ...extra_state };\n const qs = (0, plugin_helper_1.stateToQueryString)(state2, true);\n if (view.name === viewname &&\n JSON.stringify(state) === JSON.stringify(state2))\n throw new InvalidConfiguration(`View ${view.name} embeds itself with same state; inifinite loop detected`);\n segment.contents = div({\n class: \"d-inline\",\n \"data-sc-embed-viewname\": view.name,\n \"data-sc-view-source\": `/view/${view.name}${qs}`,\n }, inLazy\n ? \"\"\n : view.renderLocally()\n ? await view.run(state2, subviewExtra, view.isRemoteTable())\n : await renderServerSide(view.name, state2));\n }\n }\n }, state);\n await page_1.default.renderEachEmbeddedPageInLayout(myLayout, state, extra);\n const user_id = extra.req.user ? extra.req.user.id : null;\n const is_owner = table.ownership_formula && user_id && role > table.min_role_read\n ? await table.is_owner(extra.req.user, row)\n : owner_field && user_id && row[owner_field] === user_id;\n return render(row, fields, myLayout, viewname, table, role, extra.req, is_owner, state, extra);\n });\n};\nconst runMany = async (table_id, viewname, { columns, layout }, state, extra, { runManyQuery }) => {\n const tbl = table_1.default.findOne({ id: table_id });\n const rows = await runManyQuery(state, {\n where: extra.where,\n joinFieldsExtra: extra.joinFields,\n limit: extra.limit,\n offset: extra.offset,\n orderBy: extra.orderBy,\n orderDesc: extra.orderDesc,\n });\n const rendered = await renderRows(tbl, viewname, { columns, layout }, extra, rows, state);\n return rendered.map((html, ix) => ({ html, row: rows[ix] }));\n};\nconst render = (row, fields, layout0, viewname, table, role, req, is_owner, state, extra) => {\n const locale = req.getLocale();\n const layout = structuredClone(layout0);\n translateLayout(layout, locale);\n traverseSync(layout, (0, viewable_fields_1.standardLayoutRowVisitor)(viewname, state, table, row, req));\n return renderLayout({\n blockDispatch: (0, viewable_fields_1.standardBlockDispatch)(viewname, state, table, extra, row),\n layout,\n role,\n is_owner,\n req,\n hints: getState().getLayout(req.user).hints || {},\n });\n};\nconst run_action = async (table_id, viewname, { columns, layout }, body, { req, res }, { actionQuery }) => {\n const result = await actionQuery();\n if (result.json.error) {\n crash_1.default.create({ message: result.json.error, stack: \"\" }, req);\n }\n return result;\n};\nconst createBasicView = async ({ table, viewname, template_view, template_table, all_views_created, }) => {\n if (!template_view) {\n const configuration = await (0, plugin_helper_1.initial_config_all_fields)(false)({\n table_id: table.id,\n });\n return configuration;\n }\n const { inner, outer } = splitLayoutContainerFields(template_view.configuration.layout);\n const templateFieldTypes = {}, templateFieldLabels = {};\n for (const field of template_table.fields) {\n templateFieldTypes[field.name] = field.type_name;\n templateFieldLabels[field.name] = field.label;\n }\n const defaultBranch = findLayoutBranchWith(inner.above || inner.contents.above, (s) => {\n return s.type === \"field\";\n });\n const inners = [], columns = [];\n for (const field of table.fields) {\n if (field.primary_key)\n continue;\n const branch = findLayoutBranchWith(inner.above || inner.contents.above, (s) => {\n return (s.type === \"field\" &&\n templateFieldTypes[s.field_name] === field.type_name);\n }) || defaultBranch;\n let oldField;\n traverseSync(branch, {\n field(s) {\n oldField = template_table.getField(s.field_name);\n },\n });\n const newBranch = structuredClone(branch);\n let newCol = {};\n traverseSync(newBranch, {\n field(s) {\n s.field_name = field.name;\n newCol = {\n type: \"Field\",\n fieldview: s.fieldview,\n field_name: field.name,\n };\n },\n blank(s) {\n if (s.contents === oldField.label)\n s.contents = field.label;\n },\n });\n inners.push(newBranch);\n columns.push(newCol);\n }\n const cfg = {\n layout: outer({ above: inners }),\n columns,\n };\n return cfg;\n};\nmodule.exports = {\n name: \"Show\",\n description: \"Show a single row, with flexible layout\",\n get_state_fields,\n configuration_workflow,\n run,\n runMany,\n renderRows,\n initial_config,\n createBasicView,\n routes: { run_action },\n getStringsForI18n({ layout }) {\n return getStringsForI18n(layout);\n },\n async interpolate_title_string(table_id, title, state) {\n const tbl = table_1.default.findOne(table_id);\n if (state?.[tbl.pk_name]) {\n const freeVars = freeVariablesInInterpolation(title);\n const joinFields = {};\n const aggregations = {};\n (0, plugin_helper_1.add_free_variables_to_joinfields)(freeVars, joinFields, tbl.fields);\n add_free_variables_to_aggregations(freeVars, aggregations, tbl);\n const row = await tbl.getJoinedRow({\n where: { [tbl.pk_name]: state[tbl.pk_name] },\n joinFields,\n aggregations,\n });\n return interpolate(title, row, null, \"Show view title string\");\n }\n else\n return title;\n },\n queries: ({ table_id, exttable_name, name, // viewname\n configuration: { columns, layout }, req, res, }) => ({\n async showQuery(state) {\n const tbl = table_1.default.findOne(table_id || exttable_name);\n const fields = tbl.getFields();\n if (tbl.name === \"users\") {\n fields.push(new field_1.default({\n name: \"verification_token\",\n label: \"Verification Token\",\n type: \"String\",\n }), new field_1.default({\n name: \"reset_password_token\",\n label: \"Reset Password Token\",\n type: \"String\",\n }));\n }\n const { joinFields, aggregations } = (0, plugin_helper_1.picked_fields_to_query)(columns, fields, layout, req, tbl);\n const unhashed_reset_password_token = state._unhashed_reset_password_token;\n (0, plugin_helper_1.readState)(state, fields);\n const qstate = (0, plugin_helper_1.stateFieldsToWhere)({\n fields,\n state,\n approximate: true,\n table: tbl,\n prefix: \"a.\",\n });\n if (Object.keys(qstate).length === 0)\n return {\n rows: null,\n message: \"No row selected\",\n };\n if (tbl.ownership_formula) {\n const freeVars = freeVariables(tbl.ownership_formula);\n (0, plugin_helper_1.add_free_variables_to_joinfields)(freeVars, joinFields, fields);\n }\n const rows = await tbl.getJoinedRows({\n where: qstate,\n joinFields,\n aggregations,\n limit: 5,\n starFields: tbl.name === \"users\",\n forPublic: !req.user,\n forUser: req.user,\n });\n if (unhashed_reset_password_token && tbl.name === \"users\")\n rows.forEach((r) => {\n r.reset_password_token = unhashed_reset_password_token;\n });\n return {\n rows,\n message: null,\n };\n },\n async runManyQuery(state, { where, limit, offset, joinFieldsExtra, orderBy, orderDesc }) {\n const tbl = table_1.default.findOne({ id: table_id });\n const fields = await tbl.getFields();\n (0, plugin_helper_1.readState)(state, fields);\n const { joinFields, aggregations } = (0, plugin_helper_1.picked_fields_to_query)(columns, fields, layout, req, tbl);\n Object.assign(joinFields, joinFieldsExtra || {});\n const stateHash = hashState(state, name);\n const qstate = (0, plugin_helper_1.stateFieldsToWhere)({\n fields,\n state,\n table: tbl,\n prefix: \"a.\",\n });\n const q = (0, plugin_helper_1.stateFieldsToQuery)({ state, fields, stateHash });\n if (where)\n mergeIntoWhere(qstate, where);\n const role = req && req.user ? req.user.role_id : 100;\n if (tbl.ownership_field_id && role > tbl.min_role_read && req) {\n const owner_field = fields.find((f) => f.id === tbl.ownership_field_id);\n if (qstate[owner_field.name])\n qstate[owner_field.name] = [\n qstate[owner_field.name],\n req.user ? req.user.id : -1,\n ];\n else\n qstate[owner_field.name] = req.user ? req.user.id : -1;\n }\n if (tbl.ownership_formula && role > tbl.min_role_read) {\n const freeVars = freeVariables(tbl.ownership_formula);\n (0, plugin_helper_1.add_free_variables_to_joinfields)(freeVars, joinFields, fields);\n }\n let rows = await tbl.getJoinedRows({\n where: qstate,\n joinFields,\n aggregations,\n ...(limit && { limit: limit }),\n ...(offset && { offset: offset }),\n ...(orderBy && { orderBy: orderBy }),\n ...(orderDesc && { orderDesc: orderDesc }),\n ...q,\n forPublic: !req.user,\n forUser: req.user,\n });\n if (tbl.ownership_formula && role > tbl.min_role_read && req) {\n rows = rows.filter((row) => tbl.is_owner(req.user, row));\n }\n return rows;\n },\n async actionQuery() {\n return await db.withTransaction(async () => {\n const body = req.body || {};\n const col = columns.find((c) => c.type === \"Action\" && c.rndid === body.rndid && body.rndid);\n const table = table_1.default.findOne({ id: table_id });\n let row;\n if (table.ownership_formula) {\n const freeVars = freeVariables(table.ownership_formula);\n const joinFields = {};\n (0, plugin_helper_1.add_free_variables_to_joinfields)(freeVars, joinFields, table.fields);\n row = await table.getJoinedRow({\n where: { [table.pk_name]: body[table.pk_name] },\n joinFields,\n forUser: req.user || { role_id: 100 },\n forPublic: !req.user,\n });\n }\n else\n row = await table.getRow({ [table.pk_name]: body[table.pk_name] }, { forUser: req.user, forPublic: !req.user });\n if (body.click_action) {\n let container;\n traverseSync(layout, {\n container(segment) {\n if (segment.click_action === body.click_action)\n container = segment;\n },\n });\n if (!container)\n return { json: { error: \"Action not found\" } };\n const trigger = trigger_1.default.findOne({ name: body.click_action });\n if (!trigger)\n throw new Error(`View ${name}: Container click action ${body.click_action} not found`);\n const result = await trigger.runWithoutRow({\n table,\n Table: table_1.default,\n req,\n row,\n user: req.user,\n referrer: req?.get?.(\"Referrer\"),\n });\n return { json: { success: \"ok\", ...(result || {}) } };\n }\n const result = await (0, plugin_helper_1.run_action_column)({\n col,\n req,\n table,\n row,\n res,\n referrer: req?.get?.(\"Referrer\"),\n });\n return { json: { success: \"ok\", ...(result || {}) } };\n }, (e) => {\n return { json: { error: e.message || e } };\n });\n },\n }),\n configCheck: async (view) => {\n return await check_view_columns(view, view.configuration.columns);\n },\n connectedObjects: async (configuration) => {\n return extractFromLayout(configuration.layout);\n },\n};\n//# sourceMappingURL=show.js.map\n\n//# sourceURL=webpack://saltcorn/../saltcorn-data/dist/base-plugin/viewtemplates/show.js?\n}");
138
-
139
- /***/ },
140
-
141
- /***/ "../saltcorn-data/dist/base-plugin/viewtemplates/workflow-room.js"
142
- /*!************************************************************************!*\
143
- !*** ../saltcorn-data/dist/base-plugin/viewtemplates/workflow-room.js ***!
144
- \************************************************************************/
145
- (module, __unused_webpack_exports, __webpack_require__) {
146
-
147
- "use strict";
148
- eval("{\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\n/**\n * @category saltcorn-data\n * @module base-plugin/viewtemplates/room\n * @subcategory base-plugin\n */\nconst field_1 = __importDefault(__webpack_require__(/*! ../../models/field */ \"../saltcorn-data/dist/models/field.js\"));\nconst table_1 = __importDefault(__webpack_require__(/*! ../../models/table */ \"../saltcorn-data/dist/models/table.js\"));\nconst form_1 = __importDefault(__webpack_require__(/*! ../../models/form */ \"../saltcorn-data/dist/models/form.js\"));\nconst view_1 = __importDefault(__webpack_require__(/*! ../../models/view */ \"../saltcorn-data/dist/models/view.js\"));\nconst trigger_1 = __importDefault(__webpack_require__(/*! ../../models/trigger */ \"../saltcorn-data/dist/models/trigger.js\"));\nconst workflow_1 = __importDefault(__webpack_require__(/*! ../../models/workflow */ \"../saltcorn-data/dist/models/workflow.js\"));\nconst WorkflowRun = __webpack_require__(/*! ../../models/workflow_run */ \"../saltcorn-data/dist/models/workflow_run.js\");\nconst WorkflowStep = __webpack_require__(/*! ../../models/workflow_step */ \"../saltcorn-data/dist/models/workflow_step.js\");\nconst { text, div, a, h4, hr, button, form, input, pre, i, script, domReady, } = __webpack_require__(/*! @saltcorn/markup/tags */ \"../saltcorn-markup/dist/tags.js\");\nconst { pagination } = __webpack_require__(/*! @saltcorn/markup/helpers */ \"../saltcorn-markup/dist/helpers.js\");\nconst { renderForm, tabs, link } = __webpack_require__(/*! @saltcorn/markup */ \"../saltcorn-markup/dist/index.js\");\nconst { mkTable } = __webpack_require__(/*! @saltcorn/markup */ \"../saltcorn-markup/dist/index.js\");\nconst { getState } = __webpack_require__(/*! ../../db/state */ \"../saltcorn-data/dist/db/state.js\");\nconst db = __webpack_require__(/*! ../../db */ \"../saltcorn-data/dist/db/index.js\");\nconst viewable_fields_1 = __webpack_require__(/*! ../../viewable_fields */ \"../saltcorn-data/dist/viewable_fields.js\");\nconst { extractFromLayout } = __webpack_require__(/*! ../../diagram/node_extract_utils */ \"../saltcorn-data/dist/diagram/node_extract_utils.js\");\nconst WorkflowTrace = __webpack_require__(/*! ../../models/workflow_trace */ \"../saltcorn-data/dist/models/workflow_trace.js\");\nconst { localeDateTime } = __webpack_require__(/*! @saltcorn/markup/index */ \"../saltcorn-markup/dist/index.js\");\nconst MarkdownIt = __webpack_require__(/*! markdown-it */ \"../../node_modules/markdown-it/dist/index.cjs.js\"), md = new MarkdownIt();\nconst configuration_workflow = (req) => new workflow_1.default({\n steps: [\n {\n name: req.__(\"Workflow\"),\n form: async (context) => {\n const wfs = await trigger_1.default.find({ action: \"Workflow\" });\n return new form_1.default({\n fields: [\n {\n name: \"workflow\",\n label: \"Workflow\",\n type: \"String\",\n required: true,\n attributes: { options: wfs.map((wf) => wf.name) },\n sublabel: req.__(\"The workflow the user will be interacting with.\") +\n \" \" +\n a({\n \"data-dyn-href\": `\\`/actions/configure/\\${workflow}\\``,\n target: \"_blank\",\n }, req.__(\"Configure\")),\n },\n {\n name: \"prev_runs\",\n label: \"Show previous runs\",\n type: \"Bool\",\n },\n ],\n });\n },\n },\n ],\n});\nconst get_state_fields = () => [];\nconst getHtmlFromTraces = async ({ run, req, viewname, traces }) => {\n let items = [];\n for (let ix = 0; ix < traces.length - 1; ix++) {\n const trace = traces[ix];\n const fakeRun = new WorkflowRun(run);\n fakeRun.wait_info = trace.wait_info;\n fakeRun.context = trace.context;\n if (trace.status === \"Waiting\" && trace.wait_info.form) {\n fakeRun.context = traces[ix + 1] ? traces[ix + 1].context : run.context;\n }\n fakeRun.current_step = [trace.step_name_run];\n fakeRun.status = trace.status;\n fakeRun.error = trace.error;\n const myItems = await getHtmlFromRun({\n req,\n viewname,\n run: fakeRun,\n noInteract: true,\n });\n items.push(...myItems);\n }\n return items;\n};\nconst getHtmlFromRun = async ({ run, req, viewname, noInteract }) => {\n let items = [];\n let submit_ajax = false;\n const checkContext = async (key, alertType) => {\n if (run.context[key]) {\n items.push(div({ class: `alert alert-${alertType}`, role: \"alert\" }, run.context[key]));\n if (!noInteract) {\n delete run.context[key];\n await run.update({ context: run.context });\n submit_ajax = true;\n }\n }\n };\n await checkContext(\"notify\", \"info\");\n await checkContext(\"notify_success\", \"success\");\n await checkContext(\"error\", \"danger\");\n if (run.status === \"Error\") {\n items.push(div({ class: `alert alert-danger`, role: \"alert\" }, run.error));\n }\n // waiting look for form or output\n if (run.wait_info?.output) {\n let out = run.wait_info.output;\n if (run.wait_info.markdown)\n out = md.render(out);\n items.push(div(out));\n if (!noInteract)\n submit_ajax = true;\n }\n if (run.wait_info?.form) {\n const step = await WorkflowStep.findOne({\n trigger_id: run.trigger_id,\n name: run.current_step_name,\n });\n const form = await getWorkflowStepUserForm({ step, run, viewname, req });\n if (noInteract) {\n form.noSubmitButton = true;\n form.fields = form.fields.map((f) => {\n const nf = new field_1.default(f);\n nf.disabled = true;\n form.values[f.name] = run.context[f.name];\n return nf;\n });\n }\n items.push(renderForm(form, req.csrfToken()));\n }\n else if (submit_ajax && !noInteract) {\n items.push(script(domReady(`ajax_post_json(\"/view/${viewname}/submit_form\", {run_id: ${run.id}});`)));\n }\n return items;\n};\nconst getWorkflowStepUserForm = async ({ step, run, viewname, req }) => {\n if (step.action_name === \"EditViewForm\") {\n const view = view_1.default.findOne({ name: step.configuration.edit_view });\n const table = table_1.default.findOne({ id: view.table_id });\n const form = await (0, viewable_fields_1.getForm)(table, view.name, view.configuration.columns, view.configuration.layout, null, req);\n form.action = `/view/${viewname}/submit_form`;\n form.onSubmit = `$(this).closest('form').find('button').hide();$('#wfroom-spin-${run.id}').show();setTimeout(()=>$(this).closest('form').find('input,select,textarea').prop('disabled', true),100);`;\n if (run.context[step.configuration.response_variable])\n Object.assign(form.values, run.context[step.configuration.response_variable]);\n form.hidden(\"run_id\");\n form.xhrSubmit = true;\n await form.fill_fkey_options(false, undefined, req?.user);\n form.values.run_id = run.id;\n return form;\n }\n const form = new form_1.default({\n action: `/view/${viewname}/submit_form`,\n xhrSubmit: true,\n onSubmit: `$(this).closest('form').find('button').hide();$('#wfroom-spin-${run.id}').show();setTimeout(()=>$(this).closest('form').find('input,select,textarea').prop('disabled', true),100);`,\n submitLabel: run.wait_info.output ? req.__(\"OK\") : req.__(\"Submit\"),\n blurb: run.wait_info.output || step.configuration?.form_header || \"\",\n formStyle: \"vert\",\n ...(await run.userFormFields(step, req?.user)),\n });\n form.hidden(\"run_id\");\n form.values.run_id = run.id;\n return form;\n};\nconst run = async (table_id, viewname, { workflow, prev_runs }, state, { req, res, isPreview }, { getRowQuery, updateQuery, optionsQuery }) => {\n const trigger = await trigger_1.default.findOne({ name: workflow });\n let wfRun;\n let prevItems = [];\n if (state.id || isPreview) {\n wfRun = isPreview\n ? await WorkflowRun.findOne({ trigger_id: trigger.id }, { limit: 1, orderBy: \"id\", orderDesc: true })\n : await WorkflowRun.findOne({ id: state.id });\n if (wfRun) {\n if (wfRun.started_by != req.user?.id && req.user?.role_id != 1)\n return \"Not authorized\";\n if (trigger.configuration.save_traces) {\n const traces = await WorkflowTrace.find({ run_id: wfRun.id }, { orderBy: \"step_started_at\" });\n prevItems = await getHtmlFromTraces({ run: wfRun, req, viewname, traces });\n }\n }\n else {\n if (!isPreview)\n return \"Run not found\";\n else\n return \"No runs yet\";\n }\n }\n else\n wfRun = await WorkflowRun.create({\n trigger_id: trigger.id,\n context: {},\n started_by: req.user?.id,\n });\n await wfRun.run({\n user: req.user,\n noNotifications: true,\n trace: trigger.configuration?.save_traces,\n });\n const items = await getHtmlFromRun({ run: wfRun, req, viewname });\n //look for error status\n if (prev_runs) {\n const locale = req.getLocale();\n const runs = await WorkflowRun.find({ trigger_id: trigger.id }, { limit: 10, orderBy: \"started_at\", orderDesc: true });\n return div({ class: \"row\" }, div({ class: \"col-2 col-md-3 col-sm-4\" }, req.__(\"Previous runs\"), runs.map((run1) => div({ class: \"d-flex prevwfroomrun\" }, a({\n href: `javascript:void(0)`,\n onclick: `reload_embedded_view('${viewname}', 'id=${run1.id}')`,\n class: [\"text-nowrap\", run1.id == wfRun.id && \"fw-bold\"],\n }, localeDateTime(run1.started_at, {}, locale)), i({\n class: \"far fa-trash-alt ms-2\",\n onclick: `delprevwfroomrun('${viewname}', event, ${run1.id})`,\n })))), div({ class: \"col-10 col-md-9 col-sm-8\" }, div({ id: `wfroom-${wfRun.id}` }, prevItems, items), div({ id: `wfroom-spin-${wfRun.id}`, style: { display: \"none\" } }, i({ class: \"fas fa-spinner fa-spin\" }))));\n }\n else\n return div(div({ id: `wfroom-${wfRun.id}` }, prevItems, items));\n};\nconst submit_form = async (table_id, viewname, { workflow }, body, { req }) => {\n const wfRun = await WorkflowRun.findOne({ id: body.run_id });\n const trigger = await trigger_1.default.findOne({ id: wfRun.trigger_id });\n const step = await WorkflowStep.findOne({\n trigger_id: trigger.id,\n name: wfRun.current_step_name,\n });\n const form = await getWorkflowStepUserForm({ step, run: wfRun, viewname, req });\n form.validate(req.body || {});\n await wfRun.provide_form_input(form.values, step.configuration.response_variable);\n await wfRun.run({\n user: req.user,\n noNotifications: true,\n trace: trigger.configuration?.save_traces,\n });\n const items = await getHtmlFromRun({ run: wfRun, req, viewname });\n return {\n json: {\n success: \"ok\",\n eval_js: `$('#wfroom-${wfRun.id}').append(${JSON.stringify(items.join(\"\"))});$('#wfroom-spin-${wfRun.id}')[0]?.scrollIntoView();$('#wfroom-spin-${wfRun.id}').hide()`,\n },\n };\n};\nconst delprevrun = async (table_id, viewname, config, body, { req, res }) => {\n const { run_id } = body;\n let wfRun = await WorkflowRun.findOne({\n id: +run_id,\n });\n if (wfRun && (req.user?.role_id === 1 || wfRun.started_by === req.user?.id))\n await wfRun.delete();\n return;\n};\nmodule.exports = {\n /** @type {string} */\n name: \"WorkflowRoom\",\n /** @type {string} */\n description: \"Chatbot interface for workflows\",\n configuration_workflow,\n run,\n tableless: true,\n get_state_fields,\n routes: { submit_form, delprevrun },\n /** @type {boolean} */\n noAutoTest: true,\n /** @returns {object[]} */\n getStringsForI18n() {\n return [];\n },\n queries: ({ table_id, viewname, configuration: { columns, default_state }, req, }) => ({}),\n connectedObjects: async (configuration) => {\n return extractFromLayout(configuration.layout);\n },\n};\n/*todo:\n\n-show a previous run\n-previous runs list\n-styling\n\n*/\n//# sourceMappingURL=workflow-room.js.map\n\n//# sourceURL=webpack://saltcorn/../saltcorn-data/dist/base-plugin/viewtemplates/workflow-room.js?\n}");
149
-
150
- /***/ }
151
-
152
- },
153
- /******/ __webpack_require__ => { // webpackRuntimeModules
154
- /******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
155
- /******/ __webpack_require__.O(0, ["common_chunks","data"], () => (__webpack_exec__("../saltcorn-base-plugin/index.js")));
156
- /******/ var __webpack_exports__ = __webpack_require__.O();
157
- /******/ return __webpack_exports__;
158
- /******/ }
159
- ]);
160
- });