{"hash":"edfe408c37d182c1","builtAt":"2026-08-12T10:13:45.975Z","stats":{"documents":202,"ancestors":8,"bytes":1080786},"documents":[{"id":"system","v":"959c6d4fbbaa1ab6","previous":{"v":"1ba34b3099b510df","f":{"id":"system-folder-form","v":"ab9e900950ea1bbd","text":"System"}},"p":"root","f":{"id":"system-folder-form","v":"f54cc6f019ba1689","text":"System"},"data":{"name":"System","description":"The System Folder contains common `Forms`,`Commands` and other relevant data necessary for the operation of the System."},"locks":[],"created":{"at":"2017-08-23T08:24:50+02:00","by":"","from":"","client":"import"},"modified":{"at":"2017-08-23T08:24:50+02:00","by":"","from":"","client":"import"},"t":1707217445495,"ct":1707217445495,"a_depth":0,"a":[],"a_forms":[],"inheritedLocks":[null],"stamps":[],"meta":{"modified":{"at":"2024-02-06T11:04:05+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}},"dts":1738849329451},{"id":"apps","v":"f519a9a871a660ed","p":"root","f":{"id":"folder","v":"60bfdadb874a9f10","text":"Folder"},"data":{"name":"Apps","description":"","icon":{"name":"","size":0,"type":null,"sha1":null,"dataUrl":null,"thumbnailUrl":null,"iconUrl":null,"fileInfo":{"created":null,"read":null,"modified":null,"imported":null}}},"locks":[],"created":{"at":"2017-08-23T08:24:50+02:00","by":"","from":"","client":"import"},"modified":{"at":"2021-03-30T11:45:56+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1617104756448,"ct":1617104756448,"a_depth":0,"a":[],"a_forms":[],"inheritedLocks":[null],"stamps":[],"previous":{"v":"934a2441db88a83d","f":{"id":"folder","v":"initial","text":"Folder"}},"dts":1738944388554},{"id":"controls-folder","v":"e3ecb970e1cb90e2","previous":{"v":"28d85aee28c14bf3","f":{"id":"repository-folder-form","v":"28d007478e6061ed","text":"Repository Folder"}},"p":"system","f":{"id":"repository-folder-form","v":"ea69cb55ca6b497a","text":"Repository Folder","client":"server"},"data":{"name":"Controls","description":"","icon":{"name":"","size":0,"type":null,"sha1":null,"dataUrl":null,"thumbnailUrl":null,"iconUrl":null,"fileInfo":{"created":null,"read":null,"modified":null,"imported":null}},"contentsForm":{"id":"control-form","v":"87c5591bf43ea21a","text":"Control"},"customAddCommand":{"id":"","v":"","text":""},"configurationDocument":{"id":"","v":"","text":""},"displayTableColumns":"data.namespace,data.name,data.packages","displayTableHeaders":"","displayTableDesigns":"","sortBy":"","sortAsc":false,"showResetSorting":false},"locks":[],"lockRefs":[],"created":{"at":"2018-10-12T11:48:02+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2018-10-12T11:48:02+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1539344882378,"ct":1539344882378,"a_depth":1,"a":["system"],"a_forms":["system-folder-form"],"inheritedLocks":[null,null],"stamps":[],"dts":1738680535452},{"id":"d4581623-f542-4ade-86be-8b3ecec56f0e","v":"8a46a65f62fac57a","p":"apps","f":{"id":"application-form","v":"e6c4e4126877157e","text":"Application"},"data":{"name":"KeepTrack","label":"","description":"","namedIcon":"","namedIconColor":"","icon":{"name":"","size":0,"type":null,"sha1":null,"dataUrl":null,"thumbnailUrl":null,"iconUrl":null,"fileInfo":{"created":null,"read":null,"modified":null,"imported":null},"mediaInfo":{}},"related":[],"searchableForms":[],"searchFilters":[],"dialogForms":[{"id":"204787ca-59c2-4b11-b866-aa05fa9c9891","v":"8ebfb2cd55f2b559","text":"Arbeits-Eintrag"},{"id":"0dfd4afd-f2ec-4ff0-9a79-811badf4fed5","v":"4b6a68ca48885771","text":"Teil-Eintrag"},{"id":"d7f0c56d-8e0e-4607-911b-b0789c557d40","v":"80b8b3341a987d67","text":"Arbeit"}],"controls":[],"designs":[],"changes":[{"id":"926b4401-ced7-4f37-a152-e14d58891df2","v":"initial","text":"Barcodes & NFC"},{"id":"03f45ef9-179f-4eed-894f-ed6d870cba98","v":"initial","text":"Smartphone Layout"},{"id":"51a290ae-2a59-4437-8e95-ee60c8ca3aa8","v":"initial","text":"Dashboard"},{"id":"bfd5949b-9b67-4e7a-b2f8-f4fb3fd003a4","v":"initial","text":"Dashboard"}]},"locks":[],"created":{"at":"2022-02-28T14:58:40+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-09-05T14:07:33+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721196327555,"ct":1721196327555,"a_depth":1,"a":["apps"],"a_forms":["folder"],"inheritedLocks":[null,null],"stamps":[],"previous":{"v":"860fac4fd60c0634","f":{"id":"application-form","v":"29335d8d5cd3c1a1","text":"Application"}},"meta":{"modified":{"at":"2024-07-17T06:05:27+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}},"dts":1716385252080},{"id":"781ff5de-69db-49dc-99f0-f328b308cd7d","v":"725101d05a73c84d","p":"apps","f":{"id":"application-form","v":"e6c4e4126877157e","text":"Application"},"data":{"name":"Products","label":"","description":"","namedIcon":"fas fa-box-heart","namedIconColor":"","icon":{"name":"","size":0,"type":null,"sha1":null,"dataUrl":null,"thumbnailUrl":null,"iconUrl":null,"fileInfo":{"created":null,"read":null,"modified":null,"imported":null},"mediaInfo":{}},"related":[],"searchableForms":[],"searchFilters":[],"dialogForms":[{"id":"0a401e72-7ef0-40f4-ac90-360b8694a2aa","v":"8b0a6b6bf87280ff","text":"Component"}],"controls":[],"designs":[],"changes":[]},"locks":[],"created":{"at":"2018-12-07T12:11:00+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2021-11-02T09:38:31+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721196328192,"ct":1721196328192,"a_depth":1,"a":["apps"],"a_forms":["folder"],"inheritedLocks":[null,null],"stamps":[],"previous":{"v":"ebb51cc1fa3596c3","f":{"id":"application-form","v":"cc7463f7bb2b4de6","text":"Application"}},"meta":{"modified":{"at":"2024-07-17T06:05:28+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}},"dts":1635845921614},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2018-10-12T11:48:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","client":"server","from":""},"ct":1721196482038,"data":{"configurationDocument":{"id":"","text":"","v":""},"contentsForm":{"id":"control-form","text":"Control","v":"87c5591bf43ea21a"},"customAddCommand":{"id":"","text":"","v":0},"description":"","disableFilter":false,"disableSort":false,"displayTableColumns":"namespace,name,package,container","displayTableDesigns":"","displayTableHeaders":"","filterFields":"","icon":{"dataUrl":null,"fileInfo":{"created":null,"imported":null,"modified":null,"read":null},"iconUrl":null,"name":"","sha1":null,"size":0,"thumbnailUrl":null,"type":null},"name":"Default","rowHeight":0,"sortAsc":false,"sortBy":"","sortFields":""},"f":{"id":"repository-folder-form","text":"Repository Folder","v":"ad03521affef6500"},"id":"91f205bc-9989-4fbe-8966-bd4ba9777c26","inheritedLocks":[null,null,null],"locks":[],"meta":{"modified":{"at":"2024-07-17T06:08:02+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null}},"modified":{"at":"2020-07-06T19:07:57+00:00","backedBy":null,"by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"f":{"id":"repository-folder-form","text":"Repository Folder","v":"4980e5acd05fe39a"},"v":"6b84897882d1e7ac"},"stamps":[],"t":1721196482038,"v":"3276fb1ba7d5ec70"},{"id":"75014663-b40b-4285-bdf9-ad54c6b5964a","v":"initial","p":"d4581623-f542-4ade-86be-8b3ecec56f0e","f":{"id":"folder","v":"60bfdadb874a9f10","text":"Folder"},"data":{"name":"Controls","description":"","icon":{"name":"","size":0,"type":null,"sha1":null,"dataUrl":null,"thumbnailUrl":null,"iconUrl":null,"fileInfo":{"created":null,"read":null,"modified":null,"imported":null}}},"locks":[],"created":{"at":"2022-02-28T15:14:47+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2022-02-28T15:14:47+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1646061287581,"ct":1646061287556,"a_depth":2,"a":["apps","d4581623-f542-4ade-86be-8b3ecec56f0e"],"a_forms":["folder","application-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"66524002-b61d-4297-9ee8-6f72d2370323","v":"initial","p":"781ff5de-69db-49dc-99f0-f328b308cd7d","f":{"id":"folder","v":"f1e4977dbee77607","text":"Folder"},"data":{"name":"Controls","description":"","icon":{"name":"","size":0,"type":null,"sha1":null,"dataUrl":null,"thumbnailUrl":null,"iconUrl":null,"fileInfo":{"created":null,"read":null,"modified":null,"imported":null}}},"locks":[],"created":{"at":"2021-11-02T09:38:31+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2021-11-02T09:38:31+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1635845911778,"ct":1635845911778,"a_depth":2,"a":["apps","781ff5de-69db-49dc-99f0-f328b308cd7d"],"a_forms":["folder","application-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"abc9a4be-e6e6-4a49-9005-566705b477cc","v":"ba4d9898d896e4c2","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"security","name":"accessible-documents","template":"<!-- <div class=\"_CSS-header\">Referenced locks</div>\n\n<div>\n  <div class=\"_CSS-lock-group\" v-for=\"(documents, lockId) in locals.lockedDocumentsByLock\">\n    <ae-design name=\"title\" :document-id=\"lockId\"></ae-design>\n    <ae-list class=\"_CSS-list\" list-id=\"2e798091-303f-406b-a42a-93aa1cd8425c\" :documents=\"documents\" param-design=\"search-title\"></ae-list>\n  </div>\n</div> -->\n\n\n<!-- <ae-list list-id=\"2e798091-303f-406b-a42a-93aa1cd8425c\" :documents=\"locals.documents\" param-design=\"search-title\"></ae-list> -->\n\n<div v-for=\"document of locals.documents\" style=\"display:flex;justify-content:space-between;\">\n  <ae-design name=\"search-title\" :document=\"document\"></ae-design>\n  <div v-for=\"result of locals.documentToResultMap[document.id]\">\n    <div v-if=\"result.group\">\n      <ae-design name=\"avatar--small\" :document=\"result.group\"></ae-design>\n      <ae-design name=\"title\" :document=\"result.lock\"></ae-design>\n      <ae-design name=\"title\" :document=\"result.key\"></ae-design>\n    </div>\n    <div v-else>\n      # Account\n    </div>\n  </div>\n</div>\n ","css":"._CSS-header {\n  font-weight: 700;\n  margin: 20px 0 10px 0;\n}\n\n._CSS-list {\n  padding-left: 30px;\n}\n\n._CSS-lock-group {\n  margin: 5px;\n} ","controller":"class Controller {\n  async init () {\n    locals.maxResultCount = 100\n\n    const { documents, documentToResultMap } = await this._getByGroups()\n\n    locals.documents = documents\n    locals.documentToResultMap = documentToResultMap\n\n\n  }\n\n  async _getByGroups() {\n    const documentToResultMap = {}\n\n    const documents = []\n\n    const groups = await Aeppic.getAll(document.data.groups)\n\n    const keys = await Aeppic.find(`f.id:key-form AND data.accounts:${document.id}`)\n\n    const groupKeysMap = {}\n\n    // const groupKeysPromises = groups.map(async (group) => {\n    //   const keys = await Aeppic.find(`f.id:key-form AND data.groups:${group.id}`)\n    //   groupKeysMap[group.id] = keys\n    //   return keys\n    // })\n\n    const groupKeysPromises = groups.map((group) => Aeppic.find(`f.id:key-form AND data.groups:${group.id}`))\n    const groupKeys = [].concat(...(await Promise.all(groupKeysPromises)))\n\n \n    // const locksPromises = allKeys.map(k => Aeppic.get(k.data.lock))\n    // const locks = await Promise.all(locksPromises)\n\n    // console.log(locks)\n\n\n    // for (const key of allKeys) {\n    //   const lock = await Aeppic.get(key.data.lock)\n\n    //   const lockedDocuments = await Aeppic.find(`locks:${lock.id}`)\n\n    //   for (const document of lockedDocuments) {\n    //     const res = {\n    //       document,\n    //       lock,\n    //       key,\n    //       group: false\n    //     }\n\n    //     if (!documentToResultMap[document.id]) {\n    //       documentToResultMap[document.id] = []\n    //     }\n\n    //     documentToResultMap[document.id].push(res)\n    //   }\n\n    //   documents.push(...lockedDocuments)\n    // }\n\n    const groupDocuments = await this._getLockedDocuments(groupKeys)\n    const keyDocuments = await this._getLockedDocuments(keys)\n\n    documents.push(...groupDocuments, ...keyDocuments)\n \n    return {\n      documents,\n      documentToResultMap\n    }\n  }\n\n  async _getLockedDocuments(keys) {\n    const documents = []\n\n    const locks = await Promise.all(keys.map(k => Aeppic.get(k.data.lock)))\n    const lockQuery = locks.map(lock => `locks:${lock.id}`).join(' OR ')\n\n    const lockedDocuments = await Aeppic.find(`${lockQuery} AND (a_depth:0 OR a_depth: 1 OR a_depth: 2)`)\n    documents.push(...lockedDocuments)\n\n    return documents\n  }\n \n    // \n    // const lockPromises = keys.map(key => Aeppic.get(key.data.lock))\n\n    // for await (const lock of lockPromises) {\n    //   console.log(lock)\n    // }\n\n    // return {}\n    // const locks = await Promise.all(lockPromises)\n\n    // const documents = []\n     \n    // for (const lock of locks) {\n    //   const lockedDocuments = await Aeppic.find(`locks:${lock.id}`)\n    //   documents.push(...lockedDocuments)\n    // }\n\n\n    // return {\n    //   lockedDocumentsByLock,\n    //   documents\n    // } \n\n  // async _getByKeys() {\n  //   const keys = [document]\n\n  //   const lockPromises = keys.map(key => Aeppic.get(key.data.lock))\n  //   const locks = await Promise.all(lockPromises)\n\n  //   const lockedDocumentsByLock = {}\n  //   const documents = []\n\n  //   for (const lock of locks) {\n  //     const lockedDocuments = await Aeppic.find(`locks:${lock.id}`)\n  //     lockedDocumentsByLock[lock.id] = lockedDocuments\n  //     documents.push(...lockedDocuments)\n  //   }\n\n\n  //   return { \n  //     lockedDocumentsByLock,\n  //     documents \n  //   }  \n  // }\n}\n ","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-01-10T14:24:40+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936821,"ct":1721209936821,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"ca9cf4e5b924bfc8","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2025-03-21T16:19:54+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"ct":1742573994089,"data":{"cardinality":"BOTH","controller":"class Controller {\n  init() {\n    const placeholder = form.getPlaceholder(fieldName)\n    locals.label = placeholder.label\n  }\n  \n  async toggle() {\n    setField(!getField())\n  }\n\n  // async toggleProgress() {\n  //   const editable = await Aeppic.edit(document)\n\n  //   editable.data.inBearbeitung = !editable.data.inBearbeitung\n\n  //   Aeppic.save(editable)\n  // }\n} ","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"action","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<div class=\"p-1 h-full w-full flex gap-1 items-end justify-center\">\n  <!-- text-3xl takes the space of the hidden label -->\n  <ae-layout  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n              :param-icon=\"[\n                            params.icon ? params.icon : 'far fa-check', \n                            params.expandIcon && 'text-3xl'\n                            ].join(' ')\"\n              :param-colors=\"[!field && 'bg-white text-secondary-500 border-secondary-400 hover:bg-secondary-100', field && 'bg-green-600 text-green-50 border-green-500 hover:bg-green-700']\"\n              @selected=\"controller.toggle()\"\n              :param-title=\"locals.label + ' umschalten'\"\n              :param-disabled=\"readonly\"\n              param-border=\"border border-secondary-300\"\n  ></ae-layout>   \n</div>  \n        ","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"0b253230-f243-42c2-a3c0-546f224fe479","inheritedLocks":[null,null,null],"locks":[],"meta":{"modified":{"at":"2025-04-04T13:59:05+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null}},"modified":{"at":"2025-03-27T15:18:27+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"a":["apps","59edbdbc-2cab-426a-bb42-6e57c5158e4f","605b4b46-ddbe-4ed2-99fa-537ec332dbbf"],"a_forms":["folder","application-form","control-folder"],"p":"605b4b46-ddbe-4ed2-99fa-537ec332dbbf","v":"7171d184a96f2225"},"stamps":[],"t":1743775145666,"v":"6e68ae7ae15fb5b3"},{"id":"59e2998f-6921-42a7-a7f8-7bf943c8f6ed","v":"63bb1e4d925b5198","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"vitegra","name":"activation-code","template":"<pre v-if=\"locals.value\" style=\"font-size:bigger\">\r\n  <span style=\"font-size:2em\">{{locals.value}}</span>\r\n</pre>\r\n<button :disabled=\"!controller.canActivate\" v-else  @click=\"controller.activate()\">\r\n  Activate <strong>{{controller.deviceSerial}}</strong>\r\n</button>\r\n<template v-if=\"locals.invalidChars\">\r\n  <strong>\r\n    <br>\r\n    Invalid chars in prompt\r\n  </strong>\r\n  <br>Allowed:<br>\r\n  {{locals.validChars.join(' ')}}\r\n</template> ","css":"","controller":"init() {\r\n  locals.value = getField()\r\n  locals.device = Aeppic.get(document.p)\r\n\r\n  locals.invalidChars = false\r\n  locals.validChars = '0123456789ABCDEFGHJKLMNPRSTUWXYZ'.split('')\r\n} \r\n  \r\nget deviceSerial() {\r\n  if (locals.device && locals.device.data && locals.device.data.serialNumber.length === 6) {\r\n    return locals.device.data.serialNumber\r\n  } else {\r\n    return ''\r\n  }\r\n} \r\n \r\nget canActivate() {\r\n  let foundError = false\r\n\r\n  for (const promptChar of document.data.prompt) {\r\n    if (locals.validChars.indexOf(promptChar) < 0 ) {\r\n      console.log('found invalid char', promptChar)\r\n      foundError = true\r\n      break\r\n    }\r\n  } \r\n\r\n  if (locals.invalidChars !== foundError) {\r\n    debugger\r\n    locals.invalidChars = foundError\r\n  }\r\n\r\n  return !locals.invalidChars && document.data.prompt.length === 8 && this.deviceSerial.length === 6\r\n}\r\n\r\nasync activate() {  \r\n  const response = await Aeppic.fetch(`https://windows-services.curasystems.com/activation/vitegra/${this.deviceSerial}/_activate/${document.data.type}/${document.data.prompt}`, {\r\n    method: 'POST',\r\n    headers: { 'X-Access-Code': '1c80aa7b-3bd0-4d83-8787-7ff1fb238af6'}\r\n  })\r\n \r\n  const result = await response.text()\r\n  const lines = result.trim().split('\\n')\r\n\r\n  const lastLine = lines.pop()\r\n  const secondToLastLine = lines.pop()\r\n\r\n  if (secondToLastLine.startsWith('DECODE')) {\r\n    const code = lastLine.trim()\r\n    setField(code)\r\n    locals.value = getField()\r\n\r\n    document.data.details = result\r\n  }\r\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-11-02T09:44:11+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2021-11-02T09:44:11+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936822,"ct":1721209936822,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"5c4be6f3dd89e66b","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"f0f7ebdd-e868-4ccb-9647-c4caf966d2e5","v":"991dacf16566e8df","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"security","name":"add-group","template":"<button class=\"bp-button bp-button-icon _CSS-button\" @click=\"controller.show()\">\n  <ae-layout layout-id=\"64eaddd0-ac8a-46d4-a84f-0eb16e42b40d\"></ae-layout>\n</button>\n \n<ae-dialog ref=\"dialog\" :backdrop-close=\"false\">\n  <div slot=\"header\" class=\"_CSS-dialog__header\" slot-scope=\"scope\">\n    <ae-icon @click.native=\"scope.close()\" class=\"fa fa-window-close\"></ae-icon>\n  </div>\n  \n  <div class=\"_CSS-new\" slot=\"body\">\n    <ae-form v-if=\"locals.newRequest\" :document=\"locals.newRequest\" :readonly=\"locals.running\"></ae-form>\n\n    <section class=\"ae-control-container\">\n      <button class=\"bp-button bp-button--raised _CSS-button\" @click=\"controller.save()\" >\n        <span>Save</span>\n      </button>\n    </section>\n  </div>\n</ae-dialog>\n ","css":"._CSS-spinner {\n  height: 20px;\n  width: 20px;\n  position: absolute;\n  right: 5px;\n  top: 0;\n  bottom: 0;\n}\n\n._CSS-button {\n  position: relative;\n}\n\n._CSS-button-reset {\n  width: max-content;\n}\n\n._CSS-profile {\n  display: flex;\n  align-items: center;\n  margin: 0 0 2em 0;\n}\n\n._CSS-profile > *:first-child {\n  margin-right: 1ch;\n}\n._CSS-dialog__header {\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n\n  overflow: hidden;\n}\n","controller":"class Controller {\n\n  async show() {\n    $refs.dialog.show()\n\n    await this.createNewRequest()\n  }\n\n  async createNewRequest() {\n    locals.newRequest = await Aeppic.new('group', document.id)\n  }\n\n  async save() {\n    $refs.dialog.close()\n\n    Aeppic.save(locals.newRequest)\n\n    locals.newRequest = null\n  }\n}\n\n\n  ","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-01-15T14:20:20+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936823,"ct":1721209936823,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"304ebf35bcc7a744","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"69486c2c-dbd8-4941-a062-361e31622719","v":"156385fbb395b285","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"security","name":"add-user","template":"<button class=\"bp-button bp-button-icon _CSS-button\" @click=\"controller.show()\" v-if=\"locals.show\">\n  <ae-layout layout-id=\"64eaddd0-ac8a-46d4-a84f-0eb16e42b40d\"></ae-layout>\n</button>\n \n<ae-dialog ref=\"dialog\" :backdrop-close=\"false\">\n  <div slot=\"header\" class=\"_CSS-dialog__header\" slot-scope=\"scope\" v-if=\"locals.openedOnce\">\n    <button class=\"ae-button\" @click=\"scope.close()\">\n      <ae-icon class=\"fa fa-window-close\"></ae-icon>\n    </button>\n  </div>\n  \n  <div class=\"_CSS-new-account\" slot=\"body\">\n    <ae-form :document=\"locals.newAccountRequest\" :readonly=\"locals.running\" :watch=\"false\"></ae-form>\n\n    <section class=\"ae-control-container\">\n      <button class=\"ae-button ae-button--primary\" @click=\"controller.createAccount()\" :disabled=\"locals.running\">\n        <span>Account anlegen </span>\n        <ae-layout v-show=\"locals.running\" class=\"_CSS-spinner\" layout-id=\"fefa8352-0e9a-4ba8-8349-c9402ac8aefa\"></ae-layout>\n      </button>\n    </section>\n  \n    <section class=\"ae-control-container\" v-if=\"locals.error\">\n      <h2>Fehler beim Ausführen</h2>\n      {{ Aeppic.translate(locals.error) }}\n    </section>\n\n    <section class=\"ae-control-container\" v-if=\"!locals.running && locals.executed && !locals.error\">\n      <h2>Account wurde angelegt</h2>\n      <div class=\"_CSS-profile\">\n        <ae-design name=\"profile-avatar\" :document-id=\"locals.accountId\"></ae-design>\n        <ae-design name=\"title\" :document-id=\"locals.accountId\"></ae-design>\n      </div>\n\n     \n    </section>\n  </div>\n\n</ae-dialog>\n\n","css":"._CSS-spinner {\n  height: 20px;\n  width: 20px;\n  position: absolute;\n  right: 5px;\n  top: 0;\n  bottom: 0;\n}\n\n._CSS-profile {\n  display: flex;\n  align-items: center;\n  margin: 0 0 2em 0;\n}\n\n._CSS-profile > *:first-child {\n  margin-right: 1ch;\n}\n._CSS-dialog__header {\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n\n  overflow: hidden;\n}\n","controller":"class Controller {\n  async init() {\n    locals.show = false\n    locals.running = false\n    locals.executed = false\n    locals.error = null\n    locals.accountId = null\n\n    if (document['a_forms'][0] !== 'c69a0c1c-6c53-40c2-87e8-5ace866bf1df') {\n      return\n    }\n\n    this.usersLocation = await Aeppic.Preferences.getSetting('bizpin:locations:users')\n    this.companyId = document.a[0]\n\n    locals.show = true\n  }\n\n  async show() {\n    locals.openedOnce = true\n    $refs.dialog.show()\n\n    await this.createNewRequest()\n  }\n\n  async createNewRequest() {\n    locals.running = false\n    locals.executed = false\n    locals.error = false\n    locals.accountId = null\n\n    locals.newAccountRequest = await Aeppic.clone('3734d019-c3b2-44d4-8148-792bb36b3911', this.usersLocation)\n  }\n\n  async createAccount() {\n    if (locals.running) {\n      return\n    }\n\n    locals.executed = false\n    locals.error = false\n    locals.accountId = null\n\n    locals.running = true\n\n    const accountCreationCommand = await Aeppic.Commands.execute('f0c81dcf-c1e1-4d2c-a0e6-05644d5c866f', Aeppic.Account, { ... locals.newAccountRequest.data, companyId: this.companyId })\n    \n    if (!accountCreationCommand.ok || !accountCreationCommand.value.ok) {\n      locals.error = accountCreationCommand.value.message || 'translate:unknown-error'\n    } else {\n      locals.accountId = accountCreationCommand.value.accountId\n      \n      if (locals.newAccountRequest.data.createPasswordResetTokens) {\n        await this._createPasswordResetToken(locals.accountId)\n      }\n    }\n\n    locals.executed = true\n    locals.running = false\n  }\n\n  async _createPasswordResetToken(accountId) {\n    const account = await Aeppic.get(accountId)\n    const baseUrl = packages.window && packages.window.location && packages.window.location.origin\n\n    const tokenSendingCommand = await Aeppic.Commands.execute('5c82f673-65be-4e39-8bfd-f4d10745daf0', accountId, {\n      mailTemplateId: 'c7dfdc8c-770d-447a-bfa9-2f6d5463c5f1',\n      smsTemplateId: '066cc072-04eb-4c27-8624-b19af07e6b07',\n      baseUrl\n    })\n\n    if (!tokenSendingCommand.ok || !tokenSendingCommand.value.ok) {\n      locals.error = tokenSendingCommand.value ? tokenSendingCommand.value.message : 'translate:unknown-error'\n      console.error(tokenSendingCommand)\n    }\n\n  }\n}\n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"10843fbf-4f0c-416b-be7d-42d6bbd0ae68","v":"f769dafadd21766c","text":"Window"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-12-07T16:16:28+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936823,"ct":1721209936823,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"d76a473c8ad92cc3","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"2c4aaae8-8c79-4ec1-85ef-5a67b05934f7","v":"202873d4dd8783d2","p":"91f205bc-9989-4fbe-8966-bd4ba9777c26","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"address","template":"<label :for=\"id('street')\" class=\"ae-form-cell__label\" v-translate=\"'street'\">Street\n</label>\n<input :id=\"id('street')\" type=\"text\" v-model=\"field.street\" :required=\"fieldInfo.required\" :readonly=\"readonly\" name=\"address\" autocomplete=\"address-line1\" :data-test=\"fieldName + '_street'\">\n<label :for=\"id('streetNumber')\" class=\"ae-form-cell__label\" v-translate=\"'abbr:number'\">No.\n</label>\n<input :id=\"id('streetNumber')\" type=\"text\" v-model=\"field.streetNumber\" :required=\"fieldInfo.required\" :readonly=\"readonly\" name=\"address\" autocomplete=\"address-line2\" :data-test=\"fieldName + '_streetNumber'\">\n<label :for=\"id('supplement')\" class=\"ae-form-cell__label\" v-translate=\"'supplement'\">Supplement\n</label>\n<input :id=\"id('supplement')\" type=\"text\" v-model=\"field.supplement\" :readonly=\"readonly\" name=\"supplement\" autocomplete=\"address-line3\" :data-test=\"fieldName + '_supplement'\">\n<label :for=\"id('postalCode')\" class=\"ae-form-cell__label\" v-translate=\"'zipcode'\">Zip Code\n</label>\n<input :id=\"id('postalCode')\" type=\"text\" v-model=\"field.postalCode\" :required=\"fieldInfo.required\" :readonly=\"readonly\" name=\"postal\" autocomplete=\"postal-code\" :data-test=\"fieldName + '_postalCode'\">\n<label :for=\"id('city')\" class=\"ae-form-cell__label\" v-translate=\"'city'\">City\n</label>\n<input :id=\"id('city')\" type=\"text\" v-model=\"field.city\" :required=\"fieldInfo.required\" :readonly=\"readonly\" name=\"city\" autocomplete=\"address-level2\" :data-test=\"fieldName + '_city'\">\n<label :for=\"id('state')\" class=\"ae-form-cell__label\" v-translate=\"'state'\">State\n</label>\n<input :id=\"id('state')\" type=\"text\" v-model=\"field.state\" :required=\"fieldInfo.required\" :readonly=\"readonly\" name=\"state\" autocomplete=\"address-level1\" :data-test=\"fieldName + '_state'\">\n<label :for=\"id('country')\" class=\"ae-form-cell__label\" v-translate=\"'country'\">Country\n</label>\n<input :id=\"id('country')\" type=\"text\" v-model=\"field.country\" :required=\"fieldInfo.required\" :readonly=\"readonly\" name=\"country-name\" autocomplete=\"country-name\" :data-test=\"fieldName + '_country'\">","css":"._CSS {\n  display: grid;\n  grid-template-columns: auto 1fr;\n  grid-auto-flow: row;\n  grid-gap: var(--bp-input-content-padding);\n\n  align-items: center;\n}","controller":"","parameters":[],"standalone":"FIELD","cardinality":"SINGLE","limitTypes":true,"types":["address"],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-12-06T13:37:47+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2024-07-17T09:49:31+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936824,"ct":1721209936824,"a_depth":3,"a":["system","controls-folder","91f205bc-9989-4fbe-8966-bd4ba9777c26"],"a_forms":["system-folder-form","repository-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null,null],"stamps":[],"previous":{"v":"3e374be48b33df0d","f":{"id":"control-form","v":"9bddead515b3a305","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-09-08T14:46:10+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936823,"data":{"cardinality":"BOTH","controller":"class Controller {\n  init() {\n    this.groups = [\n      [{\n        field:'street',\n        flex: 4,\n        autocomplete: 'address-line1'\n      }, {\n        field:'streetNumber',\n        flex: 1,\n        autocomplete: 'address-line2'\n      }, {\n        field:'supplement',\n        autocomplete: 'address-line3'\n      }],\n      [{\n        field:'postalCode',\n        autocomplete: 'postal-code'\n      }, {\n        field:'city',\n        autocomplete: 'address-level2'\n      }],\n      [{\n        field:'state',\n        autocomplete: 'address-level1'\n      }, {\n        field:'country',\n        autocomplete: 'country-name'\n      }]\n    ]\n    locals.groupedFields = this.groupedFields\n    locals.usesOsmLookup = !!params.geolocationTo\n    locals.osmNominatimAttribution = 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright'\n\n    locals.lookupLoading = false \n    locals.editing = false\n\n    this.debounceOsmLook = Aeppic.debounce(async(field) => {\n      await this.lookupGeolocation(field)\n      locals.lookupLoading = false\n    }, 1000)\n  }\n\n  get hasSuccessfulLookup() {\n    return params.geolocationTo && document.data[params.geolocationTo]?.lat !== 0 \n  }\n\n\n  get groupedFields() {\n    if (!params.fields) {\n      return this.groups\n    }\n\n    const fields = params.fields.split(',')\n\n    const available = []\n\n    for (const group of this.groups) {\n      const availableFields = Array.from(group).filter(g => fields.includes(g.field))\n\n      if (availableFields.length) {\n        available.push(availableFields)\n      }\n    }\n\n    return available\n  }\n\n  setField(name, value) {\n    const field = getField()\n    field[name] = value\n\n    if (params.fields) {\n\n      const all = ['street', 'streetNumber', 'postalCode', 'city', 'country', 'supplement', 'country', 'state']\n      const fields = params.fields.split(',')\n      const hiddenFields = all.filter(f => fields.includes(f) === false)\n\n      for (const hiddenField of hiddenFields) {\n        field[hiddenField] = ''\n      }\n\n    }\n\n    setField(field)\n\n    if (params.geolocationTo) {\n      locals.lookupLoading = true\n      this.debounceOsmLook(field)\n    }\n  }\n\n  edit() {\n    if (this.readonly) {\n      return\n    }\n\n    locals.editing = true\n  }\n\n  get readonly() {\n    return readonly || params.readonly\n  }\n\n  resetField() {\n    setField({\n      street: '',\n      streetNumber: '',\n      postalCode: '',\n      city: '',\n      state: '',\n      country: '',\n      supplement: '',\n    })\n  }\n\n  get isIos() {\n    return typeof navigator === 'object' && typeof navigator.userAgent === 'string' && /iPad|iPhone|iPod/.test(navigator.userAgent)\n  }\n\n  async lookupGeolocation(field) {\n    if (!field || !params.geolocationTo || (params.geolocationTo in document.data) === false) {\n      return\n    }\n\n    const queryObject = {\n      // amenity:  \n      street: [field.street, field.streetNumber, field.supplement].join(' '),\n      city: field.city,\n      postalcode: field.postalCode,\n      country: field.country,\n      state: field.state,\n    }\n\n    const queryObjectString = Object.keys(queryObject).reduce((result, key, index) => {\n      const value = queryObject[key]?.trim()\n      if (value) {\n        if (result === '') {\n          return result +=`${key}=${value}`\n        }\n        return result += `&${key}=${value}`\n      }\n      return result\n    }, '')\n\n    console.log(queryObjectString)\n\n    const lookupResult = {\n      lat: 0,\n      lon: 0,\n    }\n\n    try {\n      const osmLookup = await fetch(`https://nominatim.openstreetmap.org/search?${queryObjectString}&format=json&limit=1`)\n      \n      if (osmLookup.ok) {\n        const results = await osmLookup.json()\n\n        if (results.length === 0) {\n          console.warn('No osm result found')\n          return\n        }\n\n        const result = results[0]\n        const lat = Number(result.lat)\n        const lon = Number(result.lon)\n\n        if (Number.isNaN(lat) || Number.isNaN(lon)) {\n          console.warn('OSM result lat or lon is NaN')\n          return\n        }\n\n        lookupResult.lon = lon\n        lookupResult.lat = lat\n\n        if (result.licence) {\n          locals.osmNominatimAttribution = result.licence\n        }\n      }\n    } catch (e) {\n      console.error('OSM lookup failed', e)\n    } finally {\n      document.data[params.geolocationTo] = lookupResult\n    }\n  }\n}","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"address","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<div class=\"border rounded-md flex gap-2 min-h-[2em] w-full\"\ndata-ae-form-field-input\n>  \n  <div class=\"flex flex-col justify-center rounded-l-md border-r px-2 text-neutral-500 bg-white\" @click.stop> \n    <ae-icon :class=\"[controller.hasSuccessfulLookup ? 'far fa-map-location-dot' : 'far fa-map']\"></ae-icon>\n    <ae-icon v-show=\"locals.lookupLoading\" class=\"far fa-spinner-third fa-spin text-primary-500\"></ae-icon>   \n  </div>\n  <div v-if=\"!locals.editing\"   class=\"p-1 flex-1 flex justify-between gap-2 items-baseline\" @click.stop=\"controller.edit()\">\n    <div class=\"flex flex-col gap-1\" >\n      <div v-for=\"group of locals.groupedFields\" class=\"flex gap-1\">\n        <div v-if=\"field[f.field]\" v-for=\"f of group\">\n        {{ field[f.field] }}\n        </div>\n      </div>\n    </div>\n\n    <div class=\"flex flex-col gap-2\"> \n      <ae-layout \n          layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n          @selected=\"controller.edit()\"\n          param-icon=\"fa fa-edit\"\n          param-classes=\"bg-neutral-100 hover:bg-neutral-200 text-xs rounded-full\"\n          param-title=\"Bearbeitung starten\"\n          v-if=\"!readonly\"\n      ></ae-layout>\n\n      <template v-if=\"field.street || field.streetNumber || field.postalCode || field.city\">\n        <a class=\"rounded-full bg-secondary-100 hover:bg-secondary-200\" v-if=\"Aeppic.Device === 'Desktop' || !controller.isIos\" title=\"Navigation auf Google Maps starten\" @click.stop target=\"_blank\" :href=\"'http://maps.google.de?q=' + field.street + ' ' + field.streetNumber + ',' + field.postalCode + ' ' + field.city\">\n          <ae-layout \n              layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n              param-icon=\"far fa-location-arrow\"\n              param-classes=\"bg-secondary-100 hover:bg-secondary-200 text-xs rounded-full\"\n              param-title=\"Navigation auf Google Maps starten\"\n              param-border=\"border border-secondary-200\"\n          ></ae-layout>\n        </a>\n        <a class=\"rounded-full bg-secondary-100 hover:bg-secondary-200\" v-else title=\"Navigation in App starten\" @click.stop target=\"_blank\" :href=\"'maps:q=' + encodeURIComponent(field.street + '+' + field.streetNumber + '+' + field.postalCode + '+' + field.city)\"\">\n          <ae-layout \n              layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n              param-icon=\"far fa-location-arrow\"\n              param-classes=\"bg-secondary-100 hover:bg-secondary-200 text-xs rounded-full\"\n              param-title=\"Navigation in App starten\"\n              param-border=\"border border-secondary-200\"\n          ></ae-layout>\n        </a>\n      </template>\n    </div>\n  </div>\n  <div v-else class=\"flex-1 flex gap-4 py-1\">\n    <div class=\"flex-1 flex flex-col gap-2 pt-1 pb-2\">\n      <div class=\"flex gap-2 flex-wrap\" v-for=\"(group, groundIndex) of locals.groupedFields\"> \n        <div class=\"flex flex-col min-w-[10px]\" v-for=\"(f, fIndex) of group\" :style=\"{ flex: f.flex || 1 }\">\n          <label :for=\"id(f.field)\" class=\"text-neutral-600 text-sm\" v-translate=\"group.field\">\n            {{ Aeppic.translate(f.field) }}\n          </label>\n          <div>\n          </div>\n          <input v-if=\"groundIndex === 0 && fIndex === 0\"\n                  :id=\"id(f.field)\"\n                  type=\"text\"\n                  :value=\"field[f.field]\"\n                  :required=\"fieldInfo.required\"\n                  :readonly=\"readonly\"\n                  :autocomplete=\"f.autocomplete\"\n                  @input=\"controller.setField(f.field, $event.target.value)\"\n                  class=\"border p-1 rounded-md\"\n                  v-focus\n          >\n          <input v-else\n                  :id=\"id(f.field)\"\n                  type=\"text\"\n                  :value=\"field[f.field]\"\n                  :required=\"fieldInfo.required\"\n                  :readonly=\"readonly\"\n                  :autocomplete=\"f.autocomplete\"\n                  @input=\"controller.setField(f.field, $event.target.value)\"\n                  class=\"border p-1 rounded-md\"\n          >\n        </div>   \n      </div>\n\n      <div v-if=\"locals.usesOsmLookup\" class=\"text-xs\">     \n        Zur Anzeige von Adressdaten auf Karten genutzt:<br>{{locals.osmNominatimAttribution}}\n      </div>\n    </div>\n\n    <div class=\"flex flex-col gap-2\" v-if=\"!readonly\"> \n      <ae-layout \n        class=\"w-max\"\n        layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n        @selected=\"locals.editing = false\"\n        param-icon=\"fa fa-edit\"\n        param-classes=\"bg-neutral-100 hover:bg-neutral-200 text-xs rounded-full\"\n        param-title=\"Bearbeitung beenden\"\n      ></ae-layout>\n\n      <ae-layout \n        class=\"w-max\"\n\n        layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n        @selected=\"controller.resetField()\"\n        param-icon=\"fa fa-times\" \n        param-classes=\"bg-neutral-100 hover:bg-neutral-200 text-xs rounded-full\"\n        param-title=\"Adresse löschen\"\n      ></ae-layout>\n    </div>\n  </div>\n</div>\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"9543919a-6c37-4699-bbd0-22ce78d9ae26","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-03-27T13:40:30+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"4ca94a6424ecabfd"},"stamps":[],"t":1774618830393,"v":"c99d4151c002fd62"},{"id":"baeb25b4-35ce-4224-8eb4-7124400c9e09","v":"b1432678d0deab75","p":"controls-folder","f":{"id":"control-form","v":"8f342d57244fe7b5","text":"Control"},"data":{"namespace":"maus","name":"address","template":"Adresse\n<br>\n<div class=\"bg-white text-base\n          font-normal\n          text-gray-700\n          mt-2\n          bg-white bg-clip-padding\n          border border-solid border-gray-300\n          rounded\n          p-2\">\n  {{locals.address.street}}, <strong>{{locals.address.postalCode}} {{locals.address.city}}</strong> \n  <span>{{locals.address.country}}</span>  \n</div>","css":"","controller":"init() {\n  locals.address = getField()\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2023-01-20T15:34:36+00:00","by":"427610a9-39dc-4014-9176-5f72aeb23263","client":"server","from":""},"modified":{"at":"2023-01-20T15:43:03+00:00","backedBy":null,"by":"427610a9-39dc-4014-9176-5f72aeb23263","client":"server","from":"","impersonatedBy":null},"t":1721144004088,"ct":1721144004088,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"befd230e67ee1bbb","f":{"id":"control-form","v":"7672ea0b1b6d4c70","text":"Control"}},"meta":{"modified":{"at":"2024-07-16T17:33:24+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2018-12-06T13:37:52+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","client":"server","from":""},"ct":1721209936823,"data":{"cardinality":"SINGLE","controller":"class Controller {\n  init() {\n    locals.address = getField()\n\n    let dialogFocused = false\n\n    this.showDialog = async () => {\n      $refs.edit.show()\n      await Aeppic.nextRenderPass()\n      const input = $refs.edit.querySelector('input')\n      input?.select()\n      input?.focus() \n    }\n\n    const closeUnfocusedDialog = Aeppic.debounce(() => {\n      if (!dialogFocused) {\n        $refs.edit.close()\n      }\n    }, 50) \n\n    this.focusDialog = () => dialogFocused = true\n    this.unfocusDialog = () => {\n      dialogFocused = false\n      closeUnfocusedDialog()\n    }\n    \n    locals.isCondensed = (params.condensed === true)\n     \n    this.groups = [\n      [{\n        field:'street',\n        flex: 7,\n        autocomplete: 'address-line1'\n      }, {\n        field:'streetNumber',\n        flex: 2,\n        autocomplete: 'address-line2'\n      }, {\n        field:'supplement',\n        autocomplete: 'address-line3'\n      }],\n      [{\n        field:'postalCode',\n        autocomplete: 'postal-code'\n      }, {\n        field:'city',\n        autocomplete: 'address-level2'\n      }],\n      [{\n        field:'state',\n        autocomplete: 'address-level1'\n      }, {\n        field:'country',\n        autocomplete: 'country-name'\n      }]\n    ]\n\n    locals.groupedFields = this.groupedFields\n  }\n\n  get groupedFields() {\n    if (!params.fields) {\n      return this.groups\n    }\n\n    const fields = params.fields.split(',')\n\n    const available = []\n\n    for (const group of this.groups) {\n      const availableFields = Array.from(group).filter(g => fields.includes(g.field))\n\n      if (availableFields.length) {\n        available.push(availableFields)\n      }\n    }\n\n    return available\n  }\n\n  setField(name, value) {\n    const field = getField()\n    field[name] = value\n\n    if (params.fields) {\n\n      const all = ['street', 'streetNumber', 'postalCode', 'city', 'country', 'supplement', 'country', 'state']\n      const fields = params.fields.split(',')\n      const hiddenFields = all.filter(f => fields.includes(f) === false)\n\n      for (const hiddenField of hiddenFields) {\n        field[hiddenField] = ''\n      }\n\n    }\n\n    setField(field)\n  }\n\n  get isIos() {\n    return typeof navigator === 'object' && typeof navigator.userAgent === 'string' && /iPad|iPhone|iPod/.test(navigator.userAgent)\n  }\n}","css":"._CSS {\n  position: relative;\n}\n\n\n._CSS dialog::backdrop {\n  backdrop-filter: blur(3px);\n}\n\n\n._CSS_field {\n  display: flex;\n  background: var(--ae-ui-element-canvas-color);\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  border-radius: var(--ae-border-radius);\n  width: 100%;\n}\n\n._CSS-cells {\n  display: flex;\n  flex-wrap: wrap;\n  margin: 0 -0.5rem 0 -0.5rem;\n}\n\n._CSS-two-columns {\n  grid-template-columns: 1fr 1fr;\n}\n\n._CSS-cell {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  justify-content: flex-end;\n  margin: 0.5rem;\n}\n\n._CSS-button {\n  box-shadow: var(--ae-shadow-1);\n  position: relative;\n  background: var(--ae-background-300);\n  color: var(--ae-primary-900);\n  padding: var(--ae-size-2);\n  height: var(--ae-size-7);\n  width: var(--ae-size-7);\n  min-height: var(--ae-size-7);\n  min-width: var(--ae-size-7);\n  border-radius: 50%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n._CSS-button__icon {\n  font-size: 1.5em;\n}","description":"","device":"ALL","label":"","limitTypes":true,"name":"address","namespace":"","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<!-- <template v-if=\"params.buttonNavigation\">\n  <a v-if=\"Aeppic.Device === 'Desktop' || !controller.isIos\" title=\"Navigation auf Google Maps starten\" @click.stop class=\"_CSS-button\" target=\"_blank\" :href=\"'http://maps.google.de?q=' + document.data.adresse.street + ' ' + document.data.adresse.streetNumber + ',' + document.data.adresse.postalCode + ' ' + document.data.adresse.city\">\n    <ae-icon class=\"fa fa-location-arrow _CSS-button__icon\"></ae-icon>\n  </a>\n  <a v-else title=\"Navigation starten\" @click.stop class=\"_CSS-button\" target=\"_blank\" :href=\"'maps:q=' + encodeURIComponent(document.data.adresse.street + '+' + document.data.adresse.streetNumber + '+' + document.data.adresse.postalCode + '+' + document.data.adresse.city)\"\">\n    <ae-icon class=\"fa fa-location-arrow _CSS-button__icon\"></ae-icon>\n  </a>\n</template> -->  \n \n<div v-show=\"locals.isCondensed\" tabindex=\"0\" @click=\"controller.showDialog()\" @keydown.enter.prevent.stop=\"controller.showDialog()\" class=\"_CSS_field pl-1 flex flex-wrap gap-1 items-baseline\">  \n  <span>{{locals.address.street}}</span>\n  <span >{{locals.address.streetNumber}}</span>\n  <span v-if=\"(locals.address.street + locals.address.streetNumber).trim().length > 0\">,</span>\n  <span v-if=\"locals.address.supplement\" class=\"text-base\">{{locals.address.supplement}}</span>\n  <span class=\"font-bold\">{{locals.address.postalCode}}</span>\n  <span>{{locals.address.city}}</span>\n  <span class=\"text-slate-600 text-sm pl-2\">{{locals.address.state}}</span>\n  <span class=\"text-sm\">- {{locals.address.country}}</span>\n</div>\n\n<dialog ref=\"edit\" class=\"drop-shadow fixed z-20 m-0\" style=\"margin-left:10%\" @focusin=\"controller.focusDialog()\" @focusout=\"controller.unfocusDialog()\">\n  <form method=\"dialog\" autofocus>\n    <button class=\"absolute right-2 top-2 hover:bg-slate-400/80 p-2 rounded-full\">\n      <ae-icon class=\"p-0 m-0 fa fa-times\"></ae-icon>\n    </button>\n    <div class=\"_CSS-cells pt-3\" v-for=\"group of locals.groupedFields\">\n      <div class=\"_CSS-cell\" v-for=\"f of group\" :style=\"{ flex: f.flex || 1 }\">\n        <label :for=\"id(f.field)\" class=\"ae-form-cell__label\" v-translate=\"group.field\">\n          {{ Aeppic.translate(f.field) }}\n        </label>\n        <input :id=\"id(f.field)\" type=\"text\" :value=\"field[f.field]\"  :required=\"fieldInfo.required\" :readonly=\"readonly\" :autocomplete=\"f.autocomplete\" @input=\"controller.setField(f.field, $event.target.value)\" >\n      </div>\n    </div>\n  </form>\n</dialog>\n\n<template v-if=\"!locals.isCondensed\">  \n  <div class=\"_CSS-cells\" v-for=\"group of locals.groupedFields\">\n    <div class=\"_CSS-cell\" v-for=\"f of group\" :style=\"{ flex: f.flex || 1 }\">\n      <label :for=\"id(f.field)\" class=\"ae-form-cell__label\" v-translate=\"group.field\">\n        {{ Aeppic.translate(f.field) }}\n      </label>\n      <input :id=\"id(f.field)\" type=\"text\" :value=\"field[f.field]\"  :required=\"fieldInfo.required\" :readonly=\"readonly\" :autocomplete=\"f.autocomplete\" @input=\"controller.setField(f.field, $event.target.value)\">\n    </div>\n  </div>\n</template>\n\n<!-- <div class=\"_CSS-cells\">\n  <div class=\"_CSS-cell\" style=\"flex: 7;\">\n    <label :for=\"id('street')\" class=\"ae-form-cell__label\" v-translate=\"'street'\">Street\n    </label>\n    <input :id=\"id('street')\" type=\"text\" v-model=\"field.street\" :required=\"fieldInfo.required\" :readonly=\"readonly\" name=\"address\" autocomplete=\"address-line1\">\n  </div>\n  <div class=\"_CSS-cell\" style=\"flex: 2;\">\n    <label :for=\"id('streetNumber')\" class=\"ae-form-cell__label\" v-translate=\"'abbr:number'\">No.\n    </label>\n    <input :id=\"id('streetNumber')\" type=\"text\" v-model=\"field.streetNumber\" :required=\"fieldInfo.required\" :readonly=\"readonly\" name=\"address\" autocomplete=\"address-line2\">\n  </div>\n  <div class=\"_CSS-cell\">\n    <label :for=\"id('supplement')\" class=\"ae-form-cell__label\" v-translate=\"'supplement'\">Supplement\n    </label>\n    <input :id=\"id('supplement')\" type=\"text\" v-model=\"field.supplement\" :readonly=\"readonly\" name=\"supplement\" autocomplete=\"address-line3\">\n  </div>\n</div>\n\n<div class=\"_CSS-cells\">\n  <div class=\"_CSS-cell\">\n    <label :for=\"id('postalCode')\" class=\"ae-form-cell__label\" v-translate=\"'zipcode'\">Zip Code\n    </label>\n    <input :id=\"id('postalCode')\" type=\"text\" v-model=\"field.postalCode\" :required=\"fieldInfo.required\" :readonly=\"readonly\" name=\"postal\" autocomplete=\"postal-code\">\n  </div>\n  <div class=\"_CSS-cell\">\n    <label :for=\"id('city')\" class=\"ae-form-cell__label\" v-translate=\"'city'\">City\n    </label>\n    <input :id=\"id('city')\" type=\"text\" v-model=\"field.city\" :required=\"fieldInfo.required\" :readonly=\"readonly\" name=\"city\" autocomplete=\"address-level2\">\n  </div>\n</div>\n\n<div class=\"_CSS-cells\">\n  <div class=\"_CSS-cell\">\n    <label :for=\"id('state')\" class=\"ae-form-cell__label\" v-translate=\"'state'\">State\n    </label>\n    <input :id=\"id('state')\" type=\"text\" v-model=\"field.state\" :required=\"fieldInfo.required\" :readonly=\"readonly\" name=\"state\" autocomplete=\"address-level1\">\n  </div>\n  <div class=\"_CSS-cell\">\n    <label :for=\"id('country')\" class=\"ae-form-cell__label\" v-translate=\"'country'\">Country\n    </label>\n    <input :id=\"id('country')\" type=\"text\" v-model=\"field.country\" :required=\"fieldInfo.required\" :readonly=\"readonly\" name=\"country-name\" autocomplete=\"country-name\">\n  </div>\n</div> -->","types":["address"]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"e63e00be-e56b-4dd9-b4b1-476e57b0690e","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2024-07-17T09:54:23+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"716fa03b43be3a34"},"stamps":[],"t":1721210063274,"v":"f6dd9b7f5105dc88"},{"id":"bfcf4a59-0c5e-4cda-9309-f5532778e6bd","v":"118a5694f1aa6004","previous":{"v":"initial"},"p":"75014663-b40b-4285-bdf9-ad54c6b5964a","f":{"id":"control-form","v":"d87073964fc278a5","text":"Control"},"data":{"namespace":"sct","name":"anlagenteilwartung-beenden","template":"<!-- Change Status -->\n<rs-get-document id=\"{{context.document.p}}\" as=\"anlagenteil\"></rs-get-document>\n<document-command ng-if=\"!anlagenteil.data.unterschrift.dataUrl\"  style=\"margin-top:6px\" target-document=\"context.document\" command-id=\"df0fb114-bd6a-43c4-a6c3-dccc3d1a2268\">\n   <md-button   class=\"md-primary md-raised\" style=\"margin:0;min-height:100px;min-width:100%;background-color: #64748e \" ng-click=\"scanner.scan()\">\n        <div layout=\"row\" layout-align=\"center\" layout-padding>\n            <div>\n                <!-- <md-icon>arrow_back</md-icon>  -->\n                <span>Fertig</span>\n            </div>\n        </div>\n    </md-button>\n  \n</document-command>\n<md-button ng-if=\"anlagenteil.data.unterschrift.dataUrl\"  class=\"md-primary md-raised\" style=\"margin:0;min-height:100px;min-width:100%;background-color:#bfcf3c\" click-navigates-to=\"{{anlagenteil.id}}\">\n    <div layout=\"row\" layout-align=\"center\" layout-padding>\n        <div>\n            <md-icon>arrow_back</md-icon> \n            <span>Zurück</span>\n        </div>\n    </div>\n</md-button>\n<input ng-if=\"false\" type=\"number\" ng-model=\"context.field\"/>\n<!-- Go Back \n<document-command ng-if=\"!(!context.document.data.ergebnis ||context.document.data.ergebnis===1 || context.document.data.ergebnis===4)\"  target-document=\"context.document\" command-id=\"7db9db71-05b8-4759-b9bd-40d79f0acb31\">\n    <md-button  class=\"md-primary md-raised\" style=\"margin:0;min-height:100px;min-width:100%;background-color:#bfcf3c\" ng-click=\"scanner.scan()\">\n        <div layout=\"row\" layout-align=\"center\" layout-padding>\n            <div>\n                <md-icon>arrow_back</md-icon> \n                <span>Zurück</span>\n            </div>\n        </div>\n    </md-button>\n</document-command>\n-->\n","css":"","controller":"","cardinalityLimitation":"SINGLE","standalone":"FIELD","types":[],"packages":[],"supportsMobile":false,"label":"","description":""},"locks":[],"created":{"at":"2022-02-28T15:15:04+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-02-28T15:15:20+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1646061320014,"ct":1646061304814,"a_depth":3,"a":["apps","d4581623-f542-4ade-86be-8b3ecec56f0e","75014663-b40b-4285-bdf9-ad54c6b5964a"],"a_forms":["folder","application-form","folder"],"inheritedLocks":[null,null,null,null],"stamps":[]},{"id":"9bb36dae-f5d0-43fc-831e-d28ffe1c71b7","v":"e877538b6ca59234","previous":{"v":"initial"},"p":"75014663-b40b-4285-bdf9-ad54c6b5964a","f":{"id":"control-form","v":"d87073964fc278a5","text":"Control"},"data":{"namespace":"sct","name":"anlagenwartung-signieren","template":"<!-- PACKAGE: rs-signature-for-angular@0.3.4 -->\n\n<div layout=\"column\">\n    <div ng-if=\"context.field.dataUrl\" layout=\"column\" style=\"position:relative\" layout-align=\"left\" xstyle=\"height:200px\">\n        <div style=\"position:relative;background:rgba(255,255,255,0.8);margin:5px;border-radius:10px\">\n            <img style=\"max-height:250px;\" ng-if=\"context.field.dataUrl.substr(0,5) === 'data:'\" ng-src=\"{{context.field.dataUrl}}\" >\n            <img style=\"max-height:250px;\" ng-if=\"context.field.dataUrl.substr(0,6) === 'store:'\" ng-src=\"{{'api/docs/' + context.document.id + '/content?field=' + context.fieldName}}\">\n            <div style=\"xposition:absolute;bottom:0px;border-top:2px solid rgba(0,0,0,0.1);width:100%\" layout-padding layout=\"row\" layout-align=\"start start\">\n                <rs-get-document id=\"{{context.document.modified.by.id}}\" as=\"user\"></rs-get-document>\n                <span style=\"font-weight:bolder\">{{user.data.displayName}}</span>\n                <span flex>&nbsp;&nbsp;&nbsp;</span>\n                <span style=\"opacity:0.7\">{{context.document.modified.at | date:'dd.MMM.yyyy HH:mm'}}</span>\n            </div>\n        </div>\n        <md-button class=\"md-primary md-raised\" style=\"margin-bottom:32px;min-height:100px;min-width:100%;background-color: #64748e \" click-navigates-to=\"{{context.document.p}}\">\n            <div layout=\"row\" layout-align=\"center\" layout-padding>\n                <div>\n                    <md-icon>arrow_back</md-icon> \n                    <span>Zurück</span>\n                </div>\n            </div>\n        </md-button>\n    </div>\n    <rs-signature ng-if=\"!context.field.dataUrl\" layout-fill layout=\"column\" file-field=\"context.field\" as=\"signature\" ng-readonly=\"false\">\n        <md-button ng-click=\"signature.sign()\" class=\"md-warn md-raised\" style=\"margin:0;width:100%;min-height:100px;background-color:#83806d\" layout=\"row\" layout-align=\"center center\">\n            <div>\n                <md-icon>assignment_turned_in</md-icon>\n                <span>Unterschreiben</span>\n            </div>\n        </md-button>\n    </rs-signature>\n</div>","css":"","controller":"","cardinalityLimitation":"SINGLE","standalone":"FIELD","types":[],"packages":[],"supportsMobile":false,"label":"","description":""},"locks":[],"created":{"at":"2022-02-28T15:15:32+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-02-28T15:16:02+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1646061362692,"ct":1646061332766,"a_depth":3,"a":["apps","d4581623-f542-4ade-86be-8b3ecec56f0e","75014663-b40b-4285-bdf9-ad54c6b5964a"],"a_forms":["folder","application-form","folder"],"inheritedLocks":[null,null,null,null],"stamps":[]},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-08-24T09:10:07+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936824,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    if (params.directPreview) {\n      console.warn('TODO: params.directPreview')\n      locals.previewDocument = null\n    }\n\n    locals.newAttachment = null\n    await this.newTemporaryAttachment()\n    \n  } \n\n  async newTemporaryAttachment() {\n    if (!params.formId) {\n      return\n    }\n\n    locals.newAttachment = await Aeppic.new(params.formId, document.id)\n  }\n\n  attachmentAdded() {\n    Aeppic.save(locals.newAttachment)\n    document.addReference(fieldName, locals.newAttachment)\n    this.newTemporaryAttachment()\n  }\n\n  removeAttachment(attachment) {\n    document.removeReference(fieldName, attachment)\n  }\n\n  get addingAllowed() {\n    if (!fieldInfo.cardinality) {\n      return !document.data[fieldName].id\n    }\n\n    if (fieldInfo.cardinality.max === 'n') {\n      return true\n    }\n    return fieldInfo.cardinality.max <= document.data[fieldName].length\n  }\n\n  get hasAnyAttachment() {\n    if (!fieldInfo.cardinality) {\n      return !!document.data[fieldName].id\n    }\n\n    if (fieldInfo.cardinality) {\n      return !!document.data[fieldName].length\n    }\n  }\n\n  async attachmentSelected(event, eventOptions) {\n    // if (params.directPreview) {\n    //   const doc = await Aeppic.get(event)\n    //   locals.previewDocument = doc\n\n    //   await Aeppic.nextRenderPass()\n    //   await Aeppic.nextRenderPass()\n    //   await Aeppic.nextRenderPass()\n    //   await Aeppic.nextRenderPass()\n    //   await Aeppic.nextRenderPass()\n    //   await Aeppic.nextRenderPass()\n    //   $refs.preview.$emit('show')\n\n    //   return\n    // }\n    \n    Aeppic.drillInto(event, eventOptions)\n  }\n\n  get readonly() {\n    if (params.readonly === true) {\n      return true\n    }\n\n    if (typeof readonly === 'boolean') {\n      return readonly\n    }\n\n    return false\n  }\n} ","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"attachment","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"\n<div class=\"border rounded-md border-dashed p-2 bg-white dark:bg-slate-800 divide-y flex flex-col gap-2 min-h-[2em]\"\n      :class=\"{ 'border-red-500': validationErrors?.required, 'bg-neutral-100': readonly }\"\n    :data-test=\"fieldName\"\n> \n  <ae-layout  v-if=\"controller.addingAllowed && !controller.readonly\"\n              layout-id=\"8ae0359f-4d25-4d17-baf0-f69c006bd4ad\"\n              :param-show-ui=\"true\"\n              :param-readonly=\"controller.readonly\"\n              :param-document=\"document\"\n              :param-field-to-add-to=\"fieldName\"\n              :param-flatten=\"true\"\n              :param-file-form-id=\"params.formId\"\n              :param-file-form-field=\"params.fileField\"\n              param-file-name-to-field=\"name\"\n              :param-types=\"params.types\"\n              :param-enforce-types=\"params.enforceTypes\"\n              :param-field-name=\"fieldName\"\n              ref=\"container\"\n  ></ae-layout>\n\n  <!-- <ae-control name=\"rd:file\"\n              v-if=\"controller.addingAllowed && !controller.readonly\"\n              :readonly=\"!controller.addingAllowed || controller.readonly\"\n              :field-name=\"fieldName\"\n              param-file-name-to-field=\"name\"\n              :document=\"document\"\n              :param-file-form-id=\"params.formId\"\n              :param-file-form-field=\"params.fileField\"\n              :param-readonly=\"controller.readonly\"\n              :param-commit-new-documents=\"params.commitNewDocuments\"\n              :param-types=\"params.types\"\n              :param-no-border=\"true\"\n  ></ae-control> -->\n\n  <div v-if=\"controller.hasAnyAttachment\" class=\"pt-2\">\n      <ae-control name=\"rd:ref\"\n                  :document=\"document\"\n                  :field-name=\"fieldName\"\n                  :param-no-add=\"true\"\n                  :param-handle-selected=\"false\"\n                  @selected=\"controller.attachmentSelected($event, arguments[1])\"\n                  :param-readonly=\"controller.readonly\"\n                  :param-handle-selected-readonly=\"true\"\n                  :param-list=\"true\"\n                  :param-no-border=\"true\"\n                  param-design=\"rd:ref-card\"\n                  :param-design-params=\"{ thumbnail: true, size: params.size, height: params.height, width: params.width, alwaysShowTitle: params.alwaysShowTitle, showDownload: params.showDownload, showDownloadOriginalOnly: params.showDownloadOriginalOnly, ... params.designParams }\"\n    ></ae-control>\n    <!-- :param-list=\"params.list !== 'undefined' ? params.list : false\" -->\n    <!-- :param-design=\"params.list ? 'ref-card' : 'attachment-card'\" -->\n  </div>\n</div>\n\n<!-- <div class=\"ae-form-cell__input\">\n  <ae-control v-if=\"controller.hasAnyAttachment\"\n              name=\"ref\"\n              :document=\"document\"\n              :field-name=\"fieldName\"\n              :param-no-add=\"true\"\n              :param-handle-selected=\"false\"\n              @selected=\"controller.attachmentSelected($event, arguments[1])\"\n              :param-readonly=\"controller.readonly\"\n              :param-handle-selected-readonly=\"true\"\n              :param-list=\"params.list !== 'undefined' ? params.list : false\"\n              :param-no-border=\"true\"\n              :param-design=\"params.list ? 'ref-card' : 'attachment-card'\"\n              :param-design-params=\"{ size: params.size, height: params.height, width: params.width, alwaysShowTitle: params.alwaysShowTitle, ... params.designParams }\"\n  ></ae-control>\n\n  <ae-control name=\"file\"\n              v-if=\"controller.addingAllowed && !controller.readonly\"\n              :readonly=\"!controller.addingAllowed || controller.readonly\"\n              :field-name=\"fieldName\"\n              param-file-name-to-field=\"name\"\n              :document=\"document\"\n              :param-file-form-id=\"params.formId\"\n              :param-file-form-field=\"params.fileField\"\n              :param-readonly=\"controller.readonly\"\n              :param-commit-new-documents=\"params.commitNewDocuments\"\n              :param-types=\"params.types\"\n              :param-no-border=\"true\"\n  ></ae-control>\n\n  <ae-layout\n    v-if=\"locals.previewDocument\"\n    ref=\"preview\"\n    layout-id=\"bbd6d99f-571d-422f-a2bc-15d3f8ecfae8\"\n    :param-file-name=\"locals.previewDocument.data[params.fileField || 'datei']?.name\"\n    :param-field-name=\"params.fileField || 'datei'\"\n    :param-document=\"locals.previewDocument\"\n  ></ae-layout>\n      \n</div> -->","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"47713338-f8a0-4263-a4fa-87c22a771146","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-12-12T15:08:46+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"3ea638f715b3c7c7"},"stamps":[],"t":1765552126912,"v":"167f379f16074bb3"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2019-03-25T15:17:27+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","client":"server","from":""},"ct":1721209936824,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    locals.previewDocument = null\n\n    locals.newAttachment = null\n    await this.newTemporaryAttachment()\n  }\n\n  async newTemporaryAttachment() {\n    if (!params.formId) {\n      return\n    }\n\n    locals.newAttachment = await Aeppic.new(params.formId, document.id)\n  }\n\n  attachmentAdded() {\n    Aeppic.save(locals.newAttachment)\n    document.addReference(fieldName, locals.newAttachment)\n    this.newTemporaryAttachment()\n  }\n\n  removeAttachment(attachment) {\n    document.removeReference(fieldName, attachment)\n  }\n\n  get addingAllowed() {\n    if (!fieldInfo.cardinality) {\n      return !document.data[fieldName].id\n    }\n\n    if (fieldInfo.cardinality.max === 'n') {\n      return true\n    }\n    return fieldInfo.cardinality.max <= document.data[fieldName].length\n  }\n\n  get hasAnyAttachment() {\n    if (!fieldInfo.cardinality) {\n      return !!document.data[fieldName].id\n    }\n\n    if (fieldInfo.cardinality) {\n      return !!document.data[fieldName].length\n    }\n  }\n\n  async attachmentSelected(event, eventOptions) {\n    if (params.directPreview) {\n      const doc = await Aeppic.get(event)\n      locals.previewDocument = doc\n\n      await Aeppic.nextRenderPass()\n      await Aeppic.nextRenderPass()\n      await Aeppic.nextRenderPass()\n      await Aeppic.nextRenderPass()\n      await Aeppic.nextRenderPass()\n      await Aeppic.nextRenderPass()\n      $refs.preview.$emit('show')\n\n      return\n    }\n    \n    Aeppic.drillInto(event, eventOptions)\n  }\n\n  get readonly() {\n    if (params.readonly === true) {\n      return true\n    }\n\n    if (typeof readonly === 'boolean') {\n      return readonly\n    }\n\n    return false\n  }\n} ","css":"","description":"","device":"ALL","label":"","limitTypes":true,"name":"attachment","namespace":"","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<!-- {{document.data.beschreibung}} -->\n\n\n<div class=\"ae-form-cell__input\">\n  <ae-control v-if=\"controller.hasAnyAttachment\"\n              name=\"ref\"\n              :document=\"document\"\n              :field-name=\"fieldName\"\n              :param-no-add=\"true\"\n              :param-handle-selected=\"false\"\n              @selected=\"controller.attachmentSelected($event, arguments[1])\"\n              :param-readonly=\"controller.readonly\"\n              :param-handle-selected-readonly=\"true\"\n              :param-list=\"params.list !== 'undefined' ? params.list : false\"\n              :param-no-border=\"true\"\n              :param-design=\"params.list ? 'ref-card' : 'attachment-card'\"\n              :param-design-params=\"{ size: params.size, height: params.height, width: params.width, alwaysShowTitle: params.alwaysShowTitle, ... params.designParams }\"\n  ></ae-control>\n\n  <ae-control name=\"file\"\n              v-if=\"controller.addingAllowed && !controller.readonly\"\n              :readonly=\"!controller.addingAllowed || controller.readonly\"\n              :field-name=\"fieldName\"\n              param-file-name-to-field=\"name\"\n              :document=\"document\"\n              :param-file-form-id=\"params.formId\"\n              :param-file-form-field=\"params.fileField\"\n              :param-readonly=\"controller.readonly\"\n              :param-commit-new-documents=\"params.commitNewDocuments\"\n              :param-types=\"params.types\"\n              :param-no-border=\"true\"\n  ></ae-control>\n\n  <ae-layout\n    v-if=\"locals.previewDocument\"\n    ref=\"preview\"\n    layout-id=\"bbd6d99f-571d-422f-a2bc-15d3f8ecfae8\"\n    :param-file-name=\"locals.previewDocument.data[params.fileField || 'datei']?.name\"\n    :param-field-name=\"params.fileField || 'datei'\"\n    :param-document=\"locals.previewDocument\"\n  ></ae-layout>\n      \n</div>","types":["ref"]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"9a40ffd1-4595-4263-b39e-1f00fc55ee36","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2024-07-17T09:54:59+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"2bc06760c16364aa"},"stamps":[],"t":1721210099639,"v":"58ece55c4b03d4ee"},{"id":"61fb9a45-3a08-4ca4-b472-f91ba7e761c7","v":"ad126f9998dab604","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"barcode","template":"<div class=\"_CSS-picker\">\n\t<div v-if=\"!field.id\" class=\"bp-button\" @click=\"controller.showBarcodeScanner()\">\n\t\t<ae-icon class=\"fa fa-barcode fa-3x\"></ae-icon>\n\t</div>\n\t<ae-control name=\"ref\"\n    style=\"width: 100%;\"\n    :field-name=\"fieldName\"\n    :document=\"document\"\n    :param-ancestor-form=\"params.ancestorForm\"\n    :param-hide-path=\"params.hidePath\"\n  ></ae-control>\n\n</div>\n\n<ae-dialog\n  :full-screen=\"true\"\n  ref=\"barcodeDialog\">\n  <div slot=\"header\" class=\"_CSS-dialog-header\">\n\t\t<div class=\"bp-button\" @click=\"$refs.barcodeDialog.close()\">\n\t\t\t<ae-icon class=\"fa fa-2x fa-window-close\"></ae-icon>\n\t\t</div>\n\t</div>\n  <barcode\n    class=\"_CSS-barcode\"\n    slot=\"body\"\n    v-if=\"$refs.barcodeDialog && $refs.barcodeDialog.isOpen || false\"\n   :value=\"locals.lastScannedBarcode\"\n   @input=\"controller.scannedBarcode($event)\"\n   ></barcode>\n</ae-dialog>\n","css":"._CSS-picker {\n  overflow: hidden;\n  display: flex;\n  align-items: center;\n}\n\n._CSS-barcode {\n  background-color: black;\n}\n\n._CSS-dialog-header {\n  display: flex;\n  justify-content: flex-end;\n}","controller":"class Controller {\n  init() {\n    locals.lastScannedBarcode = ''\n\n    Aeppic.watchRevisions(document.id, async (changedDocument) => {\n      if (changedDocument.revisions.length > 1) {\n        const revisionBeforeThisChange = changedDocument.revisions[changedDocument.revisions.length - 2]\n\n        if (revisionBeforeThisChange.data[fieldName].id === changedDocument.data[fieldName].id) {\n          return   \n        }\n      }\n\n      const selectedDeviceId = changedDocument.data[fieldName].id\n      if (!selectedDeviceId) {\n        document.data.verbrauch = null\n        document.data.betriebsstunden = null\n        return\n      }\n\n      const device = await Aeppic.get(selectedDeviceId)\n      document.data.verbrauch = device.data.verbrauchswert\n      document.data.betriebsstunden = device.data.zaehlerstand\n    })\n  }\n\n  showBarcodeScanner() {\n    locals.lastScannedBarcode = ''\n\n    $refs.barcodeDialog.show()\n  }\n\n  async scannedBarcode(barcode) {\n    locals.lastScannedBarcode = barcode\n    console.log(barcode)\n    $refs.barcodeDialog.close()\n\n    // TODO: use fieldInfo.joinedFilter\n    const documentToBeReferenced = await Aeppic.find(`f.id:${fieldInfo.formRef} data.${params.barcodeFieldName}:${barcode}`)\n    if (!documentToBeReferenced || !documentToBeReferenced[0]) {\n      return\n    }\n\n    const field = getField()\n    if (field.length) {\n      document.removeReference(fieldName)\n    }\n\n    document.addReference(fieldName, documentToBeReferenced[0])\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"76392f56-fb5f-4438-b305-de8385de9400","v":"6f753345bf942ae9","text":"Barcode Reader"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-04-10T11:55:04+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936824,"ct":1721209936824,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"97de046663bb4e8e","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2018-10-12T11:48:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","client":"server","from":""},"ct":1721209936825,"data":{"cardinality":"SINGLE","controller":"  class Controller {\n  init() {\n    locals.dragHover = false\n  }\n\n  drop(event) {\n    locals.dragHover = false\n    this.selected(event.dataTransfer.items)\n  }\n  \n  selected(fileList) {\n    if (!fileList.length) {\n      return\n    }\n\n    const files = []\n    if (fileList instanceof DataTransferItemList) {\n      files.push(fileList[0].getAsFile())\n    } else {\n      files.push(fileList.item(0))\n    }\n\n    const file = files[0]\n\n    const reader = new FileReader()\n    reader.onload = (event) => {\n      if (event.target.result) {\n        setField(event.target.result)\n      }\n    }\n    reader.readAsText(file)\n\n  }\n}","css":"._CSS {\n  display: flex;\n  background: var(--ae-ui-background);\n  border-radius: var(--ae-border-radius);\n}\n\n._CSS-expandable {\n  flex: 1;\n  display: flex;\n  flex-flow: column;\n}\n\n._CSS-expandable.ae-expandable--expanded {\n  background: var(--ae-ui-background);\n}\n\n._CSS-bpmn {\n  width: 100%;\n  height: 100%;\n  flex: 1;\n}\n\n._CSS-bpmn--writeable {\n  min-width: 400px;\n  min-height: 400px;  \n}\n\n._CSS-bpmn--hover {\n  background: var(--ae-primary-d2);\n}\n","description":"","device":"ALL","label":"","limitTypes":true,"name":"bpmn","namespace":"","packages":[{"id":"b007887e-ae29-4441-9d12-2302a4abab1d","text":"Bpmn Viewer","v":"2568562dbee0edf2"}],"parameters":[],"standalone":"FIELD","template":"\n<bpmn v-if=\"params.viewonly === true\" class=\"_CSS-bpmn\"\n  v-model=\"field\"\n  :readonly=\"true\"\n  :viewonly=\"true\"\n  ></bpmn>\n\n<ae-expandable v-else class=\"_CSS-expandable\">\n  <bpmn\n    class=\"_CSS-bpmn\"\n    :class=\"{ '_CSS-bpmn--writeable': !readonly, '_CSS-bpmn--hover': locals.dragHover }\"\n    v-model=\"field\"\n    :readonly=\"params.readonly != null ? params.readonly: readonly\"\n    :viewonly=\"params.viewonly === true ? true : false\"\n    @drop.native.prevent.stop=\"controller.drop\"\n    @dragenter.native.prevent.stop=\"locals.dragHover = true\"\n    @dragleave.native.prevent.stop=\"locals.dragHover = false\"\n    @dragover.native.prevent.stop=\"locals.dragHover = true\"\n    ></bpmn>\n  <input @change=\"controller.selected($event.target.files)\" ref=\"input\" type=\"file\" style=\"display: none;\">\n</ae-expandable>\n","types":["text"]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"48c405ca-4ff0-4aff-b9ca-2faf43235c50","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2024-07-17T09:56:37+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"08ce6feee66464bf"},"stamps":[],"t":1721210197768,"v":"b05a28c0cf47fd57"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-12-01T13:36:16+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936825,"data":{"cardinality":"BOTH","controller":"  class Controller {\n  init() {\n    locals.edit = false\n    locals.dragHover = false\n  }\n\n  drop(event) {\n    locals.dragHover = false\n    this.selected(event.dataTransfer.items)\n  }\n  \n  selected(fileList) {\n    if (!fileList.length) {\n      return\n    }\n\n    const files = []\n    if (fileList instanceof DataTransferItemList) {\n      files.push(fileList[0].getAsFile())\n    } else {\n      files.push(fileList.item(0))\n    }\n\n    const file = files[0]\n\n    const reader = new FileReader()\n    reader.onload = (event) => {\n      if (event.target.result) {\n        setField(event.target.result)\n      }\n    }\n    reader.readAsText(file)\n\n  }\n}","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"bpmn","namespace":"rd","packages":[{"id":"b007887e-ae29-4441-9d12-2302a4abab1d","text":"Bpmn Viewer","v":"2568562dbee0edf2"}],"parameters":[],"standalone":"SUPPORTED","template":"<div class=\"h-full w-full flex flex-col\">\n  <bpmn v-if=\"params.viewonly === true\" class=\"h-full w-full flex-1\" v-model=\"field\" :readonly=\"true\" :viewonly=\"true\"></bpmn>\n\n  <ae-expandable v-else class=\"flex-1 flex flex-col gap-4\" style=\"background:white\">\n    <div class=\"flex\">\n      <ae-layout\n      layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n      :param-icon=\"locals.edit ? 'far fa-eye' : 'far fa-edit'\"\n      :param-text=\"locals.edit ? 'Anzeigen' : 'Bearbeiten'\"\n      :param-primary=\"true\"\n      @selected=\"locals.edit = !locals.edit\"\n      ></ae-layout>\n    </div>\n\n    <bpmn\n      :key=\"id(locals.edit)\"\n      class=\"flex-1\" \n      :class=\"{ 'min-h-[400px] min-w-[400px]': !readonly, 'bg-primary-100': locals.dragHover }\"\n      v-model=\"field\"\n      :readonly=\"params.readonly != null ? params.readonly: readonly\"\n      :viewonly=\"locals.edit === true ? false : true\"\n      @drop.native.prevent.stop=\"controller.drop($event)\"\n      @dragenter.native.prevent.stop=\"locals.dragHover = true\"\n      @dragleave.native.prevent.stop=\"locals.dragHover = false\"\n      @dragover.native.prevent.stop=\"locals.dragHover = true\"\n    ></bpmn>\n    <input @change=\"controller.selected($event.target.files)\" ref=\"input\" type=\"file\" style=\"display: none\" />\n  </ae-expandable>\n</div>\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"9b513bde-50b6-4d79-8b18-cb758391a41f","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-01-13T10:35:31+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"1f20b66b601093b6"},"stamps":[],"t":1768300531142,"v":"4177302dc950a2a5"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2018-10-12T11:48:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","client":"server","from":""},"ct":1721209936825,"data":{"cardinality":"MULTIPLE","controller":"class Controller {\r\n\r\n  init() {\r\n    locals.checkedChangeCounter = 0\r\n  }\r\n\r\n  toggle(option) {\r\n    // if (readonly) {\r\n    //   return\r\n    // }\r\n    locals.checkedChangeCounter += 1\r\n\r\n    let index = 0\r\n    const values = getField()\r\n\r\n    for (const value of values) {\r\n      if (value === option.value) {\r\n        values.splice(index, 1)\r\n        return\r\n      }\r\n  \r\n      index += 1\r\n    }\r\n\r\n\r\n    values.push(option.value)\r\n\r\n  } \r\n\r\n  isChecked(option) {\r\n    for (const value of getField()) {\r\n      if (value === option.value) {\r\n        return true\r\n      }\r\n    }\r\n\r\n    return false\r\n  }\r\n\r\n  iconStyle(truthy) {\r\n    if (params.appearance === 'toggle') {\r\n      if (truthy) {\r\n        return 'fa-toggle-on'\r\n      } else {\r\n        return 'fa-toggle-off'\r\n      }\r\n    }\r\n\r\n    if (truthy) {\r\n      return 'fa-check-square'\r\n    } else {\r\n      return 'fa-square'\r\n    }\r\n  }\r\n\r\n  getIconClasses(option) {\r\n    const className = this.iconStyle(this.isChecked(option))\r\n\r\n    const classes = {}\r\n    classes[className] = true\r\n\r\n    const dependOn = locals.checkedChangeCounter \r\n\r\n    return classes\r\n  }\r\n\r\n  get isInvalid() {\r\n    if (!fieldInfo.required) {\r\n      return false\r\n    }\r\n\r\n    const errors = form.validateDocumentDataField(document.data, fieldName)\r\n    if (!errors) {\r\n      return false\r\n    }\r\n\r\n    return true\r\n  }\r\n\r\n}","css":"._CSS-container {\r\n  display:flex;\r\n  align-items: center;\r\n  flex-wrap: wrap;\r\n}\r\n\r\n._CSS-container > label {\r\n  padding-right: 1em;\r\n  white-space: nowrap;\r\n  overflow:hidden;\r\n  display:inline-block;\r\n} \r\n\r\n@supports(display:grid) {\r\n  ._CSS-container {\r\n    display:grid;\r\n    grid-gap: 0.5em 1em; \r\n    grid-template-columns: repeat(auto-fill, minmax(11ch, 1fr));\r\n  }\r\n  ._CSS-container > label {\r\n    padding-right: 0px;\r\n  }\r\n}\r\n\r\n/* \r\n._CSS > label:nth-child(2n) {\r\n    flex-basis: 1%;\r\n} */","description":"Multiple checkboxes in parallel instead of in a drop-in","device":"ALL","label":"","limitTypes":false,"name":"checkboxes","namespace":"","packages":[],"parameters":[],"standalone":"FIELD","template":"<div class=\"ae-form-cell__input _CSS-container\">\r\n  <label v-for=\"(option,index) in fieldInfo.settings.options\" @click=\"!readonly && controller.toggle(option)\" :class=\"{ 'ae-form-cell__input--invalid': controller.isInvalid }\" >\r\n    <ae-icon class=\"input far\" :key=\"locals.checkedChangeCounter\" :class=\"controller.getIconClasses(option)\"></ae-icon>\r\n    <span>{{option.display}}</span>\r\n    <!-- <input  type=\"checkbox\" @input=\"controller.toggle(option)\" :checked=\"controller.isChecked(option)\"> -->\r\n  </label> \r\n</div>\r\n \r\n ","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"482bb9f2-a943-42dd-8780-d88fbd0b6787","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2024-07-17T09:56:32+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"fcabee1df7b705bb"},"stamps":[],"t":1721210192608,"v":"0e21257c299b2c4c"},{"id":"5c80a98a-0e14-44c7-896a-152a329a6523","v":"c77a8a1ac36e829a","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"child","template":"<template v-if=\"!locals.ref || !locals.ref.id\"> \n  No child\n</template>\n<template v-else>\n  <ae-design :document=\"locals.ref\" name=\"child, page--left, page\" :param-auto-save=\"true\"></ae-design>\n</template>","css":"._CSS {\n  flex: none;\n}","controller":"async init() {\n  locals.ref = null\n  const activeReference = getField()\n  const hasActiveReference = !!(activeReference?.id)\n\n  if (!hasActiveReference) {\n    if (params.create) {\n      if (fieldInfo.formRef) {\n        console.log('child auto createing building', fieldInfo.formRef)\n        const newChild = await Aeppic.new(fieldInfo.formRef, document.id)\n        document.addReference(fieldInfo.name, newChild)\n        Aeppic.save(newChild)\n      }\n    }\n  }\n\n  locals.ref = getField()\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2023-05-12T19:04:29+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","from":"","client":"server"},"modified":{"at":"2023-06-14T11:42:31+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936825,"ct":1721209936825,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"8b452fa3418669d9","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"12bb31dd-98a8-48f1-9b31-d3a4e43dd6ca","v":"aaeb78c19e3df28d","previous":{"v":"fc5971ef4b13c090"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"children","template":"<div xclass=\"border rounded-md px-2 min-h-[2em]\">\n  <ae-control v-if=\"!params.onlyAdd\"\n              name=\"rd:ref\"\n              :document=\"document\"\n              :field-name=\"fieldName\"\n              :label=\"label\"\n              :param-list=\"true\"\n              :param-no-add=\"true\"\n              :param-handle-selected=\"params.handleSelected\"\n              :param-handle-selected-readonly=\"params.handleSelectedReadonly\"\n              :param-handle-remove=\"params.handleRemove\"\n              :param-delete-on-remove=\"params.deleteOnRemove\"\n              :param-confirm-remove=\"params.confirmRemove\"\n              :param-design=\"params.design\"\n              :param-add-to-beginning=\"params.addToBeginning\"\n              :param-show-filter=\"params.showFilter\"\n              :param-show-reset-sorting=\"params.showResetSorting\"\n              :param-initial-filters=\"params.initialFilters\"\n              :param-initial-sort-field=\"params.initialSortField\"\n              :param-initial-sort-descending=\"params.initialSortDescending\"\n              :param-hide-path=\"params.hidePath\"\n              :param-disable-remove=\"params.disableRemove\"\n              :param-disable-sorting=\"params.disableSorting\"\n              :readonly=\"readonly\"\n              :param-filter-fields=\"params.filterFields\"\n              :param-sort-fields=\"params.sortFields\"\n              :param-inline=\"params.inline\"\n              :param-inline-layout=\"params.inlineLayout\"\n              :param-list-empty-text=\"controller.emptyListText\"\n              :param-collapse-after=\"params.collapseAfter\"\n              :param-fixed-control-filter=\"params.fixedControlFilter\"\n              :param-show-actions=\"params.showActions\"\n  ></ae-control>\n</div>\n\n ","css":"","controller":"class Controller {\n  get emptyListText() {\n    if (params.showTemplates || params.onlyTemplate) {\n      return \"'Aus Vorlage' auswählen um Einträge hinzuzufügen\"\n    }\n    if (params.newCommands && !params.onlyTemplate) {\n      return \"'Neu' auswählen um Einträge hinzuzufügen\"\n    }\n    return \"'Neu' auswählen um Einträge hinzuzufügen\"\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2022-10-26T07:44:31+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"modified":{"at":"2026-04-20T09:21:43+00:00","by":"729fa027-7e7e-4b06-9602-f535c43824f0","backedBy":"","impersonatedBy":"","from":"","client":"server"},"t":1776676903926,"ct":1721209936826,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"d338debc-06a4-481e-9e81-9a79dbe26e4d","v":"6980ae7aacb0ce71","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"children","template":"<div class=\"ae-form-cell__input\" style=\"height:100%\">\n  <ae-layout v-if=\"params.addToBeginning && controller.canAdd\"\n    :layout-id=\"params.layoutIdAdd || '64eaddd0-ac8a-46d4-a84f-0eb16e42b40d'\"\n    @click.native=\"controller.showNewChild()\"></ae-layout>\n\n  <ae-control name=\"ref\"\n              :document=\"document\"\n              :field-name=\"fieldName\"\n              :param-list=\"true\"\n              :param-no-add=\"true\"\n              :param-handle-selected=\"params.handleSelected\"\n              :param-handle-selected-readonly=\"params.handleSelectedReadonly\"\n              :param-handle-remove=\"params.handleRemove\"\n              :param-delete-on-remove=\"params.deleteOnRemove\"\n              :param-confirm-remove=\"params.confirmRemove\"\n              :param-design=\"params.design\"\n              :param-add-to-beginning=\"params.addToBeginning\"\n              :param-show-filter=\"params.showFilter\"\n              :param-show-reset-sorting=\"params.showResetSorting\"\n              :param-initial-filters=\"params.initialFilters\"\n              :param-initial-sort-field=\"params.initialSortField\"\n              :param-initial-sort-descending=\"params.initialSortDescending\"\n              :param-hide-path=\"params.hidePath\"\n              :param-disable-remove=\"params.disableRemove\"\n              :param-disable-sorting=\"params.disableSorting\"\n              v-if=\"!params.onlyAdd\"\n              :readonly=\"readonly\"\n              :param-filter-fields=\"params.filterFields\"\n              :param-sort-fields=\"params.sortFields\"\n              :param-inline=\"params.inline\"\n              :param-inline-layout=\"params.inlineLayout\"\n  ></ae-control>\n\n  <ae-layout v-if=\"!params.addToBeginning && controller.canAdd\"\n    :layout-id=\"params.layoutIdAdd || '64eaddd0-ac8a-46d4-a84f-0eb16e42b40d'\"\n    @click.native.stop=\"controller.showNewChild()\"\n  ></ae-layout>\n\n  <button v-if=\"controller.canAdd && params.templatesContainerForm\"\n          class=\"ae-button\"\n          @click.stop=\"controller.showContainerSelector()\"\n          title=\"Aus Vorlage kopieren\"\n  >\n    <ae-icon class=\"fa fa-layer-plus\"></ae-icon>\n  </button>\n</div>\n\n<!-- TODO: rework child, template, different forms into NEW Dialog -->\n<!-- <ae-layout  ref=\"newDialog\"\n            layout-id=\"c1b33446-cc5a-47dd-9985-3124c4a561bc\"\n            :param-document=\"document\"\n></ae-layout> -->\n\n<ae-dialog ref=\"dialog\" :backdrop-close=\"false\" :dialog-size=\"{ 'height': Aeppic.Device === 'Desktop' ? '90vh' : null, width: Aeppic.Device === 'Desktop' ? '60vw' : null }\">\n  <div slot=\"header\" slot-scope=\"scope\" class=\"_CSS-dialog__header\">\n    <div class=\"_CSS-forms\" v-if=\"locals.forms.length > 1\">\n      <button   v-for=\"(formId, index) of locals.forms\"\n                class=\"ae-button\"\n                :class=\"{ 'ae-button--secondary': index === locals.selectedFormIndex }\"\n                @click.stop=\"controller.addNewChildOfForm(formId, index)\"\n      >\n        <ae-design name=\"title\" :document-id=\"formId\"></ae-design>\n      </button>  \n    </div>\n    <ae-design v-else-if=\"locals.newChild\" style=\"display:inline-block;\" name=\"title\" :document-id=\"locals.newChild.f.id\"></ae-design>\n  </div>\n  <div slot=\"body\" @ae-drill-into.stop.prevent @ae-will-navigate.stop.prevent>\n    <template v-if=\"params.showTemplates || params.onlyTemplate\">\n      <div class=\"ae-form-cell ae-control-container\"  >\n        <div class=\"ae-form-cell__label\">Vorlage</div>\n        <select v-if=\"locals.templates.length\"\n                @change=\"controller.cloneNewChild($event.target.value)\" \n                v-model=\"locals.selectedTemplateIndex\"\n        >\n          <option v-if=\"!params.onlyTemplate\"></option>\n          <option v-for=\"(template, index) in locals.templates\"\n                  :value=\"index\">\n            <ae-design name=\"title\" :document=\"template\"></ae-design>\n          </option>\n        </select>\n        <div v-else class=\"ae-form-cell__input\">\n          Keine Vorlagen vorhanden.\n        </div>\n      </div>\n      <hr>\n    </template>\n  \n    <ae-form v-if=\"locals.newChild\"\n        :document=\"locals.newChild\"\n        :key=\"locals.newChild.id + locals.newChild.v\"\n      ></ae-form>\n  </div>\n  \n\t<div slot=\"commands\" slot-scope=\"scope\" class=\"_CSS-dialog__commands\">\n    <div v-if=\"locals.newDialog\" class=\"_CSS-dialog__commands\">\n      <template v-for=\"(command, index) of locals.newDialog.data.commands.concat().reverse()\">\n        <ae-layout  layout-id=\"32cea84f-c22f-4e6f-a0bf-1ce332ee5c5a\"\n                    :param-fallback-command=\"command\"\n                    :param-document=\"locals.newChild\"\n                    :param-field-name=\"fieldName\"\n                    :param-control-params=\"params\"\n                    :param-primary-button=\"index === locals.newDialog.data.commands.length - 1\"\n                    @executed=\"scope.close()\"\n        ></ae-layout>\n      </template>\n    </div>\n    <!-- <button class=\"ae-button\" @click=\"scope.close()\">Abbrechen</button>\n\t\t<button class=\"ae-button ae-button--primary\" @click=\"controller.saveNewChild()\">Speichern</button> -->\n\t</div>\n</ae-dialog>\n\n<ae-dialog ref=\"containerDialog\" :backdrop-close=\"false\" :dialog-size=\"{ height: '40vh' }\">\n  <template #header=\"scope\">\n    <div class=\"_CSS-dialog__header\">\n      Aus Vorlage kopieren\n    </div>\n  </template>\n  <template #body>\n    <div @ae-drill-into.stop.prevent @ae-will-navigate.stop.prevent>\n      <ae-layout v-if=\"!locals.selectedContainer\" layout-id=\"36649f27-5869-49e6-9b49-3b5c3b9c795d\" :param-settings=\"locals.containerSearchSettings\" :param-hide-path=\"true\" @selected=\"locals.selectedContainer = $event\"></ae-layout>\n      <div v-else class=\"_CSS-selected-container\">\n        <div class=\"_CSS-selected-container__target\">\n          <ae-design name=\"title\" :document=\"locals.selectedContainer\" style=\"min-width: 0;\"></ae-design>\n          <button class=\"ae-button\" @click.stop=\"locals.selectedContainer = null\">\n            <ae-icon class=\"fa fa-times\"></ae-icon>\n          </button>\n        </div>\n\n        <div class=\"_CSS-selected-container__body\">\n          <ae-layout layout-id=\"acd83791-970f-4b6e-bebd-d7f3bca52014\" :param-document=\"locals.selectedContainer\" :param-readonly=\"true\" :param-fields-to-display=\"[params.templatesContainerField]\"></ae-layout>\n          <!-- <ae-form :readonly=\"true\" :document=\"locals.selectedContainer\"></ae-form> -->\n        </div>\n\n      </div>\n    </div>\n  </template>\n  <template #commands=\"scope\">\n    <button class=\"ae-button\" @click.stop=\"scope.close()\">\n      Abbrechen\n    </button>\n    <button class=\"ae-button ae-button--primary\" @click.stop=\"controller.copyContainerTemplates();scope.close()\" :disabled=\"!locals.selectedContainer\">\n      Kopieren\n    </button>\n  </template>\n</ae-dialog>\n\n\n ","css":"._CSS-child--multiple~._CSS-child--multiple > * {\n  border-top: none;\n}\n\n/* DIALOG */\n\n._CSS-dialog__header {\n  flex: 1;\n  display: flex;\n  justify-content: center;\n  flex-flow: column;\n}\n\n._CSS-dialog__commands {\n  flex: 1;\n  display: flex;\n  justify-content: flex-end;\n}\n\n._CSS-forms {\n  xborder-bottom: var(--ae-border-width) solid var(--ae-border-color);\n  padding: var(--bp-dialog-content-padding) var(--bp-dialog-content-padding) var(--bp-dialog-content-padding);\n  background: var(--ae-ui-background);\n  margin: calc(-1 * var(--bp-dialog-content-padding));\n}\n\n._CSS-selected-container__target {\n  background: var(--ae-ui-background);\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  border-radius: var(--ae-border-radius);\n  padding: var(--ae-space-2);\n\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}","controller":"class Controller {\n  init() {\n    // NOTE: newDialog is only used to render the dialog's buttons for now\n    this.NEW_DIALOG_ID = 'd22d4209-e481-478a-af6a-be2d3b8bcf1b'\n    locals.newDialog = null\n\n    locals.selectedTemplateIndex = null\n    locals.newChild = null\n    locals.templates = []\n\n    locals.selectedFormIndex = 0\n    locals.forms = []\n\n    locals.containerSearchSettings = {\n      filter: {\n        targetDocument: document,\n        ancestorForm: params.templatesContainerAncestorForm,\n        formIds: params.templatesContainerForm\n      }\n    }\n    locals.selectedContainer = null\n  }\n\n  readFormFieldId() {\n    const formField = params.formField && document.data[params.formField] \n\n    if (formField && formField.id) {\n      return formField.id\n    } else if (formField) {\n      return formField\n    } else {\n      return fieldInfo.formRef ? fieldInfo.formRef : null\n    }\n  }\n\n  async getAvailableFormIds() {\n    const forms = []\n\n    if (this.readFormFieldId()) {\n      forms.push(this.readFormFieldId())\n    }\n\n    if (params.forms) {\n      const fetched = await Aeppic.getAll(params.forms.split(','))\n      forms.push(...fetched.map(f => f ? f.id : null))\n    }\n\n    return [...new Set(forms.filter(f => f !== null))]\n  }\n\n  get canAdd() {\n    if (params.noAdd || readonly) {\n      return false\n    }\n\n    const field = getField()\n\n    if (!fieldInfo.cardinality) {\n      return (!field.id)\n    }\n    \n    if (fieldInfo.cardinality.max === 'n') {\n      return true\n    }\n    \n    return field.length < Number(fieldInfo.cardinality.max)\n  }\n\n  async showNewChild() {\n    locals.newChild = null\n\n    locals.forms = await this.getAvailableFormIds()\n    locals.selectedFormIndex = 0\n    \n    locals.selectedTemplateIndex = null\n    locals.newDialog = await Aeppic.get(this.NEW_DIALOG_ID)\n\n    if (params.showTemplates || params.onlyTemplate) {\n      const parts = []\n\n      const formId = this.readFormFieldId()\n      \n      parts.push(`f.id:${formId}`)\n      \n      const limit = await this.queryLimit('template')\n      if (limit) {\n        parts.push(limit)\n      }\n\n      const templateSearch = parts.join(' AND ')\n  \n      locals.templates = await Aeppic.find(templateSearch)\n    }\n\n    if (params.onlyTemplate) {\n      locals.selectedTemplateIndex = 0\n      await this.cloneNewChild(0)\n\n      if (locals.templates.length === 1) {\n        await this.saveNewChild()\n\n        return\n      }\n\n      $refs.dialog.show()\n      return\n    }\n\n    if (locals.forms.length) {\n      await this.addNewChild(locals.forms[0])\n      locals.selectedTemplateIndex = 0\n    } else {\n      await this.addNewChild()\n    }\n\n    // TODO: there is much more to support with NEW Dialog\n    // $refs.newDialog.$emit('show', {\n    //   command: params.newCommand,\n    //   dialog: this.NEW_DIALOG_ID,\n    // })\n\n    $refs.dialog.show()\n  }\n\n  async _executeCommand(commandId, doc, commandParams) {\n    console.log(commandId, doc)\n    const commandResult = await Aeppic.Commands.execute(commandId, doc, commandParams)\n\n    if (!commandResult.ok || (commandResult.value && 'ok' in commandResult.value && !commandResult.value.ok)) {\n      const message = commandResult.value && commandResult.value.message || 'error:execution'\n\n      locals.executionError = message\n      console.error(commandResult)\n      \n      return false\n    }\n\n    let result = commandResult.value\n\n    if (commandResult.value != null) {\n      if ('value' in commandResult.value) {\n        result = commandResult.value.value\n      } else {\n        result = commandResult.value\n      }\n    }\n\n    if (result && !Aeppic.isEditableDocument(result)) {\n      const resultAlreadyImported = await Aeppic.get(result)\n\n      if (!resultAlreadyImported) {\n        await Aeppic.import(result)\n      }\n\n      result = await Aeppic.edit(result)\n    }\n\n    return result || true\n  }\n\n  async queryLimit(paramPrefix = '') {\n    const parts = []\n \n    const ancestorSearchLimit = this._prefixParam('ancestorSearchLimit', paramPrefix)\n    if (ancestorSearchLimit != null) {\n      if (document.a.length < ancestorSearchLimit) {\n        console.error(`${this._prefixParam('ancestorSearchLimit', paramPrefix)} is out of bounds: limit is greater than document ancestors`)\n      } else {\n        parts.push(`a:${document.a[document.a.length - ancestorSearchLimit]}`)\n      }\n    }\n\n    const ancestor = this._prefixParam('ancestor', paramPrefix)\n    if (ancestor != null) {\n      parts.push(this._fieldIdsAsQueryString(form.getField(ancestor), document.data[ancestor], 'a:'))\n    }\n\n    const ancestorForm = this._prefixParam('ancestorForm', paramPrefix)\n    if (ancestorForm) {\n      const ancestorIndex = document['a_forms'].findIndex(formId => formId === ancestorForm)\n\n      if (ancestorIndex === -1) {\n        console.error(`ancestorForm is out of bounds: ${ancestorForm} is not in a_forms`)\n      } else {\n        const ancestorId = document.a[ancestorIndex]\n\n        parts.push(`a:${ancestorId}`)\n      }\n    } \n\n    const parent = this._prefixParam('parent', paramPrefix)\n    if (parent != null) {\n      parts.push(this._fieldIdsAsQueryString(form.getField(parent), document.data[parent], 'p:'))\n    }\n\n    const parentId = this._prefixParam('parentId', paramPrefix)\n    if (parentId != null) {\n      parts.push(`p:${parentId}`)\n    }\n\n    const locationForm = this._prefixParam('locationForm', paramPrefix)\n    const locationSearchLimit = this._prefixParam('locationSearchLimit', paramPrefix)\n\n    if ((locationForm != null && locationSearchLimit == null) || (locationForm == null && locationSearchLimit != null)) {\n      console.warn(`params locationForm (\"${locationForm}\") and locationSearchLimit (${locationSearchLimit}) have to be used together.`)\n    }\n\n    if (locationForm != null && locationSearchLimit != null) {\n      const depth = document.a.length - locationSearchLimit\n      const location = (await Aeppic.find(`a:${document.a[depth > 0 ? depth : 0]} AND f.id:${locationForm}`))[0]\n\n      if (location == null) {\n        console.warn(`Could not find a valid location`)\n        parts.push('p:invalid-location')\n      } else {\n        parts.push(`a:${location.id}`)\n      }\n    }\n\n    return parts.join(' AND ')\n  }\n\n  _prefixParam(param, prefix) {\n    if (!prefix || !param) {\n      return params[param]\n    }\n\n    const path = `${prefix}${param[0].toUpperCase()}${param.slice(1)}`\n    return params[path]\n  } \n\n  _fieldIdsAsQueryString(fieldsInfo, values, stringToPrefixValue) {\n    const parts = []\n\n    const isRef = fieldsInfo.subType === 'ref'\n\n    if (fieldsInfo.cardinality) {\n      for (const value of values) {\n        const id = isRef ? value.id : value\n        parts.push(`${stringToPrefixValue}${id}`)\n      }\n    } else {\n      const id = isRef ? values.id : values\n      parts.push(`${stringToPrefixValue}${id}`)\n    }\n\n    return parts.join(' OR ')\n  }\n\n  async addNewChildOfForm(formParam, selectedFormIndex) {\n    locals.selectedFormIndex = selectedFormIndex || 0\n    await this.addNewChild(formParam)\n  }\n\n  async addNewChild(overwriteFormId) {\n    await this.deleteNewChild()\n\n    const formId = overwriteFormId || this.readFormFieldId()\n    console.log('showNewChild:params', params, formId)\n\n    let newChild = null\n\n    if (params.newCommand) {\n      const commandResult = await this._executeCommand(params.newCommand, document, { formId })\n      // const commandResult = await Aeppic.Commands.execute(params.newCommand, document, { formId })\n\n      if (commandResult && commandResult.f.id === formId) {\n        newChild = commandResult\n      } else {\n        throw new Error(`New Command ${params.newCommand} did not return a Document or Document is not of formId ${formId}: ${commandResult?.id} ${commandResult?.f.id}`)\n      }\n    } else {\n      newChild = await Aeppic.new(formId, document.id)\n    }\n\n    newChild.hidden = true\n\n    Aeppic.save(newChild)\n\n    locals.newChild = newChild\n  }\n\n  async cloneNewChild(templateIndex) {\n    this.deleteNewChild() \n\n    if (templateIndex == null || templateIndex === '') {\n      this.addNewChild()\n      locals.selectedTemplateIndex = null\n      return\n    }\n\n    const cloneDeepResult = await Aeppic.cloneDeep(locals.templates[templateIndex], document.id, { save: true })\n\n    locals.newChild = cloneDeepResult.root\n  }\n\n  saveNewChild() {\n    Aeppic.save(locals.newChild)\n\n    document.addReference(fieldName, locals.newChild, {\n      addToBeginning: params.addToBeginning\n    })\n\n    // NOTE: the document was automatically saved until 2019-04-16 but this should not be necessary\n    // Aeppic.save(document)\n\n    locals.newChild = null\n\n    this.closeDialog()\n  }\n\n  closeDialog() {\n    $refs.dialog && $refs.dialog.close()\n    locals.newChild = null\n  }\n\n  deleteNewChild() {\n    if (locals.newChild) {\n      Aeppic.delete(locals.newChild)\n    }\n    locals.newChild = null\n  }\n\n  destroy() {\n    this.closeDialog()\n  }\n\n  showContainerSelector() {\n    locals.selectedContainer = null \n    \n    $refs.containerDialog.show()\n  }\n\n  async copyContainerTemplates() {\n    const availableFormIds = await this.getAvailableFormIds()\n    const toCopy = await Aeppic.getAll(locals.selectedContainer.data[params.templatesContainerField])\n\n    for (const copy of toCopy) {\n      if (!copy || !availableFormIds.includes(copy.f.id)) {\n        continue\n      }\n\n      const cloneResult = await Aeppic.cloneDeep(copy, document, { save: true })\n      document.addReference(fieldName, cloneResult.root)\n    }\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":"# Usage\n\n## Params\n\n\n| param                       | values     | description                                                                                                                                            |\n| --------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| onlyTemplate                | boolean    |                                                                                                                                                        |\n| showTemplates               | boolean    |                                                                                                                                                        |\n| noAdd                       | boolean    |                                                                                                                                                        |\n| addToBeginning              | boolean    |                                                                                                                                                        |\n| ancestorSearchLimit         | number     |                                                                                                                                                        |\n| ancestor                    | field-name |                                                                                                                                                        |\n| parent                      | field-name |                                                                                                                                                        |\n| locationForm                | form-id    | `special` only works together with `locationSearchLimit`. A location is a document to look for. The found document will be used as additional ancestor |\n| locationSearchLimit         | number     | `special` only works together with `locationForm`                                                                                                      |\n| templateAncestorSearchLimit | number     | same as parameter without `template`-prefix                                                                                                            |\n| templateAncestor            | field-name | same as parameter without `template`-prefix                                                                                                            |\n| templateParent              | field-name | same as parameter without `template`-prefix                                                                                                            |\n| templateLocationForm        | form-id    | same as parameter without `template`-prefix                                                                                                            |\n| templateLocationSearchLimit | number     | same as parameter without `template`-prefix                                                                                                            |\n\n# Changelog"},"locks":[],"created":{"at":"2019-03-27T14:48:26+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2022-11-14T13:03:55+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936826,"ct":1721209936826,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"d3d04144fd2e552a","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-10-26T08:20:19+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936826,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    // NOTE: newDialog is only used to render the dialog's buttons for now\n    // this.NEW_DIALOG_ID = 'd22d4209-e481-478a-af6a-be2d3b8bcf1b'\n    // locals.newDialog = null\n\n    // locals.selectedTemplateIndex = null\n    // locals.newChild = null\n    locals.templates = []\n    locals.templatesOptions = []\n\n    locals.selectedFormIndex = 0\n    locals.forms = []\n    locals.formsOptions = []\n    locals.commandsOptions = []\n\n    // initially is false if command needs to be checked\n    locals.canEditBasedOnCommand = params.canEditCommand ? false : null\n\n    // locals.containerSearchSettings = {\n    //   filter: {\n    //     targetDocument: document,\n    //     ancestorForm: params.templatesContainerAncestorForm,\n    //     formIds: params.templatesContainerForm\n    //   }\n    // }\n    // locals.selectedContainer = null\n\n    this.updateCanEditBasedOnCommand()\n\n    await this.loadFormsOptions()\n  }\n\n  async updateCanEditBasedOnCommand() {\n    if (!params.canEditCommand) {\n      return\n    }\n\n    const result = await Aeppic.Commands.execute(params.canEditCommand, document, { Account: Aeppic.Account })\n\n    if (result.error) {\n      console.error(result.error)\n    }\n\n    locals.canEditBasedOnCommand = result.ok && result.value?.ok === true\n  }\n\n  readFormFieldId() {\n    const formField = params.formField && document.data[params.formField]\n\n    if (formField && formField.id) {\n      return formField.id\n    } else if (formField) {\n      return formField\n    } else {\n      return fieldInfo.formRef ? fieldInfo.formRef : null\n    }\n  }\n\n  async loadFormsOptions() {\n    locals.forms = await this.getAvailableFormIds()\n\n    locals.formsOptions = locals.forms.map((f) => {\n      return { documentId: f, iconDesign: true }\n    })\n  }\n\n  async getAvailableFormIds() {\n    const forms = []\n\n    if (this.readFormFieldId()) {\n      forms.push(this.readFormFieldId())\n    }\n\n    if (params.forms) {\n      const fetched = await Aeppic.getAll(params.forms.split(','))\n      forms.push(...fetched.map((f) => (f ? f.id : null)))\n    }\n\n    return [...new Set(forms.filter((f) => f !== null))]\n  }\n\n  get canAdd() {\n    if (params.noAdd) {\n      return false\n    }\n\n    if (readonly) {\n      if (locals.canEditBasedOnCommand === null) {\n        return false\n      }\n    }\n\n    if (locals.canEditBasedOnCommand === false) {\n      return false\n    }\n\n    // if (params.noAdd || readonly) {\n    //   return false\n    // }\n\n    const field = getField()\n\n    if (!fieldInfo.cardinality) {\n      return !field.id\n    }\n\n    if (fieldInfo.cardinality.max === 'n') {\n      return true\n    }\n\n    return field.length < Number(fieldInfo.cardinality.max)\n  }\n\n  async _executeCommand(commandId, doc, commandParams) {\n    const commandResult = await Aeppic.Commands.execute(commandId, doc, commandParams)\n\n    if (!commandResult.ok || (commandResult.value && 'ok' in commandResult.value && !commandResult.value.ok)) {\n      const message = (commandResult.value && commandResult.value.message) || 'error:execution'\n\n      locals.executionError = message\n      console.error(commandResult)\n\n      return false\n    }\n\n    let result = commandResult.value\n\n    if (commandResult.value != null) {\n      if ('value' in commandResult.value) {\n        result = commandResult.value.value\n      } else {\n        result = commandResult.value\n      }\n    }\n\n    if (result && !Aeppic.isEditableDocument(result)) {\n      const resultAlreadyImported = await Aeppic.get(result)\n\n      if (!resultAlreadyImported) {\n        await Aeppic.import(result)\n      }\n\n      result = await Aeppic.edit(result)\n    }\n\n    return result || true\n  }\n\n  async queryLimit(paramPrefix = '') {\n    const parts = []\n\n    const ancestorSearchLimit = this._prefixParam('ancestorSearchLimit', paramPrefix)\n    if (ancestorSearchLimit != null) {\n      if (document.a.length < ancestorSearchLimit) {\n        console.error(\n          `${this._prefixParam(\n            'ancestorSearchLimit',\n            paramPrefix\n          )} is out of bounds: limit is greater than document ancestors`\n        )\n      } else {\n        parts.push(`a:${document.a[document.a.length - ancestorSearchLimit]}`)\n      }\n    }\n\n    const ancestor = this._prefixParam('ancestor', paramPrefix)\n    if (ancestor != null) {\n      parts.push(this._fieldIdsAsQueryString(form.getField(ancestor), document.data[ancestor], 'a:'))\n    }\n\n    const ancestorForm = this._prefixParam('ancestorForm', paramPrefix)\n    if (ancestorForm) {\n      const ancestorIndex = document['a_forms'].findIndex((formId) => formId === ancestorForm)\n\n      if (ancestorIndex === -1) {\n        console.error(`ancestorForm is out of bounds: ${ancestorForm} is not in a_forms`)\n      } else {\n        const ancestorId = document.a[ancestorIndex]\n\n        parts.push(`a:${ancestorId}`)\n      }\n    }\n\n    const ancestorIndex = this._prefixParam('ancestorIndex', paramPrefix)\n    if (ancestorIndex != null) {\n      parts.push(`a:${document.a[ancestorIndex]}`)\n    }\n\n    const parent = this._prefixParam('parent', paramPrefix)\n    if (parent != null) {\n      parts.push(this._fieldIdsAsQueryString(form.getField(parent), document.data[parent], 'p:'))\n    }\n\n    const parentId = this._prefixParam('parentId', paramPrefix)\n    if (parentId != null) {\n      parts.push(`p:${parentId}`)\n    }\n\n    const locationForm = this._prefixParam('locationForm', paramPrefix)\n    const locationSearchLimit = this._prefixParam('locationSearchLimit', paramPrefix)\n\n    // if ((locationForm != null && locationSearchLimit == null) || (locationForm == null && locationSearchLimit != null)) {\n    //   console.warn(`params locationForm (\"${locationForm}\") and locationSearchLimit (${locationSearchLimit}) have to be used together.`)\n    // }\n\n    if (locationForm != null && locationSearchLimit != null) {\n      const location = (\n        await Aeppic.find(`a:${document.a[document.a.length - locationSearchLimit]} AND f.id:${locationForm}`)\n      )[0]\n\n      if (location == null) {\n        console.warn(`Could not find a valid location`)\n        parts.push('p:invalid-location')\n      } else {\n        parts.push(`a:${location.id}`)\n      }\n    }\n\n    if (locationForm != null) {\n      parts.push(`a_forms:${locationForm}`)\n    }\n\n    return parts.join(' AND ')\n  }\n\n  _prefixParam(param, prefix) {\n    if (!prefix || !param) {\n      return params[param]\n    }\n\n    const path = `${prefix}${param[0].toUpperCase()}${param.slice(1)}`\n    return params[path]\n  }\n\n  _fieldIdsAsQueryString(fieldsInfo, values, stringToPrefixValue) {\n    const parts = []\n\n    const isRef = fieldsInfo.subType === 'ref'\n\n    if (fieldsInfo.cardinality) {\n      for (const value of values) {\n        const id = isRef ? value.id : value\n        parts.push(`${stringToPrefixValue}${id}`)\n      }\n    } else {\n      const id = isRef ? values.id : values\n      parts.push(`${stringToPrefixValue}${id}`)\n    }\n\n    return parts.join(' OR ')\n  }\n\n  // async addNewChildOfForm(formParam, selectedFormIndex) {\n  //   locals.selectedFormIndex = selectedFormIndex || 0\n  //   await this.addNewChild(formParam)\n  // }\n\n  async addNewChild(overwriteFormId) {\n    // REDESIGN: remove next line?\n    // await this.deleteNewChild()\n\n    const formId = overwriteFormId || this.readFormFieldId()\n    console.log('showNewChild:params', params, formId)\n\n    let newChild = null\n\n    if (params.newCommand) {\n      const result = await this._executeCommand(params.newCommand, document, { formId })\n\n      if (result?.f.id === formId) {\n        newChild = result\n      } else {\n        throw new Error(\n          `New Command ${params.newCommand} did not return a Document or Document is not of formId ${formId}: ${result?.id} ${result?.f.id}`\n        )\n      }\n    } else {\n      newChild = await Aeppic.new(formId, document.id)\n    }\n\n    newChild.hidden = true\n\n    Aeppic.save(newChild)\n\n    return newChild\n  }\n\n  async loadCommandsOptions() {\n    if (!params.newCommands) {\n      return []\n    }\n\n    locals.commandsOptions = params.newCommands.split(',').map((commandId) => ({ documentId: commandId }))\n\n    const commands = await Aeppic.getAll(params.newCommands.split(','))\n\n    locals.commandsOptions = commands.map((command) => {\n      return {\n        documentId: command.id,\n        iconDesign: true,\n        command, // not an option, but used in this Component\n      }\n    })\n  }\n\n  async loadTemplatesOptions() {\n    const parts = []\n\n    if (params.templateForm) {\n      parts.push(`f.id:${params.templateForm}`)\n    } else {\n      const forms = await this.getAvailableFormIds()\n      const formParts = []\n\n      forms.forEach((formId) => formParts.push(`f.id:${formId}`))\n\n      parts.push(`(${formParts.join(' OR ')})`)\n    }\n\n    // const formId = this.readFormFieldId()\n    // parts.push(`f.id:${formId}`)\n\n    const limit = await this.queryLimit('template')\n\n    if (limit) {\n      parts.push(limit)\n    }\n\n    const templateSearch = parts.join(' AND ')\n\n    let templates = await Aeppic.find(templateSearch)\n\n    if (params.templateParentForm) {\n      templates = templates.filter((doc) => doc.a_forms?.slice(-1)[0] === params.templateParentForm)\n    }\n\n    locals.templates = templates\n\n    locals.templatesOptions = locals.templates.map((template) => {\n      return {\n        documentId: template.id,\n        template, // not an option, but used in this Component\n      }\n    })\n  }\n\n  async createChildAndShowByTemplate(templateId, options) {\n    if (!params.templateFormField) {\n      this.createChildAndShow(templateId, options)\n      return\n    }\n\n    // clone references from a given field in the template and add the clones to this document. Resolve within the template.data by params.templateFormField\n\n    const template = await Aeppic.get(templateId)\n\n    const formId = this.readFormFieldId()\n\n    const validReferences = (await Aeppic.getAll(template.data[params.templateFormField])).filter(\n      (r) => !!r && r.f.id === formId\n    )\n\n    const cloneResults = await Promise.all(\n      validReferences.map(async (ref) => Aeppic.cloneDeep(ref, document, { save: true, readonly: false }))\n    )\n\n    cloneResults.forEach((cloneResult) => {\n      document.addReference(fieldInfo.name, cloneResult.root)\n    })\n  }\n\n  async createChildAndShowByCommand(commandId) {\n    const newChild = await this._executeCommand(commandId, document)\n\n    await this._showChild(newChild)\n  }\n\n  async createChildAndShow(documentId, options) {\n    let newChild = null\n\n    if (options?.clone) {\n      if (params.templateTransformerAction) {\n        const cloneResult = await Aeppic.cloneDeep(documentId, document.id, { readonly: false })\n\n        const actionResults = await Aeppic.Actions.run([params.templateTransformerAction], { root: cloneResult.root, documents: cloneResult.documents })\n        const actionResult = actionResults[0]\n\n        if (!actionResult.ok) {\n          console.error(actionResult.error)\n          return\n        }\n\n        if (!actionResult.value.documents || !actionResult.value.root) {\n          console.error(`params.templateTransformerAction ${params.templateTransformerAction} did not return {root,documents} as result`)\n          return\n        }\n\n        Aeppic.saveAll([actionResult.value.root, ...actionResult.value.documents])\n        \n        newChild = actionResult.value.root\n      } else {\n        const cloneResult = await Aeppic.cloneDeep(documentId, document.id, { save: true, readonly: false })\n        newChild = cloneResult.root\n      }\n    } else {\n      newChild = await this.addNewChild(documentId)\n    }\n\n    await this._showChild(newChild)\n  }\n\n  async _showChild(editableNewChild) {\n    const saveCommandParams = { fieldName, addToBeginning: params.addToBeginning }\n\n    const dialogId = params.dialogId || 'b000b033-4640-4dcc-9f69-24717aa1bae4'\n    const dialog = await Aeppic.get(dialogId)\n\n    if (params.navigate || params.noDialog) {\n      const commands = await Aeppic.getAll(dialog.data.commands)\n      const saveCommand = commands.find((c) => c?.data.name.trim() === 'save')\n\n      const result = await this._executeCommand(saveCommand, editableNewChild, saveCommandParams)\n\n      if (params.navigate) {\n        if (typeof result === 'boolean') {\n          Aeppic.drillInto(editableNewChild.id, { focus: true })\n        } else {\n          Aeppic.drillInto(result, { focus: true })\n        }\n      }\n    } else {\n      $refs.executionDialog.$emit('show', { dialog, document: editableNewChild, commandParams: saveCommandParams })\n    }\n  }\n}\n","css":" \n/* DIALOG */\n\n._CSS-dialog__header {\n  flex: 1;\n  display: flex;\n  justify-content: center;\n  flex-flow: column;\n}\n\n._CSS-dialog__commands {\n  flex: 1;\n  display: flex;\n  justify-content: flex-end;\n}\n\n._CSS-forms {\n  xborder-bottom: var(--ae-border-width) solid var(--ae-border-color);\n  padding: var(--bp-dialog-content-padding) var(--bp-dialog-content-padding) var(--bp-dialog-content-padding);\n  background: var(--ae-ui-background);\n  margin: calc(-1 * var(--bp-dialog-content-padding));\n}\n\n._CSS-selected-container__target {\n  background: var(--ae-ui-background);\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  border-radius: var(--ae-border-radius);\n  padding: var(--ae-space-2);\n\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}","description":"","device":"ALL","label":"","limitTypes":false,"name":"children-add","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<div v-if=\"controller.canAdd\" class=\"flex gap-1\" :data-test=\"fieldName + '_add'\">\n  <ae-control v-if=\"params.additionalAddControl\"\n                :name=\"params.additionalAddControl\"\n               :document=\"document\"\n               :field-name=\"fieldName\"\n               :params=\"params\"\n               :readonly=\"!controller.canAdd\"\n  ></ae-control>\n\n  <ae-layout v-if=\"params.showTemplates || params.onlyTemplate\"\n    layout-id=\"8bc795f2-7ac8-4b95-9019-0b27b178a36e\" \n    :param-options=\"locals.templatesOptions\"\n    :param-filter-fields=\"params.addFilterFields\"\n    @show-picker=\"controller.loadTemplatesOptions()\"\n    @selected=\"controller.createChildAndShowByTemplate($event.documentId, { clone: true })\"\n    :param-button-params=\"{ text: 'Aus Vorlage', icon: 'far fa-layer-plus', classes: 'text-xs', border: 'border' }\"\n  ></ae-layout>\n\n    <ae-layout v-if=\"params.newCommands && !params.onlyTemplate\"\n    layout-id=\"8bc795f2-7ac8-4b95-9019-0b27b178a36e\" \n    :param-options=\"locals.commandsOptions\" \n    @show-picker=\"controller.loadCommandsOptions()\"\n    @selected=\"controller.createChildAndShowByCommand($event.documentId)\"\n    :param-button-params=\"{ text: 'Neu', icon: 'far fa-layer-plus', classes: 'text-xs', border: 'border' }\"\n  ></ae-layout>\n\n  <template v-if=\"!params.onlyTemplate && !params.newCommands\">\n    <ae-layout v-if=\"locals.forms.length === 1\"\n              layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n              @selected=\"controller.createChildAndShow(locals.formsOptions[0].documentId)\"\n              param-text=\"Neu\"\n              param-icon=\"far fa-plus\"\n              param-classes=\"border border-neutral-200 text-xs rounded-full\"\n    ></ae-layout>\n    <ae-layout v-else-if=\"locals.forms.length >= 2\"\n              layout-id=\"8bc795f2-7ac8-4b95-9019-0b27b178a36e\" \n              :param-options=\"locals.formsOptions\" \n              @selected=\"controller.createChildAndShow($event.documentId)\"\n              :param-button-params=\"{ text: 'Neu', icon: 'far fa-plus-octagon', classes: 'text-xs', border: 'border' }\"\n    ></ae-layout>\n    <ae-layout  v-else\n                layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n                @selected=\"controller.createChildAndShow()\"\n                param-text=\"Neu\"\n                param-icon=\"far fa-plus\"\n                param-classes=\"border border-neutral-200 text-xs rounded-full\"\n                ref=\"button\"\n    ></ae-layout>  \n  </template>\n</div>     \n\n\n<ae-layout  layout-id=\"5886531d-7ae8-47ec-8b16-adc3378fcd1f\" \n            ref=\"executionDialog\"\n></ae-layout>\n\n\n\n<!-- <ae-dialog ref=\"dialog\" :backdrop-close=\"false\" :dialog-size=\"{ 'height': Aeppic.Device === 'Desktop' ? '90vh' : null, width: Aeppic.Device === 'Desktop' ? '60vw' : null }\">\n  <div slot=\"header\" slot-scope=\"scope\" class=\"_CSS-dialog__header\">\n    <div class=\"_CSS-forms\" v-if=\"locals.forms.length > 1\">\n      <button   v-for=\"(formId, index) of locals.forms\"\n                class=\"ae-button\"\n                :class=\"{ 'ae-button--secondary': index === locals.selectedFormIndex }\"\n                @click.stop=\"controller.addNewChildOfForm(formId, index)\"\n      >\n        <ae-design name=\"title\" :document-id=\"formId\"></ae-design>\n      </button>  \n    </div>\n    <ae-design v-else-if=\"locals.newChild\" style=\"display:inline-block;\" name=\"title\" :document-id=\"locals.newChild.f.id\"></ae-design>\n  </div>\n  <div slot=\"body\" @ae-drill-into.stop.prevent @ae-will-navigate.stop.prevent>\n    <template v-if=\"params.showTemplates || params.onlyTemplate\">\n      <div class=\"ae-form-cell ae-control-container\"  >\n        <div class=\"ae-form-cell__label\">Vorlage</div>\n        <select v-if=\"locals.templates.length\"\n                @change=\"controller.cloneNewChild($event.target.value)\" \n                v-model=\"locals.selectedTemplateIndex\"\n        >\n          <option v-if=\"!params.onlyTemplate\"></option>\n          <option v-for=\"(template, index) in locals.templates\"\n                  :value=\"index\">\n            <ae-design name=\"title\" :document=\"template\"></ae-design>\n          </option>\n        </select>\n        <div v-else class=\"ae-form-cell__input\">\n          Keine Vorlagen vorhanden.\n        </div>\n      </div>\n      <hr>\n    </template>\n  \n    <ae-design v-if=\"locals.newChild\"\n               name=\"rd:form\" \n              :document=\"locals.newChild\"\n                :key=\"locals.newChild.id + locals.newChild.v\"\n    >\n    </ae-design>\n\n  </div>\n  \n\t<div slot=\"commands\" slot-scope=\"scope\" class=\"_CSS-dialog__commands\">\n    <div v-if=\"locals.newDialog\" class=\"_CSS-dialog__commands\">\n      <template v-for=\"(command, index) of locals.newDialog.data.commands.concat().reverse()\">\n        <ae-layout  layout-id=\"32cea84f-c22f-4e6f-a0bf-1ce332ee5c5a\"\n                    :param-fallback-command=\"command\"\n                    :param-document=\"locals.newChild\"\n                    :param-field-name=\"fieldName\"\n                    :param-control-params=\"params\"\n                    :param-primary-button=\"index === locals.newDialog.data.commands.length - 1\"\n                    @executed=\"scope.close()\"\n        ></ae-layout>\n      </template>\n    </div>\n\t</div>\n</ae-dialog>\n\n<ae-dialog ref=\"containerDialog\" :backdrop-close=\"false\" :dialog-size=\"{ height: '40vh' }\">\n  <template #header=\"scope\">\n    <div class=\"_CSS-dialog__header\">\n      Aus Vorlage kopieren\n    </div>\n  </template>\n  <template #body>\n    <div @ae-drill-into.stop.prevent @ae-will-navigate.stop.prevent>\n      <ae-layout v-if=\"!locals.selectedContainer\" layout-id=\"36649f27-5869-49e6-9b49-3b5c3b9c795d\" :param-settings=\"locals.containerSearchSettings\" :param-hide-path=\"true\" @selected=\"locals.selectedContainer = $event\"></ae-layout>\n      <div v-else class=\"_CSS-selected-container\">\n        <div class=\"_CSS-selected-container__target\">\n          <ae-design name=\"title\" :document=\"locals.selectedContainer\" style=\"min-width: 0;\"></ae-design>\n          <button class=\"ae-button\" @click.stop=\"locals.selectedContainer = null\">\n            <ae-icon class=\"fa fa-times\"></ae-icon>\n          </button>\n        </div>\n\n        <div class=\"_CSS-selected-container__body\">\n          <ae-layout layout-id=\"acd83791-970f-4b6e-bebd-d7f3bca52014\" :param-document=\"locals.selectedContainer\" :param-readonly=\"true\" :param-fields-to-display=\"[params.templatesContainerField]\"></ae-layout>\n        </div>\n\n      </div>\n    </div>\n  </template>\n  <template #commands=\"scope\">\n    <button class=\"ae-button\" @click.stop=\"scope.close()\">\n      Abbrechen\n    </button>\n    <button class=\"ae-button ae-button--primary\" @click.stop=\"controller.copyContainerTemplates();scope.close()\" :disabled=\"!locals.selectedContainer\">\n      Kopieren\n    </button>\n  </template>\n</ae-dialog> -->\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"8a0a9904-a461-4998-903e-115f8231abb1","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-05-22T13:11:37+00:00","backedBy":"","by":"09763886-6748-4a56-8e0e-54a2293ee96b","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"5e777162d210975a"},"stamps":[],"t":1779455497927,"v":"e23c939e60ca6114"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"cloneOf":{"id":"code-control","text":"code","v":"06d7597a2eabbca8"},"created":{"at":"2026-05-15T13:03:15+00:00","backedBy":"","by":"09763886-6748-4a56-8e0e-54a2293ee96b","client":"server","from":"","impersonatedBy":""},"ct":1778850195425,"data":{"cardinality":"SINGLE","controller":"class Controller {\n  async init() {\n    locals.theme = (await Aeppic.Preferences.getSetting('aeppic:developer:codeTheme')) || 'vs-dark'\n  }\n  async getHoverContent(word) {\n    const infos = await Aeppic.find(`id:${word}`)\n    if (!infos || !infos.length) {\n      return []\n    }\n\n    const doc = infos[0]\n\n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","css":"","description":"A control to display source","device":"ALL","label":"","limitTypes":true,"name":"code","namespace":"rd","packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","text":"Monaco Editor","v":"0a99614c3b79c920"}],"parameters":[],"standalone":"SUPPORTED","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:70vh\" v-model=\"field\" :theme=\"locals.theme\" :readonly=\"readonly\" language=\"javascript\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>","types":["string"]},"f":{"id":"control-form","text":"Control","v":"1b0f6a65ebd1cffa"},"id":"3357cb08-c4a5-43fe-a6fe-2dd5c8fdb7f8","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-05-15T13:03:27+00:00","backedBy":"","by":"09763886-6748-4a56-8e0e-54a2293ee96b","client":"server","from":"","impersonatedBy":""},"originalCloneOf":{"id":"code-control","text":"code","v":"06d7597a2eabbca8"},"p":"controls-folder","previous":{"v":"initial"},"stamps":[],"t":1778850207429,"v":"b19a44cba23698a0"},{"id":"code-control","v":"06d7597a2eabbca8","previous":{"v":"cc0c532dc96a51e6"},"p":"controls-folder","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"},"data":{"namespace":"","name":"code","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:70vh\" v-model=\"field\" :theme=\"locals.theme\" :readonly=\"readonly\" language=\"javascript\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>","css":"","controller":"class Controller {\n  async init() {\n    locals.theme = (await Aeppic.Preferences.getSetting('aeppic:developer:codeTheme')) || 'vs-dark'\n  }\n  async getHoverContent(word) {\n    const infos = await Aeppic.find(`id:${word}`)\n    if (!infos || !infos.length) {\n      return []\n    }\n\n    const doc = infos[0]\n\n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"SINGLE","limitTypes":true,"types":["string"],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0a99614c3b79c920","text":"Monaco Editor"}],"device":"ALL","label":"","description":"A control to display source"},"locks":[],"created":{"at":"2019-03-20T10:02:51+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2024-08-07T09:53:07+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1723024387605,"ct":1721196482395,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"b6871a5d-bb2c-4902-9425-926809cee7e1","v":"2c589c3a7108c6ea","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"aeppic","name":"code-control-controller","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:40em\" v-model=\"field\" :readonly=\"readonly\" :theme=\"locals.theme\" language=\"javascript\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>","css":"","controller":"class Controller {\n  async init() {\n    locals.theme = (await Aeppic.Preferences.getSetting('aeppic:developer:codeTheme')) || 'vs-dark'\n  }\n\n  async getHoverContent(word) {\n    const doc = await Aeppic.get(word)\n\n    if (!doc) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0a99614c3b79c920","text":"Monaco Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-03-20T10:00:20+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936827,"ct":1721209936827,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"b4b140011e012078","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"54bc28bc-c19c-46c5-bd9e-a4dad6a7078f","v":"1da0dd0b680407a4","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"aeppic","name":"code-design-controller","template":"<ae-expandable  :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:70vh\" v-model=\"locals.debouncedFieldValue\" @input=\"controller.debouceUpdate()\" :readonly=\"readonly\" :theme=\"locals.theme\" language=\"javascript\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>","css":"","controller":"class Controller {\n  async init() {\n    locals.debouce = null\n    locals.debouncedFieldValue = null\n    setTimeout(() => {\n      locals.debouncedFieldValue = getField()\n    })\n\n    const theme = await Aeppic.Preferences.getSetting('aeppic:developer:theme')\n    locals.theme = theme === 'ae-light' ? 'vs' : 'vs-dark'\n  }\n\n  debouceUpdate() {\n    if (locals.debouce) {\n      clearTimeout(locals.debouce)\n    }\n\n    locals.debouce = setTimeout(() => {\n      setField(locals.debouncedFieldValue)\n    }, 500)\n  }\n\n  async getHoverContent(word) {\n    const infos = await Aeppic.find(`id:${word}`)\n    if (!infos || !infos.length) {\n      return []\n    }\n\n    const doc = infos[0]\n\n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0a99614c3b79c920","text":"Monaco Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-03-20T10:02:09+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2023-01-20T15:06:05+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936827,"ct":1721209936827,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"51211497f134fca8","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"61bd694d-cfab-492a-95a0-910d233c9c7c","v":"24ba2608c6b20f31","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"aeppic","name":"code-form-functions-controller","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:40em\" v-model=\"field\" :readonly=\"readonly\" :theme=\"locals.theme\" language=\"javascript\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>","css":"","controller":"class Controller {\n  async init() {\n    locals.theme = (await Aeppic.Preferences.getSetting('aeppic:developer:codeTheme')) || 'vs-dark'\n  }\n\n  async getHoverContent(word) {\n    const doc = await Aeppic.get(word)\n\n    if (!doc) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0a99614c3b79c920","text":"Monaco Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-01-10T10:42:05+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936827,"ct":1721209936827,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"f478774a355e4a14","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"90d405f4-9c7a-4e30-85d6-e1a23fbdac57","v":"52f449d758ca4541","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"aeppic","name":"code-layout-controller","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:70vh\" v-model=\"field\" :readonly=\"readonly\" :theme=\"locals.theme\" language=\"javascript\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>","css":"","controller":"class Controller {\n  async init() {\n    locals.theme = (await Aeppic.Preferences.getSetting('aeppic:developer:codeTheme')) || 'vs-dark'\n  }\n\n  async getHoverContent(word) {\n    const doc = await Aeppic.get(word)\n\n    if (!doc) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0a99614c3b79c920","text":"Monaco Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-03-20T10:03:48+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2023-01-27T15:17:51+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936827,"ct":1721209936827,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"9201b4d62b3a8221","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"80113bf8-afae-4ab1-be62-9d53fb9f41ac","v":"f3e48de2562c35df","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"aeppic","name":"code-list-controller","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:40em\" v-model=\"field\" :readonly=\"readonly\" :theme=\"locals.theme\" language=\"javascript\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>","css":"","controller":"class Controller {\n  async init() {\n    locals.theme = (await Aeppic.Preferences.getSetting('aeppic:developer:codeTheme')) || 'vs-dark'\n  }\n\n  async getHoverContent(word) {\n    const doc = await Aeppic.get(word)\n\n    if (!doc) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-03-20T10:04:04+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936827,"ct":1721209936827,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"d7ac11bcffeea14f","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"1c74ff11-813e-49ae-9207-990c4b3fdc03","v":"8d8f25eb512ee03d","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"color","template":"<div  class=\"w-full border rounded-md p-1 flex gap-1 bg-white relative group\"\n     @click.stop.prevent=\"controller.show({ event: $event, index: -1 })\"\n>\n  <div class=\"h-5 w-5 rounded-full border border-slate-500\"\n        :style=\"{ 'background': field || 'repeating-conic-gradient(#afafaf 0% 25%, transparent 0% 50%) 50% / 8px 8px' }\"\n  >\n  </div>\n  \n  <div v-if=\"!params.noAdd && !params.readonly\"\n    class=\"text-neutral-400 absolute right-1 bottom-0 group-hover:text-sky-300\"\n  >\n    <ae-icon class=\"fa fa-caret-down\"></ae-icon>\n  </div>\n</div>\n\n<ae-popup ref=\"list\" :max-width=\"280\"> \n  <div class=\"flex flex-col p-4 gap-6\">\n    <div class=\"flex flex-wrap gap-2 text-2xl justify-start\">\n      <div v-for=\"color of locals.defaultColors\"\n            @click.stop=\"controller.setField(color)\"\n      >\n        <div class=\"h-5 w-5 rounded-full border\" \n        :style=\"{ 'background-color': color}\"\n        ></div>\n      </div>\n\n    <div @click.stop=\"controller.remove({ index })\"\n        class=\"h-5 w-5 xtext-neutral-400 px-1 bg-neutral-100 hover:bg-neutral-200 rounded-full flex items-center justify-center\"\n    >\n      <ae-icon class=\"far fa-times text-sm\"></ae-icon>\n    </div>\n    </div>\n\n    <div class=\"flex justify-between text-sm items-end\">\n      <div class=\"text-neutral-500 \">Benutzerdefinierte Farbe</div>\n\n      <div class=\"flex gap-1 items-center\">\n        <input type=\"color\" v-model=\"field\" class=\"\">\n\n        <div class=\"min-w-[60px]\">\n        {{ field }}\n        </div>\n      </div>\n    </div>  \n  </div>\n</ae-popup>\n\n \n","css":"","controller":"class Controller {\n  init() {\n    locals.editingIndex = -1\n\n    // hexcodes from 500 colors from https://tailwindcss.com/docs/customizing-colors\n    locals.defaultColors = [\n      '#64748b',\n      // '#6b7280',\n      // '#71717a',\n      // '#737373',\n      // '#78716c',\n      '#ef4444',\n      '#f97316',\n      '#f59e0b',\n      '#eab308',\n      '#84cc16',\n      '#22c55e',\n      '#10b981',\n      '#14b8a6',\n      '#06b6d4',\n      '#0ea5e9',\n      '#3b82f6',\n      '#6366f1',\n      '#8b5cf6',\n      '#a855f7',\n      '#d946ef',\n      '#f43f5e',\n      // 'black',\n      // 'white',\n    ]\n  }\n\n\n  get field() {\n    const field = getField() \n\n    if (Array.isArray(field)) {\n      return field\n    }\n\n    if (!field) {\n      return []\n    }\n\n    return [field]\n  }\n\n  remove(args) {\n    const { index } = args\n\n    let field = getField()\n\n    if (Array.isArray(field)) { \n      field.splice(index, 1) \n    } else {\n      field = ''\n    }\n\n    setField(field)\n  }\n\n  show(args) {\n    const { event, index } = args\n\n    locals.editingIndex = index\n\n    $refs.list.show(event)\n  }\n\n  setField(color) {\n    let field = getField()\n\n    if (Array.isArray(field)) {\n      if (locals.editingIndex === -1) {\n        field.push(color)        \n      } else {\n        field.splice(locals.editingIndex, 1, color)\n      }\n    } else {\n      field = color\n    }\n\n    setField(field)\n  }\n} ","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2022-09-21T13:43:53+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2023-02-15T13:38:28+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936828,"ct":1721209936828,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"63ae1ced31b08db7","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"70c5a870-e88e-4dfc-a185-2c846e6480ce","v":"766fa12a4cce401f","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"color","template":"<input type=\"color\" v-model=\"field\" style=\"width: 100%;padding: 1px 2px;\" :disabled=\"readonly\">","css":"","controller":"","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-10-12T11:48:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936827,"ct":1721209936827,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"46400d40a3565745","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"0850f4e9-65d3-4c9c-bdf1-3d18c28331ed","v":"5f81d8597e887947","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"comments","template":"<h2 style=\"color:var(--ae-foreground-l2);margin-top:3em;\">Kommentare</h2>\n<ul class=\"_CSS-list-unstyled\">\n  <li v-for=\"comment in locals.comments\" class=\"_CSS-comment\" v-if=\"controller.displayComment(comment)\">\n    <ae-design :document-id=\"comment.id\" name=\"line-item\"></ae-design>\n  </li>\n</ul>\n<template v-if=\"!controller.readonly\">\n  <ae-design v-if=\"locals.canCreate\" :document=\"locals.newComment\" name=\"new\" @save=\"controller.saveComment()\" :watch=\"false\" class=\"_CSS-add-comment\" @focus=\"controller.focus()\"></ae-design>\n  <div v-else style=\"display:flex;justify-content:center\">\n    <span style=\"font-size:small;opacity:0.5;white-space:nowrap\">Um zu kommentieren bitte anmelden</span>\n  </div>\n</template>\n","css":"._CSS-comment {\n  background: var(--ae-ui-card-background);\n  border-color: var(--ae-ui-card-border);\n  border-radius:4px;\n  border-width: 1px;\n  border-style: solid;\n  padding: 4px;\n}\n\n._CSS-comment + ._CSS-comment {\n  margin-top: 4px;\n}\n\n._CSS-content {\n  padding: 8px;\n}\n\n._CSS-list-unstyled {\n  list-style:none;\n  margin-left:0;\n  padding-left:0;\n}\n\n._CSS-date-modified-at {\n  display: flex;\n  justify-content: flex-end;\n  font-size: 0.8em;\n}\n\n._CSS-add-comment {\n}","controller":"class Controller {\n  async init() {\n    this.publishStampId = '04a939a8-6732-4f94-ad75-6f17ac417136'\n\n    locals.newComment = null\n    locals.comments = []\n    locals.canCreate = false\n    locals.canEdit = false\n    locals.profile = Aeppic.getProfilePageForAccount(document.modified.by)\n    \n    Aeppic.subscribe(`p:${document.id} f.id:cf155969-ede1-43a4-9b10-3a697b19db91`, { sort: 'created.at' }, (documents) => locals.comments = documents)\n    \n    await this.newComment()\n\n    if (!Aeppic.Account) {\n      return\n    } \n\n    if (Aeppic.Account.data.isGroupAccount) {\n      locals.canCreate = await Aeppic.Preferences.getSetting('bizpin:comments:groupAccountAllowCreate')\n    } else {\n      locals.canCreate = true\n    }\n    \n    // const canEdit = await Aeppic.canEdit(document)\n    // if (Aeppic.Account.data.isGroupAccount) {\n    //   const groupAccountCanCreate = await Aeppic.Preferences.getSetting('bizpin:comments:groupAccountAllowCreate')\n    //   locals.canCreate = (canEdit && groupAccountCanCreate)\n    // } else {\n    //   locals.canCreate = (canEdit)\n    // }\n  }\n \n  async newComment() {\n    locals.newComment = await Aeppic.new('cf155969-ede1-43a4-9b10-3a697b19db91', document.id)\n    Aeppic.commit(locals.newComment)\n  }\n\n  async saveComment() {\n    const filledOut = !!locals.newComment.data.content || locals.newComment.data.attachments.length\n\n    if (!filledOut) {\n      return\n    }\n\n    Aeppic.save(locals.newComment)\n    \n    const comment = await Aeppic.get(locals.newComment)\n    Aeppic.stamp(comment, this.publishStampId)\n\n    await this.newComment()\n  }\n\n  get readonly() {\n    if (params.readonly === true) {\n      return true\n    }\n\n    if (typeof readonly === 'boolean') {\n      return readonly\n    }\n\n    return false\n  }\n\n  async focus() {\n    if (!await Aeppic.get(locals.newComment)) {\n      Aeppic.save(locals.newComment)\n    }\n  }\n\n  displayComment(comment) {\n    const commentHasNewForm = 'attachments' in comment.data\n\n    if (commentHasNewForm) {\n      return comment.stamps && comment.stamps.find(s => s.type && s.type.id === this.publishStampId)\n    }\n\n    return true\n  }\n\n  destroy() {\n    Aeppic.delete(locals.newComment)\n  }\n}","parameters":[],"standalone":"REQUIRED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-02-19T10:51:16+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2021-10-12T12:15:47+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936828,"ct":1721209936828,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"6580810d6120843d","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"e8c64513-da11-4064-914d-ae5686795180","v":"ff134decbdc78adf","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"content-translation","template":"<ae-control :name=\"params.controlName\" :field-name=\"fieldName\" :document=\"document\"></ae-control>\n\n<h1>Übersetzungen</h1>\n \n<div class=\"_CSS-tags ae-noselect\">\n  <div v-for=\"language in locals.children\" @click=\"locals.activeLanguageChild = language\" class=\"_CSS-tag\" :class=\"{ '_CSS-tag--active': locals.activeLanguageChild && locals.activeLanguageChild.id === language.id }\">\n    <div v-if=\"language.data[params.languageFieldName] === 'de'\" title=\"Deutsch\">\n      <svg xmlns=\"http://www.w3.org/2000/svg\" id=\"flag-icon-css-de\" width=\"16\" height=\"12\" viewBox=\"0 0 640 480\">\n        <path fill=\"#ffce00\" d=\"M0 320h640v160H0z\"/>\n        <path d=\"M0 0h640v160H0z\"/>\n        <path fill=\"#d00\" d=\"M0 160h640v160H0z\"/>\n      </svg>\n    </div> \n    <div v-else-if=\"language.data[params.languageFieldName] === 'en'\" title=\"Englisch\">\n      <svg xmlns=\"http://www.w3.org/2000/svg\" id=\"flag-icon-css-us\" width=\"16\" height=\"12\" viewBox=\"0 0 640 480\">\n        <g fill-rule=\"evenodd\">\n          <g stroke-width=\"1pt\">\n            <path fill=\"#bd3d44\" d=\"M0 0h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.7h972.8V197H0zm0 78.8h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.7h972.8v39.4H0zm0 78.8h972.8V512H0z\" transform=\"scale(.9375)\"/>\n            <path fill=\"#fff\" d=\"M0 39.4h972.8v39.4H0zm0 78.8h972.8v39.3H0zm0 78.7h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.7h972.8v39.4H0z\" transform=\"scale(.9375)\"/>\n          </g>\n          <path fill=\"#192f5d\" d=\"M0 0h389.1v275.7H0z\" transform=\"scale(.9375)\"/>\n          <path fill=\"#fff\" d=\"M32.4 11.8L36 22.7h11.4l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.3-6.7H29zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7h11.4zm64.8 0l3.6 10.9H177l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.3-6.7h11.5zm64.9 0l3.5 10.9H242l-9.3 6.7 3.6 11-9.3-6.8-9.3 6.7 3.6-10.9-9.3-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.2-6.7h11.4zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.6 11-9.3-6.8-9.3 6.7 3.6-10.9-9.3-6.7h11.5zM64.9 39.4l3.5 10.9h11.5L70.6 57 74 67.9 65 61.2l-9.3 6.7L59 57 50 50.3h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7 3.6 10.9-9.3-6.7-9.3 6.7L124 57l-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 10.9-9.2-6.7-9.3 6.7 3.5-10.9-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.3 6.7 3.6 10.9-9.3-6.7-9.2 6.7 3.5-10.9-9.3-6.7H256zm64.9 0l3.5 10.9h11.5L330 57l3.5 10.9-9.2-6.7-9.3 6.7 3.5-10.9-9.2-6.7h11.4zM32.4 66.9L36 78h11.4l-9.2 6.7 3.5 10.9-9.3-6.8-9.2 6.8 3.5-11-9.3-6.7H29zm64.9 0l3.5 11h11.5l-9.3 6.7 3.5 10.9-9.2-6.8-9.3 6.8 3.5-11-9.2-6.7h11.4zm64.8 0l3.6 11H177l-9.2 6.7 3.5 10.9-9.3-6.8-9.2 6.8 3.5-11-9.3-6.7h11.5zm64.9 0l3.5 11H242l-9.3 6.7 3.6 10.9-9.3-6.8-9.3 6.8 3.6-11-9.3-6.7h11.4zm64.8 0l3.6 11h11.4l-9.2 6.7 3.5 10.9-9.3-6.8-9.2 6.8 3.5-11-9.2-6.7h11.4zm64.9 0l3.5 11h11.5l-9.3 6.7 3.6 10.9-9.3-6.8-9.3 6.8 3.6-11-9.3-6.7h11.5zM64.9 94.5l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7 3.6 11-9.3-6.8-9.3 6.7 3.6-10.9-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.3-6.7H256zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7h11.4zM32.4 122.1L36 133h11.4l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.3-6.7H29zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 10.9-9.2-6.7-9.3 6.7 3.5-10.9-9.2-6.7h11.4zm64.8 0l3.6 10.9H177l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.3-6.7h11.5zm64.9 0l3.5 10.9H242l-9.3 6.7 3.6 11-9.3-6.8-9.3 6.7 3.6-10.9-9.3-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.2-6.7h11.4zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.6 11-9.3-6.8-9.3 6.7 3.6-10.9-9.3-6.7h11.5zM64.9 149.7l3.5 10.9h11.5l-9.3 6.7 3.5 10.9-9.2-6.8-9.3 6.8 3.5-11-9.2-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7 3.6 10.9-9.3-6.8-9.3 6.8 3.6-11-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 10.9-9.2-6.8-9.3 6.8 3.5-11-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.2 6.7 3.5 10.9-9.3-6.8-9.2 6.8 3.5-11-9.3-6.7H256zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 10.9-9.2-6.8-9.3 6.8 3.5-11-9.2-6.7h11.4zM32.4 177.2l3.6 11h11.4l-9.2 6.7 3.5 10.8-9.3-6.7-9.2 6.7 3.5-10.9-9.3-6.7H29zm64.9 0l3.5 11h11.5l-9.3 6.7 3.6 10.8-9.3-6.7-9.3 6.7 3.6-10.9-9.3-6.7h11.4zm64.8 0l3.6 11H177l-9.2 6.7 3.5 10.8-9.3-6.7-9.2 6.7 3.5-10.9-9.3-6.7h11.5zm64.9 0l3.5 11H242l-9.3 6.7 3.6 10.8-9.3-6.7-9.3 6.7 3.6-10.9-9.3-6.7h11.4zm64.8 0l3.6 11h11.4l-9.2 6.7 3.5 10.8-9.3-6.7-9.2 6.7 3.5-10.9-9.2-6.7h11.4zm64.9 0l3.5 11h11.5l-9.3 6.7 3.6 10.8-9.3-6.7-9.3 6.7 3.6-10.9-9.3-6.7h11.5zM64.9 204.8l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7 3.6 11-9.3-6.8-9.3 6.7 3.6-10.9-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.3-6.7H256zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7h11.4zM32.4 232.4l3.6 10.9h11.4l-9.2 6.7 3.5 10.9-9.3-6.7-9.2 6.7 3.5-11-9.3-6.7H29zm64.9 0l3.5 10.9h11.5L103 250l3.6 10.9-9.3-6.7-9.3 6.7 3.6-11-9.3-6.7h11.4zm64.8 0l3.6 10.9H177L168 250l3.5 10.9-9.3-6.7-9.2 6.7 3.5-11-9.3-6.7h11.5zm64.9 0l3.5 10.9H242l-9.3 6.7 3.6 10.9-9.3-6.7-9.3 6.7 3.6-11-9.3-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.2 6.7 3.5 10.9-9.3-6.7-9.2 6.7 3.5-11-9.2-6.7h11.4zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.6 10.9-9.3-6.7-9.3 6.7 3.6-11-9.3-6.7h11.5z\" transform=\"scale(.9375)\"/>\n        </g>\n      </svg>\n    </div>\n    <div v-else=\"language.data[params.languageFieldName]\">\n      <ae-icon class=\"fa fa-flag\"></ae-icon>({{ language.data[params.languageFieldName] || 'Unbekannt' }})\n    </div>\n  </div>\n\n  <ae-layout layout-id=\"64eaddd0-ac8a-46d4-a84f-0eb16e42b40d\" @click.native=\"controller.cloneDocumentAsChild()\"></ae-layout>\n</div> \n\n\n\n\n\n\n\n\n\n\n\n\n\n<!-- <div class=\"_CSS-tags ae-noselect\" v-if=\"controller.allowTranslation(document)\">\n  <div v-for=\"language in controller.languageDocuments\" @click=\"locals.languageDocument = language\" class=\"_CSS-tag\" :class=\"{ '_CSS-tag--active': locals.languageDocument.id === language.id  }\">\n    <template v-if=\"language.data[params.languageFieldName] === 'de'\">\n      <svg xmlns=\"http://www.w3.org/2000/svg\" id=\"flag-icon-css-de\" width=\"16\" height=\"12\" viewBox=\"0 0 640 480\">\n        <path fill=\"#ffce00\" d=\"M0 320h640v160H0z\"/>\n        <path d=\"M0 0h640v160H0z\"/>\n        <path fill=\"#d00\" d=\"M0 160h640v160H0z\"/>\n      </svg>\n    </template>\n    <template v-else-if=\"language.data[params.languageFieldName] === 'en'\">\n      <svg xmlns=\"http://www.w3.org/2000/svg\" id=\"flag-icon-css-us\" width=\"16\" height=\"12\" viewBox=\"0 0 640 480\">\n        <g fill-rule=\"evenodd\">\n          <g stroke-width=\"1pt\">\n            <path fill=\"#bd3d44\" d=\"M0 0h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.7h972.8V197H0zm0 78.8h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.7h972.8v39.4H0zm0 78.8h972.8V512H0z\" transform=\"scale(.9375)\"/>\n            <path fill=\"#fff\" d=\"M0 39.4h972.8v39.4H0zm0 78.8h972.8v39.3H0zm0 78.7h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.7h972.8v39.4H0z\" transform=\"scale(.9375)\"/>\n          </g>\n          <path fill=\"#192f5d\" d=\"M0 0h389.1v275.7H0z\" transform=\"scale(.9375)\"/>\n          <path fill=\"#fff\" d=\"M32.4 11.8L36 22.7h11.4l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.3-6.7H29zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7h11.4zm64.8 0l3.6 10.9H177l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.3-6.7h11.5zm64.9 0l3.5 10.9H242l-9.3 6.7 3.6 11-9.3-6.8-9.3 6.7 3.6-10.9-9.3-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.2-6.7h11.4zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.6 11-9.3-6.8-9.3 6.7 3.6-10.9-9.3-6.7h11.5zM64.9 39.4l3.5 10.9h11.5L70.6 57 74 67.9 65 61.2l-9.3 6.7L59 57 50 50.3h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7 3.6 10.9-9.3-6.7-9.3 6.7L124 57l-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 10.9-9.2-6.7-9.3 6.7 3.5-10.9-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.3 6.7 3.6 10.9-9.3-6.7-9.2 6.7 3.5-10.9-9.3-6.7H256zm64.9 0l3.5 10.9h11.5L330 57l3.5 10.9-9.2-6.7-9.3 6.7 3.5-10.9-9.2-6.7h11.4zM32.4 66.9L36 78h11.4l-9.2 6.7 3.5 10.9-9.3-6.8-9.2 6.8 3.5-11-9.3-6.7H29zm64.9 0l3.5 11h11.5l-9.3 6.7 3.5 10.9-9.2-6.8-9.3 6.8 3.5-11-9.2-6.7h11.4zm64.8 0l3.6 11H177l-9.2 6.7 3.5 10.9-9.3-6.8-9.2 6.8 3.5-11-9.3-6.7h11.5zm64.9 0l3.5 11H242l-9.3 6.7 3.6 10.9-9.3-6.8-9.3 6.8 3.6-11-9.3-6.7h11.4zm64.8 0l3.6 11h11.4l-9.2 6.7 3.5 10.9-9.3-6.8-9.2 6.8 3.5-11-9.2-6.7h11.4zm64.9 0l3.5 11h11.5l-9.3 6.7 3.6 10.9-9.3-6.8-9.3 6.8 3.6-11-9.3-6.7h11.5zM64.9 94.5l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7 3.6 11-9.3-6.8-9.3 6.7 3.6-10.9-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.3-6.7H256zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7h11.4zM32.4 122.1L36 133h11.4l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.3-6.7H29zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 10.9-9.2-6.7-9.3 6.7 3.5-10.9-9.2-6.7h11.4zm64.8 0l3.6 10.9H177l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.3-6.7h11.5zm64.9 0l3.5 10.9H242l-9.3 6.7 3.6 11-9.3-6.8-9.3 6.7 3.6-10.9-9.3-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.2-6.7h11.4zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.6 11-9.3-6.8-9.3 6.7 3.6-10.9-9.3-6.7h11.5zM64.9 149.7l3.5 10.9h11.5l-9.3 6.7 3.5 10.9-9.2-6.8-9.3 6.8 3.5-11-9.2-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7 3.6 10.9-9.3-6.8-9.3 6.8 3.6-11-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 10.9-9.2-6.8-9.3 6.8 3.5-11-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.2 6.7 3.5 10.9-9.3-6.8-9.2 6.8 3.5-11-9.3-6.7H256zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 10.9-9.2-6.8-9.3 6.8 3.5-11-9.2-6.7h11.4zM32.4 177.2l3.6 11h11.4l-9.2 6.7 3.5 10.8-9.3-6.7-9.2 6.7 3.5-10.9-9.3-6.7H29zm64.9 0l3.5 11h11.5l-9.3 6.7 3.6 10.8-9.3-6.7-9.3 6.7 3.6-10.9-9.3-6.7h11.4zm64.8 0l3.6 11H177l-9.2 6.7 3.5 10.8-9.3-6.7-9.2 6.7 3.5-10.9-9.3-6.7h11.5zm64.9 0l3.5 11H242l-9.3 6.7 3.6 10.8-9.3-6.7-9.3 6.7 3.6-10.9-9.3-6.7h11.4zm64.8 0l3.6 11h11.4l-9.2 6.7 3.5 10.8-9.3-6.7-9.2 6.7 3.5-10.9-9.2-6.7h11.4zm64.9 0l3.5 11h11.5l-9.3 6.7 3.6 10.8-9.3-6.7-9.3 6.7 3.6-10.9-9.3-6.7h11.5zM64.9 204.8l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7 3.6 11-9.3-6.8-9.3 6.7 3.6-10.9-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.2 6.7 3.5 11-9.3-6.8-9.2 6.7 3.5-10.9-9.3-6.7H256zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.5 11-9.2-6.8-9.3 6.7 3.5-10.9-9.2-6.7h11.4zM32.4 232.4l3.6 10.9h11.4l-9.2 6.7 3.5 10.9-9.3-6.7-9.2 6.7 3.5-11-9.3-6.7H29zm64.9 0l3.5 10.9h11.5L103 250l3.6 10.9-9.3-6.7-9.3 6.7 3.6-11-9.3-6.7h11.4zm64.8 0l3.6 10.9H177L168 250l3.5 10.9-9.3-6.7-9.2 6.7 3.5-11-9.3-6.7h11.5zm64.9 0l3.5 10.9H242l-9.3 6.7 3.6 10.9-9.3-6.7-9.3 6.7 3.6-11-9.3-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.2 6.7 3.5 10.9-9.3-6.7-9.2 6.7 3.5-11-9.2-6.7h11.4zm64.9 0l3.5 10.9h11.5l-9.3 6.7 3.6 10.9-9.3-6.7-9.3 6.7 3.6-11-9.3-6.7h11.5z\" transform=\"scale(.9375)\"/>\n        </g>\n      </svg>\n    </template>\n    <template v-else=\"language.data[params.languageFieldName]\">\n      <ae-icon class=\"fa fa-flag\"></ae-icon>({{ language.data[params.languageFieldName] || 'Unbekannt' }})\n    </template>\n  </div>\n\n  <ae-layout layout-id=\"64eaddd0-ac8a-46d4-a84f-0eb16e42b40d\" @click.native=\"controller.cloneDocumentAsChild()\"></ae-layout>\n</div> -->\n\n  \n<template v-if=\"locals.activeLanguageChild\">\n  <ae-layout layout-id=\"0ded34c5-1221-4511-af93-d897a79f1369\" :param-document=\"locals.activeLanguageChild\" @delete=\"controller.resetTabAndReloadChildren()\" :param-no-navigate=\"true\" param-button-layout-id=\"eec7c7aa-892c-4506-8e37-1a101797677c\"></ae-layout>\n  <ae-form layout-id=\"95e11a76-7a7f-43e2-821f-46c6ef17f90f\" :document=\"locals.activeLanguageChild\" :auto-save=\"true\"></ae-form>\n</template>\n","css":"._CSS-tags {\n  display: flex;\n  align-items: center;\n}\n\n._CSS-tag {\n  margin: 8px;\n  padding: 8px;\n  margin-left: 4px;\n  border-radius: 4px;\n}\n\n._CSS-tag--active {\n  background: rgba(255,255,255,0.9);\n  /* border-bottom: 2px solid black; */\n}","controller":"class Controller {\n  init() {\n    locals.activeLanguageChild = null\n    locals.children = []\n\n    this._updateContentTranslationChildren()\n\n    locals.activeLanguageChild = locals.children[0]\n  }\n\n  async cloneDocumentAsChild() {\n    const clone = await Aeppic.clone(document, document.id)\n    clone.data[params.languageFieldName] = ''\n  \n    Aeppic.save(clone)\n\n    locals.activeLanguageChild = clone\n  }\n\n  async _updateContentTranslationChildren() {\n    Aeppic.subscribe(`p:${document.id} f.id:${form.id}`, (docs) => {\n      const childrenLength = locals.children.length\n\n      locals.children = docs\n\n      if (childrenLength === 0) {\n        locals.activeLanguageChild = locals.children[0]\n      }\n    })\n  }\n\n  async resetTabAndReloadChildren() {\n    console.log('resetTabAndReloadChildren')\n    locals.activeLanguageChild = locals.children[0]\n\n    this._updateContentTranslationChildren()\n  }\n\n  // async _updateContentTranslationChildren() {\n  //   this.revisionWatchers = []\n\n  //   const docs = await Aeppic.find(`p:${document.id} f.id:${form.id}`)\n    \n  //   for (const watcher of this.revisionWatchers) {\n  //     watcher.stop()\n  //   } \n    \n  //   this.revisionWatchers = []\n\n  //   this.revisionWatchers.push(this.createSaveOnRevisionWatcher(document.id))\n\n  //   locals.children = docs\n    \n  //   for (const doc of docs) {\n  //     console.log(doc.id)\n  //     this.revisionWatchers.push(this.createSaveOnRevisionWatcher(doc.id))\n  //   }\n  //   console.log(this.revisionWatchers)\n  // }\n\n  // createSaveOnRevisionWatcher(documentId) {\n  //   return Aeppic.watchRevisions(documentId, (doc, change) => {\n  //     console.log('change', doc, change)\n  //     if (change.revised) {\n  //       Aeppic.save(doc)\n  //     }\n  //   })\n  // }\n}  \n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-07-06T12:19:18+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936828,"ct":1721209936828,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"261e4a15f3496b0e","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2024-11-20T11:32:38+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"ct":1732102358531,"data":{"cardinality":"BOTH","controller":"","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"contract-payment-planner","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<ae-control name=\"rd:children\"\n            :document=\"document\"\n            :params=\"{ ...params, showActions: true }\"\n            :field-name=\"fieldName\"\n></ae-control>","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"66d18300-e7dc-4948-b4f6-a06f024c4e01","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2024-11-21T09:03:28+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"03b91d7f39d3789c"},"stamps":[],"t":1732179808488,"v":"f13d8b2e4b1f75ac"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"cloneOf":{"id":"e609280c-5b9c-4658-81d0-ac3e144a64fe","text":"payment-planner","v":"89ff1b23f9d48f8d"},"created":{"at":"2024-11-20T11:06:51+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"ct":1732100811369,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    this.FORMS = {\n      PaymentPlanning: '882a183b-85ea-4991-a911-74ebbeef6be7',\n      Invoice: '54da686f-3f74-441b-b686-ed2b71dd35cc',\n      InvoicePosition: 'a40633b4-c769-4457-a267-830738d4b549',\n    }\n\n    locals.canEdit = false\n    locals.paymentPlanning = null\n    \n    locals.canEdit = await this._canEditPlannings()\n  }\n\n\n  async show() {\n    locals.paymentPlanning = await Aeppic.new(this.FORMS.PaymentPlanning, document)\n    locals.paymentPlanning.data.anzahl = 10\n\n    const ensuredAngebot = await Aeppic.get(document.data.angebote.slice(-1)[0] ?? locals.paymentPlanning.data.angebot)\n\n    if (ensuredAngebot) {\n      locals.paymentPlanning.addReference('angebot', ensuredAngebot)\n    }\n\n    $refs.planner.show()\n  }\n\n  async _canEditPlannings() {\n    if (params.canEditCommand) {\n      const result = await Aeppic.Commands.execute(params.canEditCommand, document, { Account: Aeppic.Account })\n\n      return result.ok && result.value?.ok === true\n    } else {\n      return Aeppic.canEdit(document)\n    }\n  }\n\n  async createPlannings() {\n    if (!locals.paymentPlanning.data.abschlag || !locals.paymentPlanning.data.anzahl) {\n      return\n    }\n\n    locals.working = true \n\n    const contract = await Aeppic.edit(document) \n\n    const notCreatedInvoices = (await Aeppic.getAll(contract.data.rechnungen)).filter(d => !!d && !d.data.erstellt)\n    notCreatedInvoices.forEach(p => contract.removeReference('rechnungen', p))\n\n    let lastDate = Aeppic.DateTime.fromISO(locals.paymentPlanning.data.datum)\n\n    const plannings = []\n\n    let positions = []\n\n    const angebot = await Aeppic.get(locals.paymentPlanning.data.angebot)\n\n    const zahlungsmodalitaet = locals.paymentPlanning.data.zahlungsmodalitaet\n\n    const modalities = zahlungsmodalitaet === 'ZEIT' ? \n      new Array(locals.paymentPlanning.data.anzahl).fill(Number(locals.paymentPlanning.data.abschlag)) :\n      zahlungsmodalitaet.split('-').map(Number)\n\n    const anzahl = modalities.length\n\n    console.log({ modalities, anzahl })\n\n    for (let i = 0; i < anzahl; i += 1) {\n      const planning = await Aeppic.new(this.FORMS.Invoice, document)\n      plannings.push(planning)\n\n      contract.addReference('rechnungen', planning)\n      \n      // TODO: mehrere Abschläge unterschiedlicher Höhe unterstützen (das kann das Formular aber aktuell noch nicht)\n      let abschlag = 0\n\n      if (angebot) {\n        abschlag = angebot.data.netto * (modalities[i] / 100)\n        \n        if (Number.isNaN(abschlag)) {\n          abschlag = 0\n        }\n      }\n      console.log({ angebot: angebot.data.netto, i, anzahl, modalities, abschlag })\n      \n      planning.data = {\n        datum: lastDate.toISO(),\n        // lieferdatum: lastDate.toISO(), // is optional now\n        name: locals.paymentPlanning.data.beschreibung || '',\n      }\n\n      if (angebot) {\n        planning.addReference('angebot', angebot)\n      }\n\n\n      if (abschlag) {\n        const position = await Aeppic.new(this.FORMS.InvoicePosition, planning, { allowMissingParent: true })\n\n        positions.push(position)\n\n        planning.addReference('positionen', position)\n\n        position.data.kostenProEinheit = {\n          amount: abschlag, \n          currency: 'EUR',\n          precision: 2,\n        }\n        position.data.menge = 1\n        position.data.typ = 'PAUSCHALE'\n        position.data.abrechenbar = true\n        position.data.datum = lastDate.toISO()\n      }\n      \n      lastDate = lastDate.plus({ months: 1 })\n    }\n\n    Aeppic.saveAll([...plannings, ...positions, contract])\n\n    $refs.planner.close()\n\n    locals.working = false\n  }\n} ","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"contract-payment-planner-add","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<div class=\"flex gap-1\">\n    <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n              param-icon=\"far fa-credit-card-blank\"\n              param-text=\"Planung ändern\" \n              @selected=\"controller.show()\"\n              param-border=\"border border-secondary-200\"\n              param-classes=\"text-xs\"\n              :param-disabled=\"!locals.canEdit\"\n  ></ae-layout>\n\n  <ae-control name=\"rd:children-add\"\n            :document=\"document\"\n            :params=\"params\"\n            :field-name=\"fieldName\"\n  ></ae-control>\n</div>\n\n<ae-dialog ref=\"planner\" :dialog-size=\"{ height: '60vh' }\">\n  <template #header=\"scope\">\n    <div style=\"display:flex;justify-content:space-between;\">\n      <div>\n        Ratenzahlung\n      </div>\n      <button class=\"ae-button\" @click.stop=\"scope.close()\">\n        <ae-icon class=\"fa fa-times\"></ae-icon>\n      </button>\n    </div>\n  </template>\n\n  <template #body>\n    <div>\n      <ae-form :readonly=\"locals.working\" \n                :document=\"locals.paymentPlanning\" \n                @data-changed=\"Aeppic.commit(locals.paymentPlanning)\"\n                layout-id=\"efdb156e-9350-4cb3-8e00-4a69adec691b\"\n      ></ae-form>\n    </div>\n  </template>\n\n  <template #commands=\"scope\">\n    <div style=\"display:flex;gap:var(--ae-space-1)\">\n      <button class=\"ae-button\" @click.stop=\"scope.close()\">\n        Abbrechen\n      </button>\n\n      <button class=\"ae-button ae-button--primary\" @click.stop=\"controller.createPlannings()\">\n        <ae-icon class=\"fa fa-save\"></ae-icon>\n        Planungen erstellen\n      </button>\n    </div>\n  </template>\n</ae-dialog>","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"28903782-d973-4a69-97ec-ae4afcdffe0f","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-05-22T12:43:56+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"originalCloneOf":{"id":"e609280c-5b9c-4658-81d0-ac3e144a64fe","text":"payment-planner","v":"89ff1b23f9d48f8d"},"p":"controls-folder","previous":{"v":"810af7386283dc88"},"stamps":[],"t":1747917836746,"v":"b3870f6080572697"},{"id":"b7ed4e17-e991-4cda-a51c-77f08b9d68a7","v":"0085d94b9bbe403a","previous":{"v":"2da4de0be92dcf57"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"conversations","template":"<template v-if=\"!Aeppic.LocalStorage.getValue('appLayoutId')\">\n  <div class=\"_CSS-list\">\n    <!-- TODO: check navigation before executing? -->\n    <ae-list  list-id=\"2e798091-303f-406b-a42a-93aa1cd8425c\"\n              :documents=\"locals.documents\"\n              param-design=\"line-item\"\n              @selected=\"Aeppic.drillInto($event)\"\n    ></ae-list>\n  </div>\n\n  <div class=\"bp-card bp-card--bordered\" v-if=\"!controller.readonly\">\n    <div class=\"_CSS-new-container\">\n      <div class=\"_CSS-new\" v-if=\"!controller.readonly && locals.newComment\">\n        <ae-layout class=\"_CSS-new__user\"\n                  layout-id=\"36649f27-5869-49e6-9b49-3b5c3b9c795d\"\n                  :param-settings=\"locals.selectorSettings\"\n                  :param-hide-path=\"true\"\n                  @selected=\"controller.selectedReceiver($event)\"\n                  param-icon=\"far fa-user-circle\"\n                  param-title=\"Empfänger für Ping auswählen\"\n        ></ae-layout>\n        <template v-if=\"locals.receiver\">\n          <ae-layout layout-id=\"8e98c038-26ba-4225-b972-e8203d647442\" \n                    :param-employees=\"locals.receiver\"\n                    :param-max=\"1\"\n                    :param-vertical-layout=\"true\"\n                    @remove=\"controller.removeReceiver($event)\"\n          ></ae-layout>\n\n          <!-- <ae-design name=\"avatar--resizes-with-font,avatar\" :document=\"locals.receiver\"></ae-design> -->\n          <button v-if=\"locals.receiver.length\" class=\"ae-button ae-button--small\" @click.stop=\"locals.receiver = []\">\n            <ae-icon class=\"fa fa-times\"></ae-icon>\n          </button>\n        </template>\n      \n    \n        <ae-control class=\"_CSS-new__input\"\n                  name=\"editor-comment\" \n                  :document=\"locals.newComment\"\n                  field-name=\"content\"\n                  @focusin.native=\"controller.onEditorFocus()\"\n                  :param-placeholder-text=\"controller.getPlaceholder()\"\n        ></ae-control>\n      </div>\n      <!-- <button>button</button> -->\n\n      <button class=\"ae-button ae-button--secondary _CSS-button-send\"\n              @click.stop=\"controller.save()\"\n              :disabled=\"!locals.newComment || !locals.newComment.data.content\"\n      >\n      <!-- NOTE: chrome 95 hangs if new-comment input in focussed and the ae-icon is used here -->\n        <template v-if=\"locals.receiver.length\">\n          <i class=\"fa fa-comment-plus\"></i>\t\n          <!-- <ae-icon class=\"fa fa-comment-plus\" style=\"margin-right:0.5ch\"></ae-icon> Ping senden -->\n          Ping senden\n        </template>\n        <template v-else>\n          <i class=\"fa fa-comment\"></i>\t\n          Kommentieren\n          <!-- <ae-icon class=\"fa fa-comment\" style=\"margin-right:0.5ch\"></ae-icon>  Kommentieren -->\n        </template> \n      </button>\n    </div>\n  </div>\n\n</template>\n","css":"._CSS-new-container {\n  flex: 1;\n  xmargin-top: var(--ae-space-5);\n  display: flex;\n  flex-flow: column;\n  align-items: stretch;\n  overflow: visible;\n}\n\n._CSS-new {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: flex-end;\n}\n\n._CSS-new__input {\n  flex: 1;\n}\n\n._CSS-button-send {\n  align-self: flex-end;\n}\n\n","controller":"class Controller {\n  async init() {\n    this.STAMPS = {\n      CommentPublish: '04a939a8-6732-4f94-ad75-6f17ac417136'\n    }\n\n    this.FORMS = {\n      Notification: '6d164aff-df9b-470d-9632-cfb6f5892509',\n      PingAsNotificationCategory: '97902782-7315-4837-94fc-8e080c6cfbf3',\n      Ping: '42cf6e7a-e304-49a6-86e1-c94d84f1d0d9',\n      Comment: 'cf155969-ede1-43a4-9b10-3a697b19db91',\n      Employee: 'd2ac3563-3c3f-4f02-b923-9b67abf98d10',\n      NotificationRequest: 'ddaa6921-770e-4a73-b8a4-b4a99abed24b',\n      Group: 'group'\n    }\n\n    locals.selectorSettings = {\n      filter: {\n        ancestorId: document.a[0],\n        base: `(f.id:${this.FORMS.Employee} OR f.id:${this.FORMS.Group})`,\n      }\n    }\n \n    locals.receiver = []\n    locals.content = ''\n    locals.canCreate = false\n    locals.documents = []\n    locals.newComment = null\n\n    const commentsQuery = `p:${document.id} f.id:${this.FORMS.Comment}`\n    const pingsQuery = `p:${Aeppic.Account.id} (((f.id:${this.FORMS.Notification} OR f.id:${this.FORMS.NotificationRequest}) data.category:${this.FORMS.PingAsNotificationCategory}) OR f.id:${this.FORMS.Ping}) data.document.id:${document.id}`\n    \n    Aeppic.subscribe(`(${commentsQuery}) OR (${pingsQuery})`, { sort: 'created.at' }, (documents) => {\n      // TODO: this is only for legacy comments. Newly created, and not send, are marked as 'hidden' as thus will not be displayed\n      // Write a migration script and set all unset comments to document.hidden=true\n      locals.documents = documents.filter(doc => {\n        if (doc.f.id === this.FORMS.Comment) {\n          return this.displayComment(doc)\n        }\n\n        return true\n      })\n    })\n  \n    if (!Aeppic.Account) {\n      return\n    }\n\n    if (Aeppic.Account.data.isGroupAccount) {\n      locals.canCreate = await Aeppic.Preferences.getSetting('bizpin:comments:groupAccountAllowCreate')\n    } else {\n      locals.canCreate = true\n    }\n\n    await this.createNewComment()\n  }  \n\n  async createNewComment() {\n    locals.newComment = null\n\n    const newComment = await Aeppic.new(this.FORMS.Comment, document.id)\n    newComment.hidden = true\n    Aeppic.commit(newComment)\n\n    locals.newComment = newComment\n  }\n\n  async selectedReceiver(groupOrEmployee) {\n    if (!groupOrEmployee) {\n      return\n    }\n\n    const newReceivers = []\n    if (groupOrEmployee.f.id === this.FORMS.Group) {\n      const accounts = await Aeppic.find(`a:${groupOrEmployee.a[0]} f.id:account data.groups:${groupOrEmployee.id}`, { size: 999 })\n      const employees = await Promise.all(accounts.map(a => Aeppic.getProfilePageForAccount(a)))\n\n      newReceivers.push(...employees)\n    } else {\n      newReceivers.push(groupOrEmployee)\n    }\n\n    for (const receiver of newReceivers) {\n      if (locals.receiver.find(r => r.id === receiver.id)) {\n        continue\n      }\n\n      locals.receiver.push(receiver)\n    }\n  }\n\n  async removeReceiver(employee) {\n    const index = locals.receiver.findIndex(r => r.id === employee.id)\n\n    if (index >= 0) {\n      locals.receiver.splice(index, 1)\n    }\n  }\n\n  get readonly() {\n    if (params.readonly === true) {\n      return true\n    }\n\n    if (typeof readonly === 'boolean') {\n      return readonly\n    }\n\n    return false\n  }\n\n  async save() {\n    const filledOut = !!locals.newComment.data.content\n\n    if (!filledOut) {\n      return\n    }\n\n    const isPing = !!locals.receiver.length\n\n    let editable = locals.newComment\n\n    if (isPing) {\n      editable = await this.newPing(locals.receiver)\n    }\n    \n    editable.data.content = locals.newComment.data.content\n\n    editable.hidden = false\n\n    Aeppic.save(editable)\n\n    if (!isPing) {\n      const comment = await Aeppic.get(editable)\n      Aeppic.stamp(comment, this.STAMPS.CommentPublish)\n    }\n\n    locals.receiver = []\n    await this.createNewComment()\n  }\n\n  async newPing(receivers) {\n    const ping = await Aeppic.new(this.FORMS.Ping, Aeppic.Account.id)\n\n    if (!receivers) {\n      throw new Error('Could not resolve receivers')\n    }\n\n    ping.data = {\n      document: await Aeppic.asReference(document),\n      who: await Aeppic.asReference(Aeppic.Account)\n    }\n\n    receivers.forEach(r => ping.addReference('receiver', r))\n\n    return ping\n  }\n\n  // async saveComment() {\n  //   Aeppic.save(locals.newComment)\n    \n  //   const comment = await Aeppic.get(locals.newComment)\n  //   Aeppic.stamp(comment, this.publishStampId)\n\n  //   await this.newComment()\n  // }\n\n\n  async onEditorFocus() {\n    // TODO: this is only needed, once attachments to a comment are possible (again)\n    // if (!await Aeppic.get(locals.newComment)) {\n    //   Aeppic.save(locals.newComment)\n    // }\n  }\n\n  displayComment(comment) {\n    const commentHasNewForm = 'attachments' in comment.data\n\n    if (commentHasNewForm) {\n      return comment.stamps && comment.stamps.find(s => s.type && s.type.id === this.STAMPS.CommentPublish)\n    }\n\n    return true\n  }\n\n  getPlaceholder() {\n    if (locals.receiver.length) {\n      return `Hier schreiben um einen Ping an ${locals.receiver.length} Empfänger senden.`\n    }\n\n    return `Hier schreiben um einen Kommentar erstellen. Oder Mitarbeiter auswählen um Ping zu versenden. @ verwenden um Jemanden zu erwähnen`\n  }\n\n  destroy() {\n    if (locals.newComment) {\n      Aeppic.delete(locals.newComment)\n    }\n  }\n}","parameters":[],"standalone":"REQUIRED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-10-11T15:05:16+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2024-08-21T11:54:20+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1724241260978,"ct":1721209936828,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"798a1c0c-8279-484c-a5c7-4834ccfd8fdc","v":"4fb2433bec666d11","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"copy-fields-from","template":"<button class=\"ae-button\" @click.stop=\"controller.show()\" v-if=\"!readonly\">\n  <ae-icon class=\"fa fa-copy\"></ae-icon> Aus <ae-design style=\"display:inline;\" name=\"title\" :document-id=\"params.form\"></ae-design> Daten übernehmen\n</button>\n\n<ae-dialog ref=\"dialog\" :backdrop-close=\"false\" :dialog-size=\"{ height: '70vh' }\">\n  <template #header=\"scope\">\n    <div class=\"_CSS-dialog__header\">\n      Aus Vorlage kopieren\n    </div>\n  </template>\n  <template #body>\n    <div @ae-drill-into.stop.prevent>\n      <ae-layout v-if=\"!locals.selectedContainer\" layout-id=\"36649f27-5869-49e6-9b49-3b5c3b9c795d\" :param-settings=\"locals.containerSearchSettings\" :param-hide-path=\"true\" @selected=\"locals.selectedContainer = $event\"></ae-layout>\n      <div v-else class=\"_CSS-selected-container\">\n        <div class=\"_CSS-selected-container__target\">\n          <ae-design name=\"title\" :document=\"locals.selectedContainer\" style=\"min-width: 0;\"></ae-design>\n          <button class=\"ae-button\" @click.stop=\"locals.selectedContainer = null\">\n            <ae-icon class=\"fa fa-times\"></ae-icon>\n          </button>\n        </div>\n\n        <div class=\"_CSS-selected-container__body\">\n          <ae-layout layout-id=\"acd83791-970f-4b6e-bebd-d7f3bca52014\" :param-document=\"locals.selectedContainer\" :param-readonly=\"true\" :param-fields-to-display=\"Object.keys(locals.fromToFields)\"></ae-layout>\n        </div>\n\n      </div>\n    </div>\n  </template>\n  <template #commands=\"scope\">\n    <button class=\"ae-button\" @click.stop=\"scope.close()\">\n      Abbrechen\n    </button>\n    <button class=\"ae-button ae-button--primary\" @click.stop=\"controller.copyContainerTemplates();scope.close()\" :disabled=\"!locals.selectedContainer\">\n      Kopieren\n    </button>\n  </template>\n</ae-dialog>\n ","css":"._CSS-selected-container__target {\n  background: var(--ae-ui-background);\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  border-radius: var(--ae-border-radius);\n  padding: var(--ae-space-2);\n\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}","controller":"class Controller {\n  init() {\n    locals.containerSearchSettings = {\n      filter: {\n        targetDocument: document,\n        ancestorForm: params.ancestorForm,\n        formIds: params.form\n      }\n    }\n\n    locals.fromToFields = this.parseFields()\n  }\n\n  async show() {\n    // TODO\n\n    $refs.dialog.show()\n  }\n\n  parseFields() {\n    const fromToFields = params.fields.split(',')\n    const fromTo = {}\n\n    fromToFields.forEach((field) => {\n      const [ from, to ] = field.split(':')\n      fromTo[from] = to\n    })\n\n    return fromTo\n  }\n\n  async copyContainerTemplates() {\n    const sourceForm = await Aeppic.getForm(params.form)\n    const targetForm = await Aeppic.getFormForDocument(document)\n    \n    for (const fromField in locals.fromToFields) {\n      const toField = locals.fromToFields[fromField]\n      const value = locals.selectedContainer.data[fromField]\n\n      const fromFieldInfo = sourceForm.getField(fromField)\n      const toFieldInfo = sourceForm.getField(toField)\n\n      if (fromFieldInfo.type !== toFieldInfo.type || fromFieldInfo.subType !== toFieldInfo.subType || JSON.stringify(fromFieldInfo.cardinality) !== JSON.stringify(fromFieldInfo.cardinality)) {\n        throw new Error(`Mismatch in from => to fields: ${formField} => ${toField}`)\n      }\n\n      if (fromFieldInfo.subType === 'ref') {\n        console.log('ref', value)\n\n        const refs = Array.isArray(value) ? value : [value]\n        \n        for (const ref of refs) {\n          if (!ref) {\n            continue\n          }\n\n          const cloneResult = await Aeppic.cloneDeep(ref, document, { save: true })\n          document.addReference(toField, cloneResult.root)\n        }\n      } else if (fromFieldInfo.type === 'string' || fromFieldInfo.type === 'string') {\n        document.data[toField] = value\n        console.log('string', value)\n      } else {\n        console.error('Cannot handle field', fromField, fromFieldInfo)\n      }\n    } \n    \n    // const toCopy = await Aeppic.getAll(locals.selectedContainer.data[params.templatesContainerField])\n\n    // for (const copy of toCopy) {\n    //   if (!copy || !availableFormIds.includes(copy.f.id)) {\n    //     continue\n    //   }\n\n    //   const cloneResult = await Aeppic.cloneDeep(copy, document, { save: true })\n    //   document.addReference(fieldName, cloneResult.root)\n    // }\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-11-12T13:52:26+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2020-11-12T14:43:02+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936828,"ct":1721209936828,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"3f2ca7e5ca01d17f","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"daaf2e53-8955-4951-911c-9d693e70440d","v":"849890b372e65c4f","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"copy-refs-from-selection","template":"<button class=\"ae-button ae-button--primary\" @click.stop=\"controller.show()\" v-if=\"!readonly && controller.hasAnySelections\">\n  Auswahl übernehmen\n</button>\n\n<ae-dialog ref=\"dialog\">\n  <template #header=\"scope\">\n    <div class=\"_CSS-header\">\n      <span>\n      <!-- TODO: label is empty -->\n      {{ fieldName.slice(0,1).toUpperCase() }}{{ fieldName.slice(1) }} aus Auswahl hinzufügen\n      </span>\n      <button class=\"ae-button\" @click.stop=\"scope.close()\">\n        <ae-icon class=\"fa fa-times\"></ae-icon>\n      </button>\n\n    </div>\n  </template>\n  <div slot=\"body\">\n    <ae-list  class=\"_CSS-list\"\n              list-id=\"2e798091-303f-406b-a42a-93aa1cd8425c\"\n              :documents=\"locals.documents\"\n              param-design=\"selection-card\"\n              :param-design-params=\"{ readonly: true }\"\n    ></ae-list>\n    <button style=\"margin-top:var(--ae-space-3);\" class=\"ae-button ae-button--primary\" @click.stop=\"controller.add()\">Hinzufügen</button>\n  </div>\n</ae-dialog> ","css":"._CSS-header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}","controller":"class Controller {\n  init() {\n    locals.documents = []\n  }\n\n  get hasAnySelections() {\n    return packages.SelectionManager?._selections?.slice(-1)[0]?.inclusions.size\n  }\n\n  async show() {\n    const result = packages.SelectionManager.result()\n    const documents = await Aeppic.find(result, { size: 9999 })\n    const matches = (await Aeppic.matchAll(documents, fieldInfo.joinedFilter))\n    const filtered = documents.filter((_, index ) => matches[index])\n\n    locals.documents = filtered\n\n    if (locals.documents.length) {\n      $refs.dialog.show()\n    }\n  }\n\n  async add() {\n    for (const doc of locals.documents) {\n      document.addReference(fieldName, doc)\n    }\n\n    packages.SelectionManager.create()\n    locals.documents = []\n\n    $refs.dialog.close()\n  }\n} ","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"d648078c-4579-4023-a4ec-b7b9cf639b70","v":"57181d8dce7ad69c","text":"Selection Manager v2"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-06-16T12:45:25+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-04-20T15:18:30+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936828,"ct":1721209936828,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"296f8e1c36450fa8","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"1aae67f2-d230-4d41-90ff-2154adcc71c9","v":"05d94f5ad62dbbd6","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"creator","template":"<label class=\"ae-form-cell__label\">\n  {{ params.label || 'Ersteller' }}\n</label>\n\n<div class=\"ae-form-cell__input _CSS-field\">\n  <ae-design name=\"avatar--tiny,avatar\" :document=\"locals.profilePage\"></ae-design>\n  <ae-design style=\"margin-left: var(--ae-size-2)\" name=\"title\" :document=\"locals.profilePage\"></ae-design>\n</div>\n","css":"._CSS-field {\n  display: flex;\n  align-items: center;\n}\n","controller":"async init() {\n  locals.profilePage = await this.getCreator()\n}\n\nasync getCreator() {\n  let creator = await Aeppic.get(document.created.by)\n\n  if (this.isSystemAccount(creator)) {\n    const modifier = await Aeppic.get(document.modified.by)\n\n    if (this.isSystemAccount(modifier)) {\n      return null\n    }\n\n    creator = modifier\n  }\n  \n\n  return Aeppic.getProfilePageForAccount(creator)\n}\n\nisSystemAccount(account) {\n  return !account.a || account.a[0] === 'apps' || account.a[0] === 'system'\n}","parameters":[],"standalone":"REQUIRED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-08-22T12:41:53+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936829,"ct":1721209936829,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"4b4bb23a6c08b453","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"1c70a099-3e5e-4201-856c-6a6ef0050ed4","v":"3bb4d25e10ae7647","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"creator","template":"<section class=\"flex flex-col gap-1\">\n  <label class=\"font-semibold\">\n    {{ params.label || 'Ersteller' }}\n  </label>\n\n  <div class=\"border rounded-md p-1 flex gap-2 items-center\">\n    <ae-design name=\"avatar--tiny,avatar\" :document=\"locals.profilePage\"></ae-design>\n    <ae-design name=\"title\" :document=\"locals.profilePage\"></ae-design>\n  </div>\n</section>","css":"._CSS-field {\n  display: flex;\n  align-items: center;\n}\n","controller":"async init() {\n  locals.profilePage = await this.getCreator()\n}\n\nasync getCreator() {\n  let creator = await Aeppic.get(document.created.by)\n\n  if (this.isSystemAccount(creator)) {\n    const modifier = await Aeppic.get(document.modified.by)\n\n    if (this.isSystemAccount(modifier)) {\n      return null\n    }\n\n    creator = modifier\n  }\n  \n\n  return Aeppic.getProfilePageForAccount(creator)\n}\n\nisSystemAccount(account) {\n  return !account.a || account.a[0] === 'apps' || account.a[0] === 'system'\n}","parameters":[],"standalone":"REQUIRED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2022-09-09T13:53:48+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-09-09T13:56:01+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936830,"ct":1721209936830,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"353c42dd8bbe27dc","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"30fdeb72-634d-424f-afc6-29a00bbc27fb","v":"cc612c8d10dd8be2","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"core","name":"crm-pdf","template":"<div  class=\"ae-form-cell__input\">\n  <template v-if=\"field.length\">\n    <template v-if=\"!params.onlyHistoric\">\n      <h3>Letzte Version</h3>\n      <div class=\"_CSS-latest\">\n        <ae-design name=\"page-with-toolbar\" :document=\"locals.latestEntry\" style=\"width:100%;min-height:100%;\" :param-no-right=\"true\" :param-hide-exports=\"true\" :param-hide-send-notifications=\"true\"></ae-design>\n      </div> \n    </template>\n    <h3>Alle erstellten Versionen</h3>\n    <ae-control name=\"ref\" :param-list=\"true\" :document=\"document\" :field-name=\"fieldName\" :readonly=\"true\"></ae-control>\n  </template>\n  <template v-else>\n    <div class=\"ae-form-cell__input\" style=\"font-style: italic;\">Das PDF wird mit der Erstellung automatisch generiert</div>\n  </template> \n</div>","css":"._CSS-latest {\n  min-height:700px;\n  width:100%;\n\n  display: flex;\n}","controller":"class Controller {\n  async init() {\n    locals.latestEntry = null\n\n    Aeppic.watch(document, async () => {\n      locals.latestEntry = await this.getLastEntry()  \n    })\n\n\n    locals.latestEntry = await this.getLastEntry()\n  }\n\n  async getLastEntry() {\n    const entries = await Aeppic.getAll(document.data[fieldName])\n    const sorted = Aeppic.sort(entries, 'created.at')\n    return sorted[sorted.length - 1]\n\n    // FIXME: because the user can break/overwrite the order by saving at the wrong moment, do not rely on the order !\n    if (params.addToBeginning) {\n      return sorted[0]\n    }\n\n    return sorted[sorted.length - 1]\n  }\n}\n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-03-01T11:25:52+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2021-06-09T09:00:01+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936830,"ct":1721209936830,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"a2f3777a1eeba36e","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"8649fa37-601d-48bf-bf0f-4b3dad9464ca","v":"921fbca779d3d5ef","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"crm-positions-copy","template":"<ae-layout  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n            @selected=\"controller.show()\"\n            param-text=\"Erfasste Positionen übernehmen\"\n            param-icon=\"far fa-copy\"\n            param-classes=\"border border-inherit text-xs rounded-full\"\n            ref=\"button\"\n            :param-disabled=\"!document.data.angebot.id\"\n            :param-title=\"!document.data.angebot.id ? 'Wählen Sie ein Angebot aus um erfasste Positionen aus dem Projekt übernehmen zu können.' : ''\"\n            v-if=\"!readonly\"\n></ae-layout>  \n\n<ae-dialog ref=\"dialog\" :dialog-size=\"{ width:'var(--ae-dialog__width--big)' }\" :backdrop-close=\"false\">\n  <template #header=\"scope\">\n    <div class=\"flex justify-between items-center\">\n      <span>Zu kopierende Positionen auswählen</span>\n\n      <ae-layout  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n                  @selected=\"scope.close()\"\n                  param-icon=\"far fa-times\"\n                  param-classes=\"border border-inherit rounded-full\"\n      ></ae-layout>  \n    </div>\n  </template>\n   \n  <template #body>\n    <div class=\"max-h-[80vh] flex flex-col gap-2\">  \n      <div v-if=\"!locals.entries.length\" class=\"p-4 text-neutral-500 text-center\">\n        Keine abrechenbaren Positionen gefunden.\n      </div>\n\n      <div class=\"p-1 border\"  :class=\"{ 'opacity-50': !entry.active }\" v-for=\"entry of locals.entries\">\n        <div class=\"flex gap-1\" :title=\"entry.noPositonsInAngebot ? 'Keine Positionen dieses Typs im Angebot gefunden' : ''\">\n          <ae-design name=\"rd:styled-title,title\" :document=\"entry.position\"></ae-design>\n          <div class=\"flex-1\"></div>\n\n<!-- {{entry.availablePositionsAsOptions}}  -->\n<!-- {{entry.selectedOptionIdentifiers}}   -->\n          <ae-layout v-if=\"entry.availablePositionsAsOptions.length\"\n                        layout-id=\"8bc795f2-7ac8-4b95-9019-0b27b178a36e\"\n                      :param-options=\"entry.availablePositionsAsOptions\"\n                      :param-selected-identifiers=\"entry.selectedOptionIdentifiers\"\n                      @selected=\"controller.selectedPositionOption(entry, $event)\"\n                      class=\"max-w-80 w-80 lg:max-w-md lg:w-md\"\n          ></ae-layout>\n\n          <!-- <ae-control name=\"rd:crm-positions-select\" :document=\"entry.position\" field-name=\"kostenProEinheit\" :param-position-draft=\"entry.draft\" @no-positions=\"{ entry.active = false; entry.noPositonsInAngebot = true; }\"></ae-control> -->\n\n          <ae-layout  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n                      @selected=\"entry.active = !entry.active\"\n                      :param-icon=\"!entry.active ? 'far fa-plus' : 'far fa-trash'\"\n                      param-classes=\"border border-inherit rounded-full\"\n                      :param-disabled=\"entry.noPositonsInAngebot\"\n          ></ae-layout>  \n        </div>\n        <div class=\"\">\n          <ae-design name=\"rd:path\" :document=\"entry.draft\" class=\"text-neutral-500 text-sm\"></ae-design>\n          <!-- <button class=\"ae-button ae-button--small\" @click.stop=\"Aeppic.drillInto(entry.draft)\">\n            Entwurf bearbeiten\n          </button> -->\n        </div>\n      </div>\n    </div>\n  </template>\n  <template #commands=\"scope\">\n    <div class=\"p-4 flex gap-1 justify-end\">\n      <ae-layout  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n      @selected=\"controller.copy()\"\n      param-text=\"Ausgewählte Positionen kopieren\"\n      param-icon=\"far fa-copy\"\n      :param-primary=\"true\"\n      param-classes=\"border border-inherit rounded-full\"\n      :param-disabled=\"locals.entries.every(e => !e.active)\"\n      ></ae-layout>  \n    </div>\n  </template>\n</ae-dialog> ","css":"","controller":"class Controller {\n  async init() {\n    this.FORMS = {\n      Kunde: '8fc08287-dbcc-4e41-9d5d-28e4cd828943',\n      PositionRechnung: 'a40633b4-c769-4457-a267-830738d4b549',\n      Rechnung: '54da686f-3f74-441b-b686-ed2b71dd35cc',\n      PositionsEntwurf: '651d8603-6d29-4534-be59-4449387e857c',\n      ErfassteZeit: 'f9279010-1935-4139-abbb-82d928aa09dc',\n      Command: 'command-form',\n      PositionAngebot: '8d1804b0-c5c6-4acf-a15f-1b02872e792e',\n    }\n\n    locals.positions = []\n    locals.entries = []\n  }\n\n  async show() {\n    \n    await this._update()\n\n    $refs.dialog.show()\n  }\n\n  async _getCompanyProject(offerOrInvoice) {\n    const parentOfContract = await Aeppic.get(offerOrInvoice.a.slice(-2)[0])\n  \n    if (!parentOfContract) {\n      throw new Error('Could not resolve parent of contract')\n    }\n  \n    const command = await Aeppic.findOne(`p:${parentOfContract.f.id} f.id:${this.FORMS.Command} data.name:\"billing_get-project\"`)\n  \n    if (!command) {\n      throw new Error('Could not find command to get company project info')\n    }\n  \n    const commandResult = await Aeppic.Commands.execute(command, parentOfContract)\n  \n    if (!commandResult.ok) {\n      Log.error(commandResult.error)\n      throw new Error('Could not fetch company project info')\n    }\n  \n    return Aeppic.get(commandResult.value)\n  }\n\n  async _update() {\n    locals.entries = []\n\n    const companyProject = await this._getCompanyProject(document)\n\n    const drafts = await Aeppic.find(`a:${companyProject.id} (f.id:${this.FORMS.PositionsEntwurf} OR f.id:${this.FORMS.ErfassteZeit}) data.abgerechnet:false`, { sort: 'data.datum,p,data.name', size: 999 }) \n\n    const angebot = await Aeppic.get(document.data.angebot)\n    const regularPositons = (await Aeppic.getAll(angebot.data.positionen)).filter(p => p?.f.id === this.FORMS.PositionAngebot)\n\n    const getAvailablePositions = (draft) => {\n      const availablePositions = []\n\n      for (const regularPosition of regularPositons) {\n        if (draft.f.id === this.FORMS.ErfassteZeit) {\n          if (regularPosition.data.typ === 'ZEIT') {\n            availablePositions.push(regularPosition)\n          }\n\n          continue\n        }\n\n        if (draft.data.typ === regularPosition.data.typ) {\n          availablePositions.push(regularPosition)\n        }\n      }\n\n      return availablePositions\n    }\n\n    const positions = []\n \n    await Promise.all(\n      drafts.map(async (draft) => {\n        const position = await Aeppic.new(this.FORMS.PositionRechnung, document)\n        const parent = await Aeppic.get(draft.p)\n\n        // DECPRECATED: draft.data.tag\n        position.data.datum = draft.data.datum || draft.data.tag\n\n        const name = draft.data.titel || draft.data.name || ''\n\n        if (parent.data.name) {\n          if (name) {\n            position.data.name = `${parent.data.name} (${name})`\n          } else {\n            position.data.name = parent.data.name\n          }\n        } else {\n          position.data.name = name\n        }\n\n        position.data.dauer = draft.data.dauer || {}\n        position.data.abrechenbar = ('abrechenbar' in draft.data) ? draft.data.abrechenbar : true\n        position.addReference('uebernommenAus', draft)\n\n        if (draft.f.id === this.FORMS.ErfassteZeit) {\n          position.data.typ = 'ZEIT'\n          position.data.menge = 1\n        } else {\n          position.data.typ = draft.data.typ\n          position.data.menge = draft.data.menge\n        }\n\nconsole.log(draft.data, position.data)\n\n        // update calculated fields, if any\n        Aeppic.commit(position)\n\n        const availablePositions = getAvailablePositions(draft)\n\n        const options = availablePositions.map((p) => ({ documentId: p.id, identifier: p.id }))\n        console.log({ availablePositions, options }) \n\n        const entry = {\n          active: !!options.length,\n          noPositonsInAngebot: !options.length,\n          position,\n          draft,\n          availablePositionsAsOptions: options,\n          selectedOptionIdentifiers: []\n          //           selectedOptionIdentifiers: options.length ? [options[0]?.identifier] : []\n        }\n\n        // fill entry.selectedOptionIdentifiers and calculate entry.position\n        if (options.length) {\n          this.selectedPositionOption(entry, options[0])\n        }\n\n        positions.push(entry)\n      })\n    ) \n \n    locals.entries = positions\n  }\n\n  async selectedPositionOption(entry, option) {\n    // update dropdown selected\n    entry.selectedOptionIdentifiers = [option.identifier]\n\n    const selectedPositionOfAngebot = await Aeppic.get(option.identifier)\n\n    // if (!id) {\n    //   // TODO: the field should accept a number I hope\n    //   document.data.kostenProEinheit = 0\n    //   return\n    // }\n\n    const newPosition = entry.position \n\n    newPosition.data.kostenProEinheit = selectedPositionOfAngebot.data.kostenProEinheit\n  \n    if (selectedPositionOfAngebot.data.typ === 'ZEIT' || selectedPositionOfAngebot.data.typ === 'SOCKEL_PLUS_VARIABLE') {\n      const minutesOfAngebot = Aeppic.DateTimeDuration.fromObject(selectedPositionOfAngebot.data.dauer).shiftTo('minutes').minutes * selectedPositionOfAngebot.data.menge\n      const minutesOfNew = Aeppic.DateTimeDuration.fromObject(entry.draft.data.dauer).shiftTo('minutes').minutes\n\n      newPosition.data.menge = minutesOfNew / minutesOfAngebot\n      newPosition.data.dauer = selectedPositionOfAngebot.data.dauer\n    } else if (selectedPositionOfAngebot.data.typ === 'ENTFERNUNG') {\n      newPosition.data.menge = entry.draft.data.menge / selectedPositionOfAngebot.data.menge\n    }\n\n    Aeppic.commit(newPosition)\n  }\n\n  async copy() {\n    for (const entry of locals.entries) {\n      if (!entry.active) {\n        continue\n      }\n\n\n      const editableDraft = await Aeppic.edit(entry.draft)\n      editableDraft.addReference('abrechnungsPosition', entry.position)\n      editableDraft.data.abgerechnet = true\n\n      Aeppic.save(entry.position)\n      Aeppic.save(editableDraft)\n\n      document.addReference(fieldName, entry.position)\n    }\n\n    // FIXME: Save document to ensure consistency with the cleared drafts. This should be changed, once a concept of save-context is there\n    Aeppic.save(document)\n\n    $refs.dialog.close()\n  }\n}  \n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2023-03-10T13:44:30+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2024-04-10T09:57:24+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936830,"ct":1721209936830,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"003033f224aa2384","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"0a290482-9812-4eb8-ae8e-1b9dce4df97d","v":"1689fed5d0710c25","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"crm-positions-select","template":"<select @input=\"controller.addPositionToNewPosition($event.target.value)\" class=\"\">\n  <option v-for=\"(position, index) of locals.positions\" :value=\"position.id\">\n    <ae-design name=\"record-title\" :document=\"position\"></ae-design> \n  </option>\n</select>\n\n","css":"","controller":"class Controller {\n  async init() {\n    this.FORMS = {\n      PositionAngebot: '8d1804b0-c5c6-4acf-a15f-1b02872e792e',\n      ErfassteZeit: 'f9279010-1935-4139-abbb-82d928aa09dc',\n    }\n\n    locals.positions = []\n\n    if (!params.positionDraft) {\n      throw new Error('Provide params.positionDraft')\n      return\n    }\n\n    try {\n      const rechnung = await Aeppic.get(document.p)\n      const angebot = await Aeppic.get(rechnung.data.angebot)\n      const allPositions = await Aeppic.getAll(angebot.data.positionen)\n      locals.positions = allPositions.filter(p => p && p.f.id === this.FORMS.PositionAngebot && p.data.typ === document.data.typ)\n\n      if (locals.positions.length) {\n        this.addPositionToNewPosition(locals.positions[0].id)\n      } else {\n        $emit('no-positions')\n      }\n    } catch (e) {\n      $emit('no-positions')\n    }\n  }\n\n  async addPositionToNewPosition(id) {\n    if (!id) {\n      // TODO: the field should accept a number I hope\n      document.data.kostenProEinheit = 0\n      return\n    }\n\n    const selectedPosition = await Aeppic.get(id)\n    document.data.kostenProEinheit = selectedPosition.data.kostenProEinheit\n  \n    if (selectedPosition.data.typ === 'ZEIT' || selectedPosition.data.typ === 'SOCKEL_PLUS_VARIABLE') {\n      const minutesOfAngebot = Aeppic.DateTimeDuration.fromObject(selectedPosition.data.dauer).shiftTo('minutes').minutes * selectedPosition.data.menge\n      const minutesOfNew = Aeppic.DateTimeDuration.fromObject(params.positionDraft.data.dauer).shiftTo('minutes').minutes\n\n      document.data.menge = minutesOfNew / minutesOfAngebot\n      document.data.dauer = selectedPosition.data.dauer\n    } else if (selectedPosition.data.typ === 'ENTFERNUNG') {\n      document.data.menge = params.positionDraft.data.menge / selectedPosition.data.menge\n    }\n\n    Aeppic.commit(document)\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-04-09T12:44:03+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-04-09T13:07:00+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936830,"ct":1721209936830,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"6b67baeff8475ecb","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"06f2ee65-607b-4c02-907f-4b5625ecce49","v":"8ea32cb3afdb22b9","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"css","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:70vh\" v-model=\"field\" :readonly=\"readonly\" theme=\"vs-dark\" language=\"less\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>\n","css":"","controller":"class Controller {\n  async getHoverContent(word) {\n    const doc = await Aeppic.get(word)\n   \n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0a99614c3b79c920","text":"Monaco Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-10-12T11:48:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2023-01-20T15:05:55+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936830,"ct":1721209936830,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"2006e48de30a9745","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"2acbd49a-2a37-498c-b335-8c636b878041","v":"0f3e5c80625265bd","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"currency","template":"<div class=\"border rounded-md flex gap-1 bg-white\">\n  <div class=\"px-2 flex items-center border-r pr-2 text-neutral-500\" @click.stop>\n    <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n          @selected=\"$refs.popup.show({ target: $refs.button.$el })\"\n          ref=\"button\"\n          :param-icon=\"locals.currencies.find(c => c.code  === field.currency)?.icon || locals.currencies[0].icon\"\n          param-classes=\"border-none\"\n    ></ae-layout>\n\n\n    <!-- <select v-model=\"field.currency\">\n      <option value=\"\">-</option>\n      <option v-for=\"currency of locals.currencies\"\n              :value=\"currency.code\"\n              :selected=\"field.currency === currency.code\"\n      >\n        <ae-icon :class=\"[currency.icon]\"></ae-icon>  \n        {{currency.symbol}}\n      </option>\n    </select> -->\n  </div>\n \n  <input :readonly=\"readonly\" class=\"flex-1 px-1\" ref=\"number\" v-if=\"!locals.showViewValue\" v-focus @focus=\"locals.showViewValue = false\" @blur=\"locals.showViewValue = true\" type=\"number\" :value=\"field.amount\" @input=\"controller.setField($event.target.value)\">\n  <input :readonly=\"readonly\" class=\"flex-1 px-1\" v-show=\"locals.showViewValue\" type=\"text\" :value=\"controller.viewValue\" @focus=\"locals.showViewValue = false\">\n</div>\n\n<ae-popup ref=\"popup\">\n  <div class=\"py-1 px-4 flex flex-col gap-2\">\n    <div v-for=\"currency of locals.currencies\"\n          @click.stop=\"controller.setCurrency(currency)\"\n      >\n      <ae-icon :class=\"[currency.icon]\"></ae-icon>  \n      <!-- {{currency.code}} -->\n    </div>\n  </div>\n</ae-popup>","css":"","controller":"class Controller {\n  init() {\n    // Use ISO 4217 code!\n    this.DEFAULT_CURRENCY = 'EUR'\n    this.DEFAULT_PRECISION = 2\n    this.DEFAULT_LANGUAGE = 'de-DE'\n    locals.showViewValue = true    \n\n    locals.currencies = [\n      { code: 'EUR', symbol: '€', icon: 'far fa-euro-sign' },\n      { code: 'USD', symbol: 'US$', icon: 'far fa-dollar-sign' },\n      { code: 'JPY', symbol: '¥', icon: 'far fa-yen-sign' },\n      { code: 'GBP', symbol: '£', icon: 'far fa-pound-sign' },\n    ]\n  }\n\n  setField(value) {\n    if (readonly) {\n      return\n    }\n\n    const valueOrZero = value || 0\n    const field = getField()\n    field.precision = this.DEFAULT_PRECISION\n    field.currency = field.currency || this.DEFAULT_CURRENCY\n    field.amount = Number.parseFloat(Number.parseFloat(valueOrZero).toFixed(field.precision))\n\n    setField(field)\n  }\n\n  get viewValue() {\n    const field = getField()\n\n    if (!field.currency) {\n      return `${field.amount}`\n    }\n\n    const numberFormat = new Intl.NumberFormat(navigator.language || this.DEFAULT_LANGUAGE, { style: 'decimal', minimumFractionDigits: 2 })\n\n    if (typeof numberFormat.format !== 'function') {\n      return `${field.amount}`\n    }\n\n    return numberFormat.format(field.amount)\n  }\n\n  setCurrency(currency) {\n    if (readonly) {\n      return\n    }\n\n    const field = getField() \n    field.currency = currency.code\n    setField(field)\n\n    $refs.popup?.close()\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2022-12-02T10:03:22+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2023-04-18T14:13:26+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936831,"ct":1721209936831,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"7b8ac07de0ec16ca","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"50986b4c-73c7-4844-8b15-5f916cdb1284","v":"0212d801eedc9d55","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"currency","template":"<div class=\"_CSS-container\">\n  <input style=\"min-width:0;\" ref=\"number\" v-if=\"!locals.showViewValue\" v-focus @focus=\"locals.showViewValue = false\" @blur=\"locals.showViewValue = true\" type=\"number\" :value=\"field.amount\" @input=\"controller.setField($event.target.value)\">\n  <input style=\"min-width:0;\" v-show=\"locals.showViewValue\" type=\"text\" :value=\"controller.viewValue\" @focus=\"locals.showViewValue = false\">\n  <select v-model=\"field.currency\" style=\"min-width:20px;width:min-content;\">\n    <option value=\"\">-</option>\n    <option v-for=\"currency of locals.currencies\"\n            :value=\"currency.code\"\n            :selected=\"field.currency === currency.code\"\n    >\n      <!-- <ae-icon :class=\"[currency.icon]\"></ae-icon> -->\n      {{currency.symbol}}\n    </option>\n  </select>\n</div>","css":"._CSS-container {\n  display: flex;\n}","controller":"class Controller {\n  init() {\n    // Use ISO 4217 code!\n    this.DEFAULT_CURRENCY = 'EUR'\n    this.DEFAULT_PRECISION = 2\n    this.DEFAULT_LANGUAGE = 'de-DE'\n    locals.showViewValue = true    \n\n    locals.currencies = [\n      { code: 'EUR', symbol: '€', icon: 'fa fa-euro-sign' },\n      { code: 'USD', symbol: 'US$', icon: 'fa fa-dollar-sign' },\n      { code: 'JPY', symbol: '¥', icon: 'fa fa-yen-sign' },\n      { code: 'GBP', symbol: '£', icon: 'fa fa-pound-sign' },\n    ]\n  }\n\n  setField(value) {\n    const valueOrZero = value || 0\n    const field = getField()\n    field.precision = this.DEFAULT_PRECISION\n    field.currency = field.currency || this.DEFAULT_CURRENCY\n    field.amount = Number.parseFloat(Number.parseFloat(valueOrZero).toFixed(field.precision))\n\n    setField(field)\n  }\n\n  get viewValue() {\n    const field = getField()\n\n    if (!field.currency) {\n      return `${field.amount}`\n    }\n\n    const numberFormat = new Intl.NumberFormat(navigator.language || this.DEFAULT_LANGUAGE, { style: 'currency', currency: field.currency })\n\n    if (typeof numberFormat.format !== 'function') {\n      return `${field.amount}`\n    }\n\n    return numberFormat.format(field.amount)\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-09-04T09:55:08+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2021-05-28T08:47:47+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936830,"ct":1721209936830,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"ef3a68fa7b7aeec5","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"b2608d20-d1c1-4d90-9de0-a20c60776a74","v":"33e5600b6ae0b445","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"wip","name":"currency","template":"<!-- <input\r\n    style=\"width:100%;max-width:150px\"\r\n    type=\"number\"\r\n    :min=\"params.min\"\r\n    :max=\"params.max\"\r\n    :placeholder=\"params.placeholder\"\r\n    :step=\"params.step||1\"\r\n    v-model.number=\"field\"\r\n    :required=\"fieldInfo.required\"\r\n    :readonly=\"params.readonly != null ? params.readonly : readonly\"\r\n    :aria-label=\"label\">\r\n<span v-if=\"params.unit\"  class=\"_CSS-unit\">\r\n  {{params.unit}}\r\n</span> -->\r\n\r\n<template v-if=\"typeof field === 'object'\">\r\n  <CurrencyInput \r\n  :min=\"params.min\"\r\n  :max=\"params.max\"\r\n  :currency=\"params.currency\"\r\n  v-model=\"field.amount\"></CurrencyInput>\r\n</template>\r\n<template v-else>\r\n  <CurrencyInput \r\n  :min=\"params.min\"\r\n  :max=\"params.max\"\r\n  :currency=\"params.currency\"\r\n  v-model=\"field\"></CurrencyInput>\r\n</template>","css":"","controller":"init() {\r\n  const field = getField()\r\n\r\n  if (typeof field === 'object') {\r\n    field.currency  = params.currency || 'EUR'\r\n    field.precision = params.precision || 2\r\n  }\r\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"ab2b3bef-ba12-427a-9659-edb734faf178","v":"a45871ed59ca600b","text":"Aeppic Currency Input"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-04-30T11:49:01+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","from":"","client":"server"},"modified":{"at":"2021-05-28T08:47:50+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936830,"ct":1721209936830,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"18edba1480244587","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"0bb69d6b-dee3-4729-aaf7-de1801eb4f3e","v":"5be17bd95827af3e","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"date","template":"<div v-if=\"fieldInfo.cardinality\" class=\"_CSS-list ae-form-cell__input\">\n  <div  class=\"_CSS-entry\"\n        v-for=\"(value, index) of field\"\n  >\n    <ae-layout layout-id=\"3e185751-e4f9-4493-b73d-3c99055d1b86\"\n              :param-value=\"value\"\n              @input=\"controller.onInputCardinality(index, $event)\"\n              :param-required=\"fieldInfo.required\"\n              :param-readonly=\"readonly\"\n              :param-inline=\"params.inline\"\n              :param-min=\"params.min\"\n              :param-max=\"params.max\"\n              :param-min-field=\"params.minField\"\n              :param-max-field=\"params.maxField\"\n              :param-document=\"document\"\n    ></ae-layout>\n    <button v-if=\"!readonly\"  class=\"ae-button\" @click.stop=\"controller.removeEntry(index)\">\n      <ae-icon class=\"fa fa-times\"></ae-icon>\n    </button>\n\n  </div>\n  <div style=\"width:100%;\" v-if=\"!readonly\">\n    <button  class=\"ae-button\" @click.stop=\"controller.addEntry()\">\n      <ae-icon class=\"fa fa-plus\"></ae-icon>\n    </button>\n  </div>\n</div>\n<ae-layout  v-else\n            layout-id=\"3e185751-e4f9-4493-b73d-3c99055d1b86\"\n            :param-value=\"field\"\n            @input=\"controller.onInput($event)\"\n            :param-required=\"fieldInfo.required\"\n            :param-readonly=\"readonly\"\n            :param-inline=\"params.inline\"\n            :param-min=\"params.min\"\n            :param-max=\"params.max\"\n            :param-min-field=\"params.minField\"\n            :param-max-field=\"params.maxField\"\n            :param-document=\"document\"\n></ae-layout> ","css":"._CSS-list {\n  display: flex;\n  flex-wrap: wrap;\n}\n\n._CSS-entry {\n  display: flex;\n  margin-right: var(--ae-space-3);\n}","controller":"onInput(value) {\n  setField(value)\n\n  $emit('input')\n}\n\nonInputCardinality(index, value) {\n  const field = getField()\n  field.splice(index, 1, value)\n  setField(field)\n  \n    $emit('input')\n\n}\n\naddEntry() {\n  const field = getField()\n  field.push('')\n  setField(field)\n}\n\nremoveEntry(index) {\n  const field = getField()\n  field.splice(index, 1)\n  setField(field)\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-07-05T10:04:45+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2021-12-15T09:18:07+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936832,"ct":1721209936832,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"b885af0c799bfa1a","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"15568f94-377b-4e55-9830-dd8f561bab79","v":"38039b8d8c4f3448","p":"91f205bc-9989-4fbe-8966-bd4ba9777c26","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"date","template":"<input :readonly=\"readonly\" type=\"date\" :value=\"controller.htmlDate(field)\" @input=\"field = controller.toISOStringWithOffset($event.target.value)\"\n  :required=\"fieldInfo.required\" :aria-label=\"label\" style=\"width:100%\">\n","css":"","controller":"class Controller {\n  htmlDate(value) {\n    if (value) {\n      return new Date(value).toISOString().slice(0, 10)\n    }\n    return ''\n  }\n\n  localeDateString(value) {\n    if (value) {\n      return new Date(value).toLocaleDateString()\n    }\n    return ''\n  }\n\n  toISOStringWithOffset(value) {\n    if (!value) {\n      return ''\n    }\n\n    const date = new Date(value)\n\n    var signedOffset = -(date.getTimezoneOffset())\n    var absoluteOffset = Math.abs(signedOffset)\n    return (\n      // Date\n      (\n        this.pad(date.getFullYear(), 4) + '-' +\n        this.pad(date.getMonth() + 1, 2) + '-' +\n        this.pad(date.getDate(), 2)\n      ) +\n      // Time\n      (\n        'T' +\n        this.pad(date.getHours(), 2) + ':' +\n        this.pad(date.getMinutes(), 2) + ':' +\n        this.pad(date.getSeconds(), 2)\n      ) +\n      // Offset\n      (\n        (signedOffset >= 0 ? '+' : '-') +\n        this.pad(absoluteOffset / 60, 2) +\n        this.pad(absoluteOffset % 60, 2)\n      )\n    )\n  }\n\n  pad(number, length) {\n    var string = number.toString()\n    var stringLength = string.length\n    return stringLength < length\n      ? '0'.repeat(length - stringLength) + string\n      : string\n  }\n}\n","parameters":[],"standalone":"FIELD","cardinality":"SINGLE","limitTypes":true,"types":["date","datetime"],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2017-08-29T10:31:16+02:00","by":"","from":"","client":"d2db1053-3d9f-4fb3-a5ae-9b2b3724842f"},"modified":{"at":"2024-07-17T09:49:55+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936832,"ct":1721209936832,"a_depth":3,"a":["system","controls-folder","91f205bc-9989-4fbe-8966-bd4ba9777c26"],"a_forms":["system-folder-form","repository-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null,null],"stamps":[],"previous":{"v":"e230cf2a11e7bc06","f":{"id":"control-form","v":"9bddead515b3a305","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2023-07-14T11:52:04+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936832,"data":{"cardinality":"BOTH","controller":"class Controller {\n  onInput(value) {\n    setField(value)\n    \n    $emit('input')\n  }\n  \n  onInputCardinality(index, value) {\n    const field = getField()\n    field.splice(index, 1, value)\n    setField(field)\n    \n    $emit('input')\n    \n  }\n  \n  addEntry() {\n    const field = getField()\n    field.push('')\n    setField(field)\n\n\n    this.edit(field.length - 1)\n  }\n  \n  removeEntry(index) {\n    const field = getField()\n    field.splice(index, 1)\n    setField(field)\n  }\n\n  // new methods below\n\n  edit(index) {\n    $refs.layout.$emit('focus-at-index', index)\n  }\n\n  update(args) {\n    const { index, value } = args\n\n    let field = getField()\n    \n    if (Array.isArray(getField())) { \n      if (value) {\n        field.splice(index, 1, value)\n      } else {\n        field.splice(index, 1, '') \n      }\n    } else {\n      field = value\n    }\n\n \n    setField(field) \n  }\n\n  remove(args) {\n    const { index } = args\n\n    let field = getField()\n\n    if (Array.isArray(getField())) { \n      field.splice(index, 1) \n    } else {\n      field = ''\n    }\n\n    setField(field)\n\n    this.edit(null)\n  }\n}","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"date","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<div v-if=\"fieldInfo.cardinality\" class=\"w-full relative min-h-[2em]\" :data-test=\"fieldName\">\n  <ae-layout class=\"w-full\"\n          layout-id=\"866974ba-6e69-483b-8933-2ec6ac66ef1c\"\n          :param-entries=\"field\"\n          :param-cardinality=\"fieldInfo.cardinality\"\n          param-icon=\"far fa-calendar-alt\"\n          @add=\"controller.addEntry()\"\n          @remove=\"controller.remove($event)\"\n          @update=\"controller.update($event)\"\n          ref=\"layout\"\n          :param-border-color=\"validationErrors?.required && 'border-red-500'\"\n          :param-readonly=\"readonly\"\n          param-class-min-entry-width=\"min-w-64\"\n          param-add-layout-id=\"1a42b378-3876-4ef9-b0bc-9557009a1d69\"\n          :param-add-layout-params=\"{\n            required: fieldInfo.required,\n            readonly: readonly,\n            inline: params.inline,\n            min: params.min,\n            max: params.max,\n            minField: params.minField,\n            maxField: params.maxField,\n            initialShowPopover: true,\n            noIcon: true,\n            disableRemove: true,\n            type: params.type,\n          }\"\n          param-entry-layout-id=\"1a42b378-3876-4ef9-b0bc-9557009a1d69\"\n          :param-entry-layout-params=\"{\n            readonly: true,\n            noIcon: true,\n            disableRemove: true,\n            type: params.type,\n          }\"\n  ></ae-layout>\n</div>\n<ae-layout  v-else-if=\"params.type === 'year-month'\"\n            layout-id=\"1a42b378-3876-4ef9-b0bc-9557009a1d69\"\n            :param-value=\"field\"\n            @input=\"controller.onInput($event)\"\n            :param-required=\"fieldInfo.required\"\n            :param-readonly=\"readonly\"\n            :param-inline=\"params.inline\"\n            :param-min=\"params.min\"\n            :param-max=\"params.max\"\n            :param-min-field=\"params.minField\"\n            :param-max-field=\"params.maxField\"\n            :param-type=\"params.type\"\n            :param-document=\"document\"\n            :data-test=\"fieldName\"\n></ae-layout> \n<ae-layout  v-else\n            layout-id=\"1a42b378-3876-4ef9-b0bc-9557009a1d69\"\n            :param-value=\"field\"\n            @input=\"controller.onInput($event)\"\n            :param-required=\"fieldInfo.required\"\n            :param-readonly=\"readonly\"\n            :param-inline=\"params.inline\"\n            :param-min=\"params.min\"\n            :param-max=\"params.max\"\n            :param-min-field=\"params.minField\"\n            :param-max-field=\"params.maxField\"\n            :param-document=\"document\"\n            :data-test=\"fieldName\"\n></ae-layout> \n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"7445cbef-5250-415b-a4aa-47ec81f9f1b4","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-05-19T12:09:46+00:00","backedBy":null,"by":"729fa027-7e7e-4b06-9602-f535c43824f0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"555d69df62eb2d50"},"stamps":[],"t":1747656586133,"v":"26a836a88e9abf7a"},{"id":"2759b0d7-c84d-469d-a1d7-444613bfc780","v":"3fd65c40e6e07c49","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"charite","name":"datepicker","template":"<datepicker \n  :value=\"controller.toDate(field)\" \n  @input=\"(value) => {field = controller.toISOStringWithOffset(value)}\" \n  input-class=\"_CSS-picker-field\"\n  :inline=\"true\"\n  lang=\"de\" \n  :monday-first=\"true\" \n  :use-utc=\"false\"\n  :highlighted=locals.highlighted>\n</datepicker>\n","css":"._CSS-picker-field {\n  width: 100%;\n}\n\n._CSS .cell.highlighted._CSS-highlight-1 {\n  background: red;\n}\n\n._CSS .cell.highlighted._CSS-highlight-2._CSS-highlight-2 {\n  color: green;\n}","controller":"class Controller {\n\n  init(){\n    locals.highlighted = {\n      dates: [ new Date('2019-02-14T00:00:00+0100') ],\n      customPredictor: function(date) {\n        if(date.getDate() % 4 == 0){\n          return '_CSS-highlight-1'\n        }\n        else if(date.getDate() % 5 == 0){\n          return '_CSS-highlight-2'\n        }\n      },\n      includeDisabled: false\n    }\n  }\n\n  toDate(value) {\n    if(value){\n      return new Date(value)\n    }\n    else{\n      return null\n    }\n  }\n\n  localeDateString(value) {\n    if (value) {\n      return new Date(value).toLocaleDateString()\n    }\n    return ''\n  }\n\n  toISOStringWithOffset(value) {\n    if (!value) {\n      return ''\n    }\n\n    const date = new Date(value)\n\n    var signedOffset = -(date.getTimezoneOffset())\n    var absoluteOffset = Math.abs(signedOffset)\n    return (\n      // Date\n      (\n        this.pad(date.getFullYear(), 4) + '-' +\n        this.pad(date.getMonth() + 1, 2) + '-' +\n        this.pad(date.getDate(), 2)\n      ) +\n      // Time\n      (\n        'T' +\n        this.pad(date.getHours(), 2) + ':' +\n        this.pad(date.getMinutes(), 2) + ':' +\n        this.pad(date.getSeconds(), 2)\n      ) +\n      // Offset\n      (\n        (signedOffset >= 0 ? '+' : '-') +\n        this.pad(absoluteOffset / 60, 2) +\n        this.pad(absoluteOffset % 60, 2)\n      )\n    )\n  }\n\n  pad(number, length) {\n    var string = number.toString()\n    var stringLength = string.length\n    return stringLength < length\n      ? '0'.repeat(length - stringLength) + string\n      : string\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"ef7e8b10-f09a-47a0-ba25-2aabe9661396","v":"10439297ce26ee93","text":"vuejs-datepicker"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-01-31T10:51:14+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936833,"ct":1721209936833,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"a8e0fccb2fb45a8a","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2023-02-27T16:01:03+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936833,"data":{"cardinality":"BOTH","controller":"class Controller {\n  // async init() {\n    // TODO: make use of this fallback and fender date, month, year select boxed for eg. safari desktop browser\n    // locals.useFallback = false\n    // await Aeppic.nextRenderPass()\n    // locals.useFallback = $refs.input.type !== 'date'\n  // }\n\n  init() {\n    $on('field-updated', () => {\n      Aeppic.commit(document)\n    }) \n  }\n\n\n  htmlDate(value) {\n    if (value) {\n      return Aeppic.DateTime.fromISO(value).toFormat(\"yyyy-MM-dd'T'T\")\n    }\n    return ''\n  }\n\n  toISOStringWithOffset(value) {\n    if (!value) {\n      return ''\n    }\n\n    const date = new Date(value)\n\n    return Aeppic.DateTime.fromJSDate(date).toFormat(\"yyyy-LL-dd'T'HH:mm:ssZZ\")\n  }\n}\n","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"datetime","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<ae-layout layout-id=\"1a42b378-3876-4ef9-b0bc-9557009a1d69\"\n            :param-value=\"field\"\n            @input=\"field = $event\"\n            :param-required=\"validationErrors?.required\"\n            :param-readonly=\"readonly\"\n            :param-inline=\"params.inline\"\n            :param-min=\"params.min\"\n            :param-max=\"params.max\"\n            :param-min-field=\"params.minField\"\n            :param-max-field=\"params.maxField\"\n            :param-document=\"document\"\n            :param-disable-remove=\"params.disableRemove\"\n            param-mode=\"dateTime\"\n            :data-test=\"fieldName\"\n></ae-layout>\n\n<!-- NOTE: old code using native html control  -->\n<!-- <input  :readonly=\"readonly\"\n        :disabled=\"readonly\"\n        type=\"datetime-local\"\n        :value=\"controller.htmlDate(field)\"\n        @input=\"field = controller.toISOStringWithOffset($event.target.value)\"\n        :required=\"fieldInfo.required\"\n        :aria-label=\"label\"\n        style=\"width:100%\"\n        :min=\"params.min\"\n        :max=\"params.max\"\n> -->  ","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"5348c012-2037-4ba8-86cb-fcaaaf76b4e2","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-04-25T12:29:21+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"fe8082a45d64c331"},"stamps":[],"t":1745584161569,"v":"993ab95871f19cb3"},{"id":"d6647125-8cd0-4762-80cf-ee7dcd6e1a50","v":"0c0ea8d2b42fc213","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"datetime","template":"<ae-layout layout-id=\"3e185751-e4f9-4493-b73d-3c99055d1b86\"\n            :param-value=\"field\"\n            @input=\"field = $event\"\n            :param-required=\"fieldInfo.required\"\n            :param-readonly=\"readonly\"\n            :param-inline=\"params.inline\"\n            :param-min=\"params.min\"\n            :param-max=\"params.max\"\n            :param-min-field=\"params.minField\"\n            :param-max-field=\"params.maxField\"\n            :param-document=\"document\"\n            param-mode=\"dateTime\"\n></ae-layout>\n\n\n<!-- NOTE: old code using native html control  -->\n<!-- <input  :readonly=\"readonly\"\n        :disabled=\"readonly\"\n        type=\"datetime-local\"\n        :value=\"controller.htmlDate(field)\"\n        @input=\"field = controller.toISOStringWithOffset($event.target.value)\"\n        :required=\"fieldInfo.required\"\n        :aria-label=\"label\"\n        style=\"width:100%\"\n        :min=\"params.min\"\n        :max=\"params.max\"\n> --> ","css":"","controller":"class Controller {\n  // async init() {\n    // TODO: make use of this fallback and fender date, month, year select boxed for eg. safari desktop browser\n    // locals.useFallback = false\n    // await Aeppic.nextRenderPass()\n    // locals.useFallback = $refs.input.type !== 'date'\n  // }\n\n\n  htmlDate(value) {\n    if (value) {\n      return Aeppic.DateTime.fromISO(value).toFormat(\"yyyy-MM-dd'T'T\")\n    }\n    return ''\n  }\n\n  toISOStringWithOffset(value) {\n    if (!value) {\n      return ''\n    }\n\n    const date = new Date(value)\n\n    return Aeppic.DateTime.fromJSDate(date).toFormat(\"yyyy-LL-dd'T'HH:mm:ssZZ\")\n  }\n}\n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-12-03T13:49:18+00:00","by":"c5ed555f-cc11-463b-960b-0a60b3118e69","from":"","client":"server"},"modified":{"at":"2021-12-15T09:12:52+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936833,"ct":1721209936833,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"b7ac5305a1ad8bbd","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2023-02-21T13:29:39+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936833,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    this.modes = [\n      'SELECTORS',\n      'today',\n      'tomorrow',\n      'next-monday',\n      'from-to',\n      'datetime',\n    ]\n    locals.rerenderKey = 0\n\n\n    locals.mode = 'SELECTORS'\n    locals.datePickerMode = document.data[params.hasTime] ? 'dateTime' : 'date'\n    locals.isRange = !!(getField() && document.data[params.to])\n\n    if (!params.to) {\n      throw new Error('parameter \"to\" must be another fieldName')\n    }\n\n    locals.value = {\n      start: getField(), \n      end: document.data[params.to]\n    }\n  }\n\n  async show() {\n    if (readonly) {\n      return\n    }\n\n    $refs.popup.show({ target: $refs.container })\n\n\n    await Aeppic.nextRenderPass()\n    await Aeppic.nextRenderPass()\n    \n\n    $refs.popup?.position()\n  }\n\n  toggleDateTime() {\n    if (readonly) {\n      return\n    }\n    \n    if (locals.datePickerMode === 'dateTime') {\n      locals.datePickerMode = 'date'\n    } else {\n      locals.datePickerMode = 'dateTime'\n    }\n  }\n\n  updateField(event) {\n    if (readonly) {\n      return\n    }\n\n    if (params.type === 'year-month') {\n      const start = typeof event === 'object' ? event.start : event\n      const startDt = Aeppic.DateTime.fromISO(start)\n      \n      this.setFields(this.toDate(startDt), this.toDate(startDt), false)\n\n      return\n    } \n    \n    if (typeof event === 'object') {\n      const { start, end } = event\n\n      const startDt = start && Aeppic.DateTime.fromISO(start)\n      let endDt = end && Aeppic.DateTime.fromISO(end)\n\n      // if (params.keepTimeDistance) {\n      //   if (startDt && endDt) {\n      //     const previousStart = Aeppic.DateTime.fromISO(getField())\n      //     const previousEnd = Aeppic.DateTime.fromISO(document.data[params.to])\n          \n      //     const distance = previousEnd.diff(previousStart).as('hours')\n      //     console.log(distance) \n\n      //     if (distance > 0) {\n      //       endDt = endDt.plus({ hours: distance })\n      //     }\n      //   }\n      // }\n\n      if (locals.datePickerMode === 'dateTime') {\n        this.setFields(startDt && this.toDateTime(startDt), endDt && this.toDateTime(endDt), true)\n      } else {\n        this.setFields(startDt && this.toDate(startDt), endDt && this.toDate(endDt), false)\n      }\n\n      return\n    } \n\n\n    if (!event) {\n      this.setFields('', null, !!locals.datePickerMode)\n      return\n    }\n\n    const start = Aeppic.DateTime.fromISO(event)\n\n    if (locals.datePickerMode === 'dateTime') {\n      this.setFields(this.toDateTime(start), null, true)\n    } else {\n      this.setFields(this.toDate(start), null, false)\n    }\n  }\n\n  unset() {\n    if (readonly) {\n      return\n    }\n    \n    this.setFields()\n    locals.rerenderKey += 1\n\n    $refs.popup && $refs.popup.close()\n  }\n\n  toDate(date) {\n    return date.startOf('day').toFormat(\"yyyy-LL-dd'T'HH:mm:ssZZ\")\n  }\n\n  toDateTime(date) {\n    return date.toFormat(\"yyyy-LL-dd'T'HH:mm:ssZZ\")\n  }\n\n  toTimePartOnly(date) {\n    return date.toFormat(\"'T'HH:mm:ssZZZ\")\n  }\n\n  toDatePartOnly(date) {\n    return date.toFormat(\"yyyy-LL-dd'\")\n  }  \n\n  setFields(from, to, isDatetime) {\n    if (readonly) {\n      return\n    }\n    \n\n    document.data[params.to] = to || ''\n    document.data[params.hasTime] = !!isDatetime ?? false\n\n    // document.data[fieldName] = from || ''\n    setField(from || '')\n\n    // Aeppic.nextRenderPass(() => {\n      if (to) {\n        locals.value = { \n          start: from,\n          end: to,\n        }\n      } else {\n        locals.value = { start: from }\n      }\n    // })\n  }\n\n  get datesString() {\n    const data = Array.isArray(document.data[fieldName]) ? document.data[fieldName] : [document.data[fieldName]]\n    const endData = Array.isArray(document.data[params.to]) ? document.data[params.to] : [document.data[params.to]]\n    const hasTimeData = Array.isArray(document.data[params.hasTime]) ? document.data[params.hasTime] : [document.data[params.hasTime]]\n\n    return data.map((date, index) => {\n      if (!date) {\n        return ''\n      }\n\n      const hasTime = hasTimeData[index]\n      const FORMAT = hasTime ? Aeppic.DateTime.DATETIME_SHORT : Aeppic.DateTime.DATE_SHORT\n  \n      const startField = date\n      const endField = endData[index]\n  \n      if (startField && endField) {\n        const start = Aeppic.DateTime.fromISO(startField)\n        const end = Aeppic.DateTime.fromISO(endField)\n      \n        const isSameDay = start.day === end.day && start.month === end.month && start.year === end.year\n  \n        if (isSameDay) {\n          if (hasTime) { \n            return `${start.toLocaleString(FORMAT)} - ${end.toLocaleString(Aeppic.DateTime.TIME_24_SIMPLE)}`\n          }\n  \n          return `${start.toLocaleString(FORMAT)}`\n        }\n  \n        return `${start.toLocaleString(FORMAT)} - ${end.toLocaleString(FORMAT)}`\n      }\n  \n      if (startField) {\n        return Aeppic.DateTime.fromISO(startField).toLocaleString(FORMAT)\n      }\n  \n      return ''\n    }).filter(Boolean).join(', ')\n  }\n\n  get requiredFailed() {\n    if (!fieldInfo.required) {\n      return false\n    }\n\n    const field = getField()\n\n    return !field\n  }\n}\n","css":"._CSS-display {\n  display: flex;\n  align-items: center;\n}\n\n._CSS-datestring {\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n._CSS-display:hover {\n  cursor: pointer;\n}\n\n._CSS-types {\n  display: grid;\n}\n\n._CSS-popup {\n  min-width: 200px;\n  position: relative;\n}\n\n._CSS-popup__header {\n  position: absolute;\n  right: 0;\n  top: 0;\n\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n  padding-left: var(--ae-size-1);\n}\n\n._CSS-popup__body {\n  padding: var(--ae-size-2);\n} \n\n._CSS-input-datetime {\n  display: flex;\n}","description":"","device":"ALL","label":"","limitTypes":false,"name":"datetime-range","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<!-- {{params}}\n<pre>\n{{document.data}}\n</pre> -->\n<div\n  class=\"border rounded-md py-1 px-2 flex gap-2 bg-white dark:bg-slate-800\"\n  :class=\"{ 'border-red-500': controller.requiredFailed }\"\n  @click.stop=\"controller.show()\"\n  data-ae-form-field-input\n  ref=\"container\"\n>\n  <div class=\"flex items-center border-r -my-1 pr-2 text-neutral-500\" @click.stop>\n    <ae-icon class=\"far fa-calendar-alt\"></ae-icon>\n  </div>\n\n  <div class=\"flex-1 flex flex-wrap gap-1 overflow-hidden\">\n    <div v-if=\"controller.datesString\" class=\"pl-1 flex items-center gap-2 w-full overflow-hidden\">\n      {{ controller.datesString }}\n    </div>\n    <div v-else class=\"italic text-neutral-400\">Datum auswählen</div>\n  </div>\n\n  <ae-layout\n    layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n    @selected=\"controller.unset()\"\n    class=\"self-center\"\n    param-icon=\"far fa-times\"\n    param-classes=\"bg-neutral-100 hover:bg-neutral-200 text-xs rounded-full\"\n    v-if=\"controller.datesString\"\n  ></ae-layout>\n</div>\n\n<ae-popup ref=\"popup\" \n        @click.native.stop \n        @close=\"() => { locals.mode = 'SELECTORS' }\"\n        placement=\"bottom-start\"\n        @show=\"() => { params.type === 'year-month' && $refs.dateInput.$emit('emit-update-now') }\"\n>\n  <template v-slot:default=\"scope\">\n    <div>\n      <ae-layout\n        ref=\"dateInput\"\n        layout-id=\"1a42b378-3876-4ef9-b0bc-9557009a1d69\"\n        :param-is-range=\"locals.isRange\"\n        :param-inline=\"true\"\n        :param-value=\"locals.value\"\n        :param-mode=\"locals.datePickerMode\"\n        :param-has-time=\"true\"\n        :param-type=\"params.type\"\n        @input=\"controller.updateField($event)\"\n        @toggle-time=\"controller.toggleDateTime()\"\n        @toggle-range=\"locals.isRange = !locals.isRange\"\n        :key=\"id(locals.rerenderKey)\"\n      ></ae-layout>\n    </div>\n  </template>\n</ae-popup>\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"738ff061-0e21-45b2-9a20-861af5c777b3","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-08-14T10:45:17+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"624dcca04d4aecf3"},"stamps":[],"t":1755168317964,"v":"ffccf4609eca108e"},{"id":"af6bc9f3-8063-4642-b905-65ce948a17c3","v":"c6f115d9e32e0051","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"datetime-range","template":"<div class=\"ae-form-cell__input _CSS-display\" ref=\"input\" @click.stop=\"$refs.popup.show({ target: $refs.input })\">\n  <span class=\"_CSS-datestring\" :title=\"controller.datesString\" :key=\"controller.datesString\"> {{ controller.datesString }} </span>\n\n  <button class=\"ae-button ae-button--small\" v-if=\"field\" @click.stop=\"controller.unset()\">\n    <ae-icon class=\"fa fa-times\"></ae-icon>\n  </button>\n\n</div>\n<ae-popup ref=\"popup\" @close=\"() => { locals.mode = 'SELECTORS' }\">\n  <template  v-slot:default=\"scope\">\n    <div class=\"_CSS-popup\"> \n      <div class=\"_CSS-popup__header\">\n        <!-- <span class=\"_CSS-datestring\">{{controller.datesString}}</span> -->\n        <button class=\"ae-button ae-button\" @click.stop=\"scope.close()\">\n          <ae-icon class=\"fa fa-times\"></ae-icon>\n        </button> \n      </div>\n      \n      <div class=\"_CSS-popup__body\">\n        <div class=\"_CSS-types\" v-if=\"locals.mode === 'SELECTORS'\">\n          <button class=\"ae-button\" @click.stop=\"controller.setMode('today')\"> <ae-icon class=\"fa fa-calendar\"></ae-icon> Heute </button>\n          <button class=\"ae-button\" @click.stop=\"controller.setMode('tomorrow')\"> <ae-icon class=\"fa fa-calendar-plus\"></ae-icon> Morgen </button>\n          <button class=\"ae-button\" @click.stop=\"controller.setMode('next-monday')\"> <ae-icon class=\"fa fa-calendar-plus\"></ae-icon> Nächsten Montag </button>\n          <button class=\"ae-button\" @click.stop=\"controller.setMode('from-to')\"> <ae-icon class=\"fa fa-repeat\"></ae-icon> von - bis </button>\n          <!-- <button v-if=\"params.hasTime\" class=\"ae-button\" @click.stop=\"controller.setMode('datetime')\"> <ae-icon class=\"fa fa-clock\"></ae-icon> Datum & Uhrzeit </button> -->\n\n          <div class=\"_CSS-input-datetime\" title=\"Datum mit Uhrzeit\">\n            <button class=\"ae-button ae-button--small\" :class=\"{ 'ae-button--primary': document.data[params.hasTime] }\" @click.stop=\"document.data[params.hasTime] = !document.data[params.hasTime]\">\n              <ae-icon class=\"fa fa-clock\"></ae-icon>\n            </button>\n\n            <ae-control v-if=\"document.data[params.hasTime]\" name=\"datetime\" :document=\"document\" :field-name=\"fieldName\"></ae-control>\n            <ae-control v-else name=\"date\" :document=\"document\" :field-name=\"fieldName\"></ae-control>\n          </div>\n        </div>\n        <div class=\"_CSS-inputs\" v-else>\n          <template v-if=\"locals.mode === 'from-to'\">\n            <template v-if=\"params.hasTime\">\n              <label class=\"ae-form-cell__label\">\n                Mit Uhrzeit?\n              </label>\n              <ae-control name=\"switch\" :document=\"document\" :field-name=\"params.hasTime\"></ae-control>\n            </template>\n            <label class=\"ae-form-cell__label\">\n              von\n            </label>\n            <ae-control v-if=\"document.data[params.hasTime]\" name=\"datetime\" :document=\"document\" :field-name=\"fieldName\"></ae-control>\n            <ae-control v-else name=\"date\" :document=\"document\" :field-name=\"fieldName\"></ae-control>\n            \n            <label class=\"ae-form-cell__label\">\n              bis\n            </label> \n            <ae-control v-if=\"document.data[params.hasTime]\" name=\"datetime\" :document=\"document\" :field-name=\"params.to\" :param-min=\"document.data[fieldName].replace(/\\\\+.*/, '')\"></ae-control>\n            <ae-control v-else name=\"date\" :document=\"document\" :field-name=\"params.to\"  :param-min=\"document.data[fieldName].replace(/\\\\+.*/, '')\"></ae-control>\n          </template>\n          <!-- <template v-if=\"locals.mode === 'datetime'\">\n            <template v-if=\"params.hasTime\">\n              <label class=\"ae-form-cell__label\">\n                Mit Uhrzeit?\n              </label>\n              <ae-control name=\"switch\" :document=\"document\" :field-name=\"params.hasTime\"></ae-control>\n            </template>\n            <ae-control v-if=\"document.data[params.hasTime]\" name=\"datetime\" :document=\"document\" :field-name=\"fieldName\"></ae-control>\n            <ae-control v-else name=\"date\" :document=\"document\" :field-name=\"fieldName\"></ae-control>\n          </template> -->\n        </div>\n      </div>\n \n    </div>\n  </template>\n</ae-popup>\n","css":"._CSS-display {\n  display: flex;\n  align-items: center;\n}\n\n._CSS-datestring {\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n._CSS-display:hover {\n  cursor: pointer;\n}\n\n._CSS-types {\n  display: grid;\n}\n\n._CSS-popup {\n  min-width: 200px;\n  position: relative;\n}\n\n._CSS-popup__header {\n  position: absolute;\n  right: 0;\n  top: 0;\n\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n  padding-left: var(--ae-size-1);\n}\n\n._CSS-popup__body {\n  padding: var(--ae-size-2);\n} \n\n._CSS-input-datetime {\n  display: flex;\n}","controller":"class Controller {\n  init() {\n    this.modes = [\n      'SELECTORS',\n      'today',\n      'tomorrow',\n      'next-monday',\n      'from-to',\n      'datetime',\n    ]\n\n    locals.mode = 'SELECTORS'\n\n    if (!params.to) {\n      throw new Error('parameter \"to\" must be another fieldName')\n    }\n\n    // await Aeppic.nextRenderPass()\n    // $refs.popup.show({ target: $refs.popup.$el })\n  }\n\n  determineModes(hasTime) {\n    locals.hasTime = hasTime\n  }\n\n  unset() {\n    this.setFields()\n\n    $refs.popup && $refs.popup.close()\n  }\n\n  setMode(mode) {\n    const now = Aeppic.DateTime.local()\n\n    locals.mode = mode\n\n    if (mode === 'today') {\n      this.setFields(this.toDate(now))\n    } else if (mode === 'tomorrow') {\n      this.setFields(this.toDate(\n        now.plus({ days: 1 })\n      ))\n    } else if (mode === 'next-monday') {\n      this.setFields(this.toDate(\n        now.plus({ weeks: 1 }).startOf('week')\n      ))\n    } else if (mode === 'from-to') {\n      return\n    } else if (mode === 'datetime') {\n      this.setFields(document.data[fieldName], '')\n      return\n    }\n\n    $refs.popup && $refs.popup.close()\n\n    locals.mode = 'SELECTORS'\n  }\n\n  toDate(date) {\n    return date.startOf('day').toFormat(\"yyyy-LL-dd'T'HH:mm:ssZZ\")\n  }\n\n  toDateTime(date) {\n    return date.toFormat(\"yyyy-LL-dd'T'HH:mm:ssZZ\")\n  }\n\n  toTimePartOnly(date) {\n    return date.toFormat(\"'T'HH:mm:ssZZZ\")\n  }\n\n  toDatePartOnly(date) {\n    return date.toFormat(\"yyyy-LL-dd'\")\n  }\n\n  setFields(from, to, datetime) {\n    document.data[fieldName] = from || ''\n    document.data[params.to] = to || ''\n    document.data[params.hasTime] = datetime || false\n  }\n\n  get datesString() {\n    const hasTime = document.data[params.hasTime]\n    const FORMAT = hasTime ? Aeppic.DateTime.DATETIME_SHORT : Aeppic.DateTime.DATE_SHORT\n\n    const startField = document.data[fieldName]\n    const endField = document.data[params.to]\n\n    if (startField && endField) {\n      const start = Aeppic.DateTime.fromISO(startField).toLocaleString(FORMAT)\n      const end = Aeppic.DateTime.fromISO(endField).toLocaleString(FORMAT)\n\n      return `${start} - ${end}`\n    }\n\n    if (startField) {\n      return Aeppic.DateTime.fromISO(startField).toLocaleString(FORMAT)\n    }\n\n    return ''\n  }\n}\n ","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-09-18T13:40:19+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-02-10T16:15:24+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936833,"ct":1721209936833,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"c9c4afeda66d7c8e","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"51e176d8-78dd-4ed5-9032-0274c1ae8d55","v":"718ab5376c2605cf","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"design","template":"<ae-design :name=\"params.name\" :document=\"document\" :params=\"params\" :style=\"{ 'min-height': params.minHeight }\"></ae-design>","css":"","controller":"","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-03-01T10:59:50+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2021-03-01T11:02:20+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936833,"ct":1721209936833,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"1c9b25011903e48a","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"a07095e2-4c2d-4123-8b40-1c2acabeeee7","v":"initial","p":"66524002-b61d-4297-9ee8-6f72d2370323","f":{"id":"control-form","v":"411a8f4168bc63d0","text":"Control"},"data":{"namespace":"products","name":"device-assignment","template":"Assign","css":"","controller":"","cardinalityLimitation":"SINGLE","standalone":"FIELD","types":[],"packages":[],"supportsMobile":false,"label":"","description":""},"locks":[],"created":{"at":"2021-11-02T09:38:31+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2021-11-02T09:38:31+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1635845911810,"ct":1635845911793,"a_depth":3,"a":["apps","781ff5de-69db-49dc-99f0-f328b308cd7d","66524002-b61d-4297-9ee8-6f72d2370323"],"a_forms":["folder","application-form","folder"],"inheritedLocks":[null,null,null,null],"stamps":[]},{"id":"8729af6c-2285-4f67-8702-bdf628fc3690","v":"9cd9cacb28a516d4","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"vitegra","name":"device-components","template":"<ae-design v-if=\"locals.components\" v-for=\"c of locals.components\" :document=\"c\" name=\"overview-line,line\"></ae-design>","css":"","controller":"init() {\r\n  locals.components = Aeppic.subscribe(`f.id:cad45fd2-d641-4ff8-b38c-342e53533a23 p:b036a13f-8d59-42e2-92b2-a4ea1dab2d53 data.device.id:${document.id}`, { size:1, sort: { field: 'created.at', descending: true } })\r\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-11-02T09:44:07+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2021-11-02T09:44:07+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936834,"ct":1721209936834,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"5d652b5bc53cb1ed","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"43a9c53b-d7b0-468e-a511-66b7bb672400","v":"3e2878095b2031d9","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"vitegra","name":"device-tests","template":"<template  v-for=\"test of locals.tests\">\r\n  <ae-design name=\"overview-line,card,line\" :document=\"test\"></ae-design>   \r\n</template>","css":" ","controller":"init() {\r\n  locals.tests = Aeppic.subscribe(`p:df765fcb-a4ed-48a5-87a7-a4873b97597a data.device.id:${document.id}`)\r\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-11-02T09:44:09+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2021-11-02T09:44:09+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936835,"ct":1721209936835,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"031c5bb7b81629c8","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"612720d1-1b33-4f36-9637-94eecd968476","v":"1953697ea78a9c62","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"duration","template":"<div  class=\"ae-form-cell__input\"\n      :class=\"{ 'ae-form-cell--readonly' : readonly }\"\n      ref=\"input\"\n      @click.stop=\"controller.show($event)\"\n>\n  {{ locals.readable }}\n</div>\n\n<ae-popup ref=\"popup\" placement=\"bottom-start\" @close=\"locals.customMode = false\">\n  <template v-slot=\"scope\">\n    <div class=\"_CSS-popup\">\n      <section class=\"_CSS-popup__header\">\n        <button style=\"flex: 1;\" class=\"ae-button ae-button--small\" @click.stop=\"locals.customMode = !locals.customMode\">\n          <ae-icon v-if=\"locals.customMode\" class=\"fa fa-toggle-on\"></ae-icon>\n          <ae-icon v-else class=\"fa fa-toggle-off\"></ae-icon>\n          Benutzerdefiniert\n        </button>\n\n        <button class=\"ae-button\" @click.stop=\"scope.close()\">\n          <ae-icon class=\"fa fa-times\"></ae-icon>\n        </button>\n      </section>\n\n\n      <section v-if=\"controller.format === locals.FORMAT_DAY_HOUR || controller.format === locals.FORMAT_DAY\">\n        <div class=\"ae-form-cell__label\">\n          Tage\n        </div>\n        <div class=\"_CSS-time-buttons\" v-if=\"!locals.customMode\">\n          <button class=\"ae-button\" :class=\"{ 'ae-button--secondary': field.days === buttonValue }\" v-for=\"buttonValue of locals.dayButtonValues\" @click.stop=\"controller.patchField({ days: buttonValue })\">\n            {{ buttonValue }}d\n          </button>\n        </div>\n        <div v-else>\n          <input type=\"number\" :value=\"field.days\" @input=\"controller.patchField({ days: $event.target.value })\">\n        </div>\n      </section>\n\n      <section v-if=\"controller.format === locals.FORMAT_HOUR_MINUTE_SECOND || controller.format === locals.FORMAT_HOUR_MINUTE || controller.format === locals.FORMAT_DAY_HOUR\">\n        <div class=\"ae-form-cell__label\">\n          Stunden\n        </div>\n        <div class=\"_CSS-time-buttons\"  v-if=\"!locals.customMode\">\n          <button class=\"ae-button\" :class=\"{ 'ae-button--secondary': field.hours === buttonValue }\" v-for=\"buttonValue of locals.hourButtonValues\" @click.stop=\"controller.patchField({ hours: buttonValue })\">\n            {{ buttonValue }}h\n          </button>\n        </div>\n        <div v-else>\n          <input type=\"number\" :value=\"field.hours\" @input=\"controller.patchField({ hours: $event.target.value })\">\n        </div>          \n      </section>\n\n      <section v-if=\"controller.format === locals.FORMAT_HOUR_MINUTE_SECOND || controller.format === locals.FORMAT_HOUR_MINUTE\">\n        <div class=\"ae-form-cell__label\">\n          Minuten\n        </div>\n        <div class=\"_CSS-time-buttons\"  v-if=\"!locals.customMode\">\n          <button class=\"ae-button\" :class=\"{ 'ae-button--secondary': field.minutes === buttonValue }\" v-for=\"buttonValue of locals.minuteButtonValues\" @click.stop=\"controller.patchField({ minutes: buttonValue })\">\n            {{ buttonValue }}m\n          </button>\n        </div>\n        <div v-else>\n          <input type=\"number\" :value=\"field.minutes\" @input=\"controller.patchField({ minutes: $event.target.value })\">\n        </div>          \n      </section>\n      <section v-if=\"controller.format === locals.FORMAT_HOUR_MINUTE_SECOND\">\n        <div class=\"ae-form-cell__label\">\n          Sekunden\n        </div>\n        <div class=\"_CSS-time-buttons\"  v-if=\"!locals.customMode\">\n          <button class=\"ae-button\" :class=\"{ 'ae-button--secondary': field.seconds === buttonValue }\" v-for=\"buttonValue of locals.secondButtonValues\" @click.stop=\"controller.patchField({ seconds: buttonValue })\">\n            {{ buttonValue }}s\n          </button>\n        </div>\n        <div v-else>\n          <input type=\"number\" :value=\"field.seconds\" @input=\"controller.patchField({ seconds: $event.target.value })\">\n        </div>          \n      </section>\n    </div>\n  </template>\n</ae-popup>\n","css":"._CSS-popup {\n  padding: var(--ae-size-2);\n  background-color: white;\n  box-shadow: var(--ae-shadow-2);\n  transition: opacity .15s, visibility 1s;\n  border-radius: var(--ae-border-radius);\n}\n\n._CSS-time-buttons {\n  display: grid;\n  grid-template-columns: repeat(6, 1fr);\n}\n\n._CSS-popup__header {\n  display: flex;\n  justify-content: space-between;\n  margin-bottom: var(--ae-size-2);\n}","controller":"class Controller {\n  init() {\n    this.FORMAT_DAY = 'dd'\n    this.FORMAT_DAY_HOUR = 'dd:hh'\n    this.FORMAT_HOUR_MINUTE = 'hh:mm'\n    this.FORMAT_HOUR_MINUTE_SECOND = 'hh:mm:ss'\n    this.FORMATS = [this.FORMAT_HOUR_MINUTE, this.FORMAT_HOUR_MINUTE_SECOND, this.FORMAT_DAY_HOUR, this.FORMAT_DAY]\n\n    locals.customMode = false\n    locals.setOfSelectedUnits = new Set()\n\n    locals.FORMAT_HOUR_MINUTE_SECOND = this.FORMAT_HOUR_MINUTE_SECOND\n    locals.FORMAT_HOUR_MINUTE = this.FORMAT_HOUR_MINUTE\n    locals.FORMAT_DAY_HOUR = this.FORMAT_DAY_HOUR\n    locals.FORMAT_DAY = this.FORMAT_DAY\n\n    locals.dayButtonValues = [0,1,2,3,4,5,6,7,14,21,30,31]\n    locals.hourButtonValues = [0,1,2,3,4,5,6,7,8,9,10,11]\n    locals.minuteButtonValues = [0,5,10,15,20,25,30,35,40,45,50,55]\n    locals.secondButtonValues = [0,10,20,30,40,50]\n\n    watchParam('format', () => {\n      if (params.format && this.FORMATS.includes(params.format) === false) {\n        console.error(`[${control.data.name}]: Ignoring invalid or unsupported format`)\n      }\n    })\n\n    if (params.format && this.FORMATS.includes(params.format) === false) {\n      console.error(`[${control.data.name}]: Ignoring invalid or unsupported format`)\n    }\n\n    this.updateDerivedValues() \n\n    Aeppic.watchRevisions(document, () => this.updateDerivedValues())\n  }\n\n  show() {\n    locals.setOfSelectedUnits = new Set()\n\n    if (readonly) {\n      return\n    }\n\n    $refs.popup && $refs.popup.show({ target: $refs.input })\n  }\n\n  getFieldAsReadable() {\n    const field = getField()\n\n    let duration = null\n\n    if (params.displayRaw) {\n      duration =  Aeppic.DateTimeDuration.fromObject(field)\n    } else {\n      // TODO: refactor code dulication\n      duration =  Aeppic.DateTimeDuration.fromObject(field)\n    \n      if (this.format === this.FORMAT_HOUR_MINUTE) {\n        duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('hours', 'minutes')\n      } else if (this.format === this.FORMAT_HOUR_MINUTE_SECOND) {\n        duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('hours', 'minutes', 'seconds')\n      } else if (this.format === this.FORMAT_DAY_HOUR) {\n        duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('days', 'hours')\n      } else if (this.format === this.FORMAT_DAY) {\n        duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('days')\n      } else {\n        throw new Error('unsupported format')\n      }\n\n      duration = duration.normalize()\n    } \n\n    console.log(duration)\n    let targetFormattingParts = []\n\n    // if (this.format === this.FORMAT_DAY_HOUR) { \n      if (duration.days !== 0) {\n        targetFormattingParts.push(\"d'd'\")\n      }\n    // }\n\n    // if (this.format === this.FORMAT_HOUR_MINUTE || this.format === this.FORMAT_DAY_HOUR) {\n      if (duration.hours !== 0) {\n        targetFormattingParts.push(\"h'h'\")\n      }\n      if (duration.minutes !== 0) {\n        targetFormattingParts.push(\"m'm'\")\n      }\n    // }\n\n    // if (this.format === this.FORMAT_HOUR_MINUTE_SECOND) {\n      if (duration.seconds !== 0) {\n        targetFormattingParts.push(\"s's'\")\n      }\n    // }\n\n    return duration.toFormat(targetFormattingParts.join(' '))\n  }\n\n  getFieldWithColons() {\n    const field = getField()\n\n    return Aeppic.DateTimeDuration.fromObject(field).toFormat(this.format)\n  }\n\n  get format() {\n    return params.format || this.FORMAT_HOUR_MINUTE \n  }\n\n  setField(value) {\n    const durationObj = this.parseInput(value)\n    console.log('setField', durationObj)\n\n    setField(durationObj)\n\n    locals.colonValue = value\n    locals.readable = this.getFieldAsReadable()\n  }\n\n  patchField(value) {\n    console.log('patchField', value)\n    if (!value) {\n      return\n    }\n\n    const units = Object.keys(value)\n    units.forEach(unit => locals.setOfSelectedUnits.add(unit))\n\n    let allValuesSelected = false\n\n    const updateAllValuesSelected = (expectedSize) => {\n      allValuesSelected = locals.setOfSelectedUnits.size === expectedSize\n    } \n\n    const field = getField()\n    let duration = null\n\n    if (this.format === this.FORMAT_HOUR_MINUTE) {\n      duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('hours', 'minutes')\n      // updateAllValuesSelected(2)\n    } else if (this.format === this.FORMAT_HOUR_MINUTE_SECOND) {\n      duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('hours', 'minutes', 'seconds')\n      // updateAllValuesSelected(3)\n    } else if (this.format === this.FORMAT_DAY_HOUR) {\n      duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('days', 'hours')\n      // updateAllValuesSelected(2)\n    } else if (this.format === this.FORMAT_DAY) {\n      duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('days')\n      // updateAllValuesSelected(1)\n    } else {\n      throw new Error('unsupported format')\n    }\n\n    const newDuration = duration.set(value).normalize()\n\n    setField(newDuration.toObject())\n\n    this.updateDerivedValues() \n\n    if (allValuesSelected) {\n      $refs.popup.close()\n    }\n  }\n\n  updateDerivedValues() {\n    locals.readable = this.getFieldAsReadable()\n    locals.colonValue = this.getFieldWithColons()\n  }\n\n  parseInput(value) {\n    const parts = value.split(':').map((s) => {\n      const maybeNumber = parseInt(s)\n\n      if (Number.isNaN(maybeNumber)) {\n        return null\n      }\n\n      return maybeNumber\n    })\n\n    if (this.format === this.FORMAT_DAY_HOUR) {\n      if (parts[0]) {\n        if (parts[1]) {\n          return { days: parts[0], hours: parts[1] }\n        }\n\n        return { days: parts[0] }\n      }\n\n      return {}\n    }\n\n    if (parts.length === 1) {\n      return { hours: parts[0] }\n    }\n\n    if (parts.length === 2) {\n      return { hours: parts[0], minutes: parts[1] }\n    }\n\n    return {}\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-09-13T09:18:52+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2022-07-22T11:43:50+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936835,"ct":1721209936835,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"6bc78bf16fb7eb9b","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"78a0a717-71a5-4be0-b700-682003d2a6ac","v":"d0eb653f6589af58","p":"91f205bc-9989-4fbe-8966-bd4ba9777c26","f":{"id":"control-form","v":"8f342d57244fe7b5","text":"Control"},"data":{"namespace":"","name":"duration","template":"<label>\n  Hours\n  <input type=\"number\" min=\"0\" max=\"240\">\n</label>\n<label>Minutes\n  <input type=\"number\" min=\"0\" max=\"59\">\n</label>\n<label>Seconds\n  <input type=\"number\" min=\"0\" max=\"59\">\n</label>","css":"._CSS {\n  display: flex;\n  flex-direction: row;\n}","controller":"","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-07-08T13:13:09+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-07-08T13:18:09+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721144004091,"ct":1721144004091,"a_depth":3,"a":["system","controls-folder","91f205bc-9989-4fbe-8966-bd4ba9777c26"],"a_forms":["system-folder-form","repository-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null,null],"stamps":[],"previous":{"v":"1bc568c140a6a563","f":{"id":"control-form","v":"7672ea0b1b6d4c70","text":"Control"}},"meta":{"modified":{"at":"2024-07-16T17:33:24+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2023-09-18T13:25:13+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936835,"data":{"cardinality":"BOTH","controller":"class Controller {\n  init() {\n    this.FORMAT_DAY = 'dd'\n    this.FORMAT_DAY_HOUR = 'dd:hh'\n    this.FORMAT_HOUR_MINUTE = 'hh:mm'\n    this.FORMAT_HOUR_MINUTE_SECOND = 'hh:mm:ss'\n    this.FORMAT_DAY_HOUR_MINUTE = 'dd:hh:mm'\n    this.FORMATS = [this.FORMAT_HOUR_MINUTE, this.FORMAT_HOUR_MINUTE_SECOND, this.FORMAT_DAY_HOUR, this.FORMAT_DAY, this.FORMAT_DAY_HOUR_MINUTE]\n\n    locals.customMode = false\n    locals.setOfSelectedUnits = new Set()\n\n    locals.FORMAT_DAY_HOUR_MINUTE = this.FORMAT_DAY_HOUR_MINUTE\n    locals.FORMAT_HOUR_MINUTE_SECOND = this.FORMAT_HOUR_MINUTE_SECOND\n    locals.FORMAT_HOUR_MINUTE = this.FORMAT_HOUR_MINUTE\n    locals.FORMAT_DAY_HOUR = this.FORMAT_DAY_HOUR\n    locals.FORMAT_DAY = this.FORMAT_DAY\n\n    locals.dayButtonValues = [0,1,2,3,4,5,6,7,14,21,30,31]\n    locals.hourButtonValues = [0,1,2,3,4,5,6,7,8,9,10,11]\n    locals.minuteButtonValues = [0,5,10,15,20,25,30,35,40,45,50,55]\n    locals.secondButtonValues = [0,10,20,30,40,50]\n\n\n    this.lastDates = {\n      started: null,\n      stopped: null,\n    }\n\n    watchParam('format', () => {\n      if (params.format && this.FORMATS.includes(params.format) === false) {\n        console.error(`[${control.data.name}]: Ignoring invalid or unsupported format`)\n      }\n    })\n\n    if (params.format && this.FORMATS.includes(params.format) === false) {\n      console.error(`[${control.data.name}]: Ignoring invalid or unsupported format`)\n    }\n\n    this.updateDerivedValues() \n\n    Aeppic.watchRevisions(document.id, (changedDocument) => {\n      console.log('watchRevisions', changedDocument)\n      this.updateDuration(changedDocument)\n      this.updateDerivedValues()\n    })\n  }\n\n\n  updateDuration(changedDocument) {\n    if (!params.startedField || !params.stoppedField || (changedDocument.data[params.startedField] === this.lastDates.started \n                                                          && changedDocument.data[params.stoppedField] === this.lastDates.stopped)) {\n      return \n    }\n\n    this.lastDates.started = changedDocument.data[params.startedField]\n    this.lastDates.stopped = changedDocument.data[params.stoppedField]\n\n    const duration = Aeppic.DateTime.fromISO(changedDocument.data[params.stoppedField]).diff(Aeppic.DateTime.fromISO(changedDocument.data[params.startedField]))\n    const shiftedDuration = duration.shiftTo('hours', 'minutes', 'seconds').toObject() || {}\n\n    document.data[fieldName] = shiftedDuration\n  }\n\n  show() {\n    locals.setOfSelectedUnits = new Set()\n\n    if (readonly) {\n      return\n    }\n\n    $refs.popup && $refs.popup.show({ target: $refs.input })\n  }\n\n  getFieldAsReadable() {\n    const field = getField()\n\n    let duration = null\n\n    if (params.displayRaw) {\n      duration =  Aeppic.DateTimeDuration.fromObject(field)\n    } else {\n      // TODO: refactor code dulication\n      duration =  Aeppic.DateTimeDuration.fromObject(field)\n\n      if (this.format === this.FORMAT_DAY_HOUR_MINUTE) {\n        duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('days', 'hours', 'minutes')\n      } else if (this.format === this.FORMAT_HOUR_MINUTE) {\n        duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('hours', 'minutes')\n      } else if (this.format === this.FORMAT_HOUR_MINUTE_SECOND) {\n        duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('hours', 'minutes', 'seconds')\n      } else if (this.format === this.FORMAT_DAY_HOUR) {\n        duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('days', 'hours')\n      } else if (this.format === this.FORMAT_DAY) {\n        duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('days')\n      } else {\n        throw new Error('unsupported format')\n      }\n\n      duration = duration.normalize()\n    } \n\n    console.log(duration.toObject())\n\n    let targetFormattingParts = []\n\n    // if (this.format === this.FORMAT_DAY_HOUR) { \n      if (duration.days !== 0) {\n        targetFormattingParts.push(\"d'd'\")\n      }\n    // }\n\n    // if (this.format === this.FORMAT_HOUR_MINUTE || this.format === this.FORMAT_DAY_HOUR) {\n      if (duration.hours !== 0) {\n        targetFormattingParts.push(\"h'h'\")\n      }\n      if (duration.minutes !== 0) {\n        targetFormattingParts.push(\"m'm'\")\n      }\n    // }\n\n    // if (this.format === this.FORMAT_HOUR_MINUTE_SECOND) {\n      if (duration.seconds !== 0) {\n        targetFormattingParts.push(\"s's'\")\n      }\n    // }\n\n    return duration.toFormat(targetFormattingParts.join(' '))\n  }\n\n  getFieldWithColons() {\n    const field = getField()\n\n    return Aeppic.DateTimeDuration.fromObject(field).toFormat(this.format)\n  }\n\n  get format() {\n    return params.format || this.FORMAT_HOUR_MINUTE \n  }\n\n  setField(value) {\n    const durationObj = this.parseInput(value)\n    console.log('setField', durationObj)\n\n    setField(durationObj)\n\n    locals.colonValue = value\n    locals.readable = this.getFieldAsReadable()\n  }\n\n  patchField(value) {\n    console.log('patchField', value)\n    if (!value) {\n      return\n    }\n\n    const units = Object.keys(value)\n    units.forEach(unit => locals.setOfSelectedUnits.add(unit))\n\n    let allValuesSelected = false\n\n    const updateAllValuesSelected = (expectedSize) => {\n      allValuesSelected = locals.setOfSelectedUnits.size === expectedSize\n    } \n\n    const field = getField()\n    let duration = null\n\n    if (this.format === this.FORMAT_DAY_HOUR_MINUTE) {\n      duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('days', 'hours', 'minutes')\n    } else if (this.format === this.FORMAT_HOUR_MINUTE) {\n      duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('hours', 'minutes')\n      // updateAllValuesSelected(2)\n    } else if (this.format === this.FORMAT_HOUR_MINUTE_SECOND) {\n      duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('hours', 'minutes', 'seconds')\n      // updateAllValuesSelected(3)\n    } else if (this.format === this.FORMAT_DAY_HOUR) {\n      duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('days', 'hours')\n      // updateAllValuesSelected(2)\n    } else if (this.format === this.FORMAT_DAY) {\n      duration = Aeppic.DateTimeDuration.fromObject(field).shiftTo('days')\n      // updateAllValuesSelected(1)\n    } else {\n      throw new Error('unsupported format')\n    }\n\n    const newDuration = duration.set(value).normalize()\n\n    setField(newDuration.toObject())\n\n    this.updateDerivedValues() \n\n    if (allValuesSelected) {\n      $refs.popup.close()\n    }\n  }\n\n  updateDerivedValues() {\n    locals.readable = this.getFieldAsReadable()\n    locals.colonValue = this.getFieldWithColons()\n  }\n\n  parseInput(value) {\n    const parts = value.split(':').map((s) => {\n      const maybeNumber = parseInt(s)\n\n      if (Number.isNaN(maybeNumber)) {\n        return null\n      }\n\n      return maybeNumber\n    })\n\n    if (this.format === this.FORMAT_DAY_HOUR) {\n      if (parts[0]) {\n        if (parts[1]) {\n          return { days: parts[0], hours: parts[1] }\n        }\n\n        return { days: parts[0] }\n      }\n\n      return {}\n    }\n\n    if (parts.length === 1) {\n      return { hours: parts[0] }\n    }\n\n    if (parts.length === 2) {\n      return { hours: parts[0], minutes: parts[1] }\n    }\n\n    return {}\n  }\n}","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"duration","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<div  class=\"w-full border rounded-md py-1 px-2 flex gap-1 bg-white dark:bg-slate-800 min-h-[2em]\"\n      :class=\"{ 'bg-neutral-100' : readonly, 'border-red-500': validationErrors?.required }\"\n      ref=\"input\"\n      @click.stop=\"controller.show($event)\"\n      :data-test=\"fieldName\"\n>\n  {{ locals.readable }}\n</div>\n\n<ae-popup ref=\"popup\" placement=\"bottom-start\" @close=\"locals.customMode = false\">\n  <template v-slot=\"scope\">\n    <div class=\"flex flex-col gap-2 p-2\">\n      <section class=\"flex justify-between\">\n        <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n                   @selected=\"locals.customMode = !locals.customMode\"\n                   :param-icon=\"locals.customMode ? 'far fa-toggle-on' : 'far fa-toggle-off'\"\n                   param-text=\"Benutzerdefiniert\"\n        ></ae-layout>\n\n        <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n                   @selected=\"scope.close()\"\n                   param-icon=\"far fa-times\"\n        ></ae-layout>\n      </section>\n\n      <section v-if=\"controller.format === locals.FORMAT_DAY_HOUR || controller.format === locals.FORMAT_DAY || controller.format === locals.FORMAT_DAY_HOUR_MINUTE\">\n        <div class=\"text-neutral-500\">\n          Tage\n        </div>\n        <div class=\"grid grid-cols-6 gap-1\" v-if=\"!locals.customMode\">\n          <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" :param-primary=\"field.days === buttonValue\" v-for=\"buttonValue of locals.dayButtonValues\" @selected=\"controller.patchField({ days: buttonValue })\" :param-text=\"buttonValue + 'd'\">\n          </ae-layout>\n        </div>\n        <div v-else>\n          <input class=\"border p-1 rounded-md\"  type=\"number\" :value=\"field.days\" @input=\"controller.patchField({ days: $event.target.value })\">\n        </div>\n      </section>\n\n      <section v-if=\"controller.format === locals.FORMAT_HOUR_MINUTE_SECOND || controller.format === locals.FORMAT_HOUR_MINUTE || controller.format === locals.FORMAT_DAY_HOUR || controller.format === locals.FORMAT_DAY_HOUR_MINUTE\">\n        <div class=\"text-neutral-500\">\n          Stunden\n        </div>\n        <div class=\"grid grid-cols-6 gap-1\"  v-if=\"!locals.customMode\">\n          <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" :param-primary=\"field.hours === buttonValue\" v-for=\"buttonValue of locals.hourButtonValues\" @selected=\"controller.patchField({ hours: buttonValue })\" :param-text=\"buttonValue + 'h'\">\n          </ae-layout>\n        </div>\n        <div v-else>\n          <input class=\"border p-1 rounded-md\" type=\"number\" :value=\"field.hours\" @input=\"controller.patchField({ hours: $event.target.value })\">\n        </div>          \n      </section>\n\n      <section v-if=\"controller.format === locals.FORMAT_HOUR_MINUTE_SECOND || controller.format === locals.FORMAT_HOUR_MINUTE || controller.format === locals.FORMAT_DAY_HOUR_MINUTE\">\n        <div class=\"text-neutral-500\">\n          Minuten\n        </div>\n        <div class=\"grid grid-cols-6 gap-1\"  v-if=\"!locals.customMode\">\n          <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" :param-primary=\"field.minutes === buttonValue\" v-for=\"buttonValue of locals.minuteButtonValues\" @selected=\"controller.patchField({ minutes: buttonValue })\" :param-text=\"buttonValue + 'm'\">\n          </ae-layout>\n        </div>\n        <div v-else>\n          <input class=\"border p-1 rounded-md\" type=\"number\" :value=\"field.minutes\" @input=\"controller.patchField({ minutes: $event.target.value })\">\n        </div>          \n      </section>\n      <section v-if=\"controller.format === locals.FORMAT_HOUR_MINUTE_SECOND\">\n        <div class=\"text-neutral-500\">\n          Sekunden\n        </div>\n        <div class=\"grid grid-cols-6 gap-1\"  v-if=\"!locals.customMode\">\n          <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" :param-primary=\"field.seconds === buttonValue\" v-for=\"buttonValue of locals.secondButtonValues\" @selected=\"controller.patchField({ seconds: buttonValue })\" :param-text=\"buttonValue + 's'\">\n          </ae-layout>\n        </div>\n        <div v-else>\n          <input class=\"border p-1 rounded-md\" type=\"number\" :value=\"field.seconds\" @input=\"controller.patchField({ seconds: $event.target.value })\">\n        </div>          \n      </section>\n    </div>\n  </template>\n</ae-popup>\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"b8286553-d1a2-427f-aa53-211e3a5c7bb4","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-03-10T14:43:37+00:00","backedBy":"","by":"729fa027-7e7e-4b06-9602-f535c43824f0","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"7476fcdf456ab77b"},"stamps":[],"t":1773153817010,"v":"d51420cd27502bb1"},{"id":"5bde2ce9-c8a0-42d3-b418-0fcb2316a554","v":"953584b6dffee29f","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"duration-sums","template":"<!-- ref=\"input\" -->\n<!-- @click.stop=\"!readonly && $refs.popup && $refs.popup.show({ target: $refs.input })\" -->\n<div  class=\"ae-form-cell__input _CSS-container\"\n      :class=\"{ 'ae-form-cell--readonly' : readonly }\"\n>\n  <!-- {{ locals.readable }} -->\n  <div v-if=\"Object.keys(locals.sum).length\">\n    <div class=\"_CSS-heading\"> Gesamt </div>\n    <div>\n      {{ controller.toReadable(locals.sum) }}  \n    </div>\n  </div>\n  <div v-if=\"Object.keys(locals.month).length\">\n    <div class=\"_CSS-heading\"> nach Monaten </div>\n    <div class=\"_CSS-times\" v-for=\"(duration, month) in locals.month\">\n      <span>{{ Aeppic.DateTime.fromISO(month).toFormat('LLLL yyyy') }}</span>\n      <span class=\"_CSS-box--align-right\">{{ controller.toReadable(duration) }}</span>\n    </div>\n  </div>\n  <div v-if=\"Object.keys(locals.year).length\">\n    <div class=\"_CSS-heading\"> nach Jahren </div>\n    <div class=\"_CSS-times\" v-for=\"(duration, year) in locals.year\">\n      <span>{{ Aeppic.DateTime.fromISO(year).toFormat('yyyy') }}</span>\n      <span class=\"_CSS-box--align-right\">{{ controller.toReadable(duration) }}</span>\n    </div>\n  </div>\n</div>\n","css":"._CSS-container {\n  display: grid;\n  grid-template-columns: 1fr 2.5fr 2.5fr;\n  grid-gap: 50px;\n}\n\n._CSS-heading {\n  xfont-weight: bold;\n  color: var(--ae-background-600);\n  margin-bottom: var(--ae-space-2);\n}\n\n._CSS-times {\n  max-width: 250px;\n  display: grid;\n  grid-template-columns: auto auto;\n}\n\n._CSS-box--align-right {\n  text-align: right;\n}\n\n","controller":"class Controller {\n  init() {\n    if (params.sum) {\n      locals.sum = document.data[params.sum]\n    }\n    if (params.month) {\n      locals.month = JSON.parse(document.data[params.month])\n    }\n    if (params.year) {\n      locals.year = JSON.parse(document.data[params.year])\n    }\n  }\n\n  toReadable(durationObject) {\n    const duration = Aeppic.DateTimeDuration.fromObject(durationObject)\n    let targetFormattingParts = []\n\n    if (duration.days !== 0) {\n      targetFormattingParts.push(\"d'd'\")\n    }\n\n    if (duration.hours !== 0) {\n      targetFormattingParts.push(\"h'h'\")\n    }\n\n    // NOTE: always show minutes\n    // if (duration.minutes !== 0) {\n      targetFormattingParts.push(\"mm'm'\")\n    // }\n\n    if (duration.seconds !== 0) {\n      targetFormattingParts.push(\"s's'\")\n    }\n\n    return duration.toFormat(targetFormattingParts.join(' '))\n  }\n\n  get format() {\n    return params.format || this.FORMAT_HOUR_MINUTE \n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-06-23T14:06:30+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2021-06-23T14:06:30+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936835,"ct":1721209936835,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"47e0cf3f80e1cabe","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-11-28T10:30:31+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936835,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    this.FORMS = {\n      CoreDatei: 'dbee6e16-d63a-493c-bba6-505f79918f9f',\n      CoreOrdner: 'a04c8913-db48-4e80-b20d-0f3cc17a778c',\n      CoreAufgabe: '0deb4864-cbb6-420c-99ee-337c7444ab24',\n      Kontakt: '6f641574-7cb6-424a-9657-4b8d652a2cf7',\n      Protokoll: '8dbe21a7-7642-4884-986b-a119c6498bd3',\n      KnowledgeSeite: 'd6f1cc99-f34e-4cb8-99b3-11f917485c25',\n      KnowledgeOrdner: '24523534-125d-4ebe-9a83-bd7fed39ff92',\n      Vertrag: '3d45f20b-ecb3-4dc5-aa39-67a28e3923d8',\n      Angebot: 'ea3fb00c-4b3e-44ed-8bfb-7dce7f8a8f4c',\n      Rechnung: 'a40633b4-c769-4457-a267-830738d4b549',\n    }\n\n    // this.EMPTY_EDITOR_CONTENT_JSONSTRINGIFIED = '{\"type\":\"doc\",\"content\":[{\"type\":\"paragraph\"}]}'\n    this.EMPTY_EDITOR_CONTENT_JSONSTRINGIFIED = '{\"type\":\"doc\",\"content\":[{\"type\":\"paragraph\"}],\"tiptapVersion\":\"2.x\"}'\n\n    this.pendingUpdateField = 0\n\n    locals.lastKnownEditorContentAsString = ''\n\n    locals.menuActive = false\n    locals.mouseover = false\n\n    const defaultCommands = [\n      'bold',\n      'italic',\n      'strikethrough',\n      'underline',\n      'highlight',\n      'link',\n      'list-ul',\n      'list-ol',\n      'tasks',\n      'horizontal-rule',\n      'quote',\n      'undo',\n      'redo',\n      'h1',\n      'h2',\n      'h3',\n      'code',\n      'smile',\n      'ae-ref',\n      'ae-ref-employee',\n      'horizontal-rule',\n      'table',\n      'attachment',\n    ]\n\n    const commandGroupings = {\n      formatting: ['bold', 'italic', 'strikethrough', 'underline', 'highlight', 'code', 'quote', 'smile'],\n      heading: ['h1', 'h2', 'h3'],\n      structure: ['horizontal-rule'],\n      list: ['list-ul', 'list-ol', 'tasks'],\n      link: ['link'],\n      referenceEmployee: ['ae-ref-employee'],\n      reference: ['ae-ref', 'ae-ref-employee'],\n      table: ['table'],\n      embed: ['attachment'],\n      history: ['undo', 'redo'],\n    }\n\n    const commands = []\n\n    let commandsAreExternal = false\n\n    if (params.commands || params.editorControls) {\n      commandsAreExternal = true\n      const param = params.commands ?? params.editorControls\n\n      if (typeof param === 'string') {\n        commands.push(...param.split(','))\n      } else {\n        commands.push(...param)\n      }\n    } \n    if (params.editorControlGroups) {\n      commandsAreExternal = true\n\n      const selectedGroups = []\n\n      if (typeof params.editorControlGroups === 'string') {\n        selectedGroups.push(...params.editorControlGroups.split(',').filter(Boolean))\n      } else {\n        selectedGroups.push(...params.editorControlGroups)\n      }\n\n      selectedGroups.forEach(group => {\n        const c = commandGroupings[group]\n\n        if (!c.length) {\n          console.warn(`Invalid command group provided: ${group}`)\n        }\n\n        commands.push(...c)\n      })\n    }\n    \n    if (commandsAreExternal === false) {\n      commands.push(...defaultCommands)\n    }\n\n    locals.commands = commands\n\n    locals.commandsSet = new Set([...locals.commands])\n\n    locals.showTableMenus = false\n\n    locals.company = null\n\n    locals.key = Aeppic.uuid()\n\n    locals.refButtonSettings = {\n      filter: {\n        ancestorForm: 'c69a0c1c-6c53-40c2-87e8-5ace866bf1df',\n        targetDocument: document,\n        formIds: Object.values(this.FORMS).join(','),\n      },\n      search: {\n        sort: [\n          {\n            field: 'f.id',\n            descending: true,\n          },\n          {\n            field: 'data.name',\n          },\n          {\n            field: 'created.at',\n          },\n        ],\n      },\n    }\n\n    locals.refButtonEmployee = {\n      filter: {\n        ancestorForm: 'c69a0c1c-6c53-40c2-87e8-5ace866bf1df',\n        targetDocument: document,\n        formIds: 'd2ac3563-3c3f-4f02-b923-9b67abf98d10',\n      },\n      search: {\n        sort: [\n          {\n            field: 'data.nachname',\n          },\n          {\n            field: 'data.vorname',\n          },\n          {\n            field: 'id',\n          },\n        ],\n      },\n    }\n\n    locals.fallbackToQuillEditor = false\n\n    locals.editor = null\n    locals.newLink = ''\n    locals.insertChildTypes = []\n\n    locals.addOptions = [\n      (locals.commandsSet.has('ae-ref-employee') || locals.commandsSet.has('ae-ref')) && { heading: 'Verknüpfen' },\n      locals.commandsSet.has('ae-ref-employee') && { id: 'ref-employee', icon: 'far fa-at', display: 'Mitarbeiter' },\n      locals.commandsSet.has('ae-ref') && { id: 'ae-ref', icon: 'far fa-at', display: 'Anderes Document' },\n      locals.commandsSet.has('attachment') && { heading: 'Einfügen' },\n      locals.commandsSet.has('attachment') && { id: 'attachment', icon: 'far fa-paperclip', display: 'Datei' },\n      (locals.commandsSet.has('link') ||\n        locals.commandsSet.has('code') ||\n        locals.commandsSet.has('table') ||\n        locals.commandsSet.has('quote') ||\n        locals.commandsSet.has('list-ul') ||\n        locals.commandsSet.has('list-ol') ||\n        locals.commandsSet.has('horizontal-rule')) && { heading: 'Formatierung' },\n      locals.commandsSet.has('link') && { id: 'link', icon: 'far fa-link', display: 'Link' },\n      locals.commandsSet.has('code') && { id: 'CodeBlock', icon: 'far fa-code', display: 'Code Block' },\n      locals.commandsSet.has('table') && { id: 'table', icon: 'far fa-table', display: 'Tabelle' },\n      locals.commandsSet.has('quote') && { id: 'Blockquote', icon: 'far fa-quote-left', display: 'Zitat' },\n      locals.commandsSet.has('list-ul') && { id: 'BulletList', icon: 'far fa-list-ul', display: 'Aufzählung' },\n      locals.commandsSet.has('list-ol') && { id: 'OrderedList', icon: 'far fa-list-ol', display: 'Nummerierte Liste' },\n      locals.commandsSet.has('horizontal-rule') && {\n        id: 'horizontal-rule',\n        icon: 'far fa-horizontal-rule',\n        display: 'Trennstrich',\n      },\n      (locals.commandsSet.has('h1') || locals.commandsSet.has('h2') || locals.commandsSet.has('h3')) && {\n        heading: 'Überschriften',\n      },\n      locals.commandsSet.has('h1') && { id: 'h1', icon: 'far fa-h1', display: 'Überschrift 1' },\n      locals.commandsSet.has('h2') && { id: 'h2', icon: 'far fa-h2', display: 'Überschrift 2' },\n      locals.commandsSet.has('h3') && { id: 'h3', icon: 'far fa-h3', display: 'Überschrift 3' },\n    ].filter((o) => !!o)\n\n    locals.tableOptions = [\n      { id: 'addColumnBefore', display: 'Spalte links einfügen', icon: 'fal fa-border-left' },\n      { id: 'addColumnAfter', display: 'Spalte rechts einfügen', icon: 'fal fa-border-right' },\n      { id: 'addRowBefore', display: 'Reihe vorher einfügen', icon: 'fal fa-border-top' },\n      { id: 'addRowAfter', display: 'Reihe nachher einfügen', icon: 'fal fa-border-bottom' },\n      { id: 'deleteColumn', display: 'Spalte löschen', icon: '' },\n      { id: 'deleteRow', display: 'Reihe löschen', icon: '' },\n      { id: 'deleteTable', display: 'Tabelle löschen', icon: '' },\n      { id: 'mergeCells', display: 'Zellen vereinen', icon: '' },\n      { id: 'splitCell', display: 'Zellen teilen', icon: '' },\n      { id: 'toggleHeaderColumn', display: 'Kopfspalte umschalten', icon: '' },\n      { id: 'toggleHeaderRow', display: 'Kopfzeile umschalten', icon: '' },\n      { id: 'toggleHeaderCell', display: 'Zelle als Kopfzelle umschalten', icon: '' },\n      { id: 'mergeOrSplit', display: 'Zellen teilen/verbinden umschalten', icon: '' },\n    ]\n\n    const childTypes = params.childTypes || []\n    const ensuredChildForms = await Aeppic.getAll(childTypes.map((c) => c.form))\n\n    locals.insertChildTypes = ensuredChildForms.reduce((result, childType, index) => {\n      if (childType?.f.id === 'form') {\n        result.push(childTypes[index])\n      }\n      return result\n    }, [])\n\n    locals.editor = await this.createEditor()\n\n    if (!locals.editor) {\n      // fallback to quill\n      return\n    }\n\n    const resolveCompany = async () => {\n      if (document.a_forms[0] === 'c69a0c1c-6c53-40c2-87e8-5ace866bf1df') {\n        return Aeppic.get(document.a[0])\n      }\n\n      return null\n    }\n\n    locals.company = await resolveCompany()\n\n    await Aeppic.nextRenderPass()\n    await Aeppic.nextRenderPass()\n\n    await Aeppic.nextRenderPass()\n    await Aeppic.nextRenderPass()\n\n    this._documentWatcher = Aeppic.watch(document, (newDocument, reason) => {\n      const fieldChanged = !!newDocument.previous?.patch?.[fieldName]\n\n      params.debug && console.log('watch', { newDocument, reason }, { oldV: document.v, newV: newDocument.v, fieldChanged })\n\n      if (!reason.updated || !fieldChanged) {\n        return\n      }\n\n      // do not compare editor content with change if change was made by an internal call.\n      // NOTE: be aware that this might not always be true. An external watch might be mis-interpreted.\n      //       But that should not be a problem here, because the later change will be processed anyway.\n      if (this.pendingUpdateField > 0) {\n        this.pendingUpdateField -= 1\n        return\n      }\n\n      const newDocumentFieldValue = newDocument.data[fieldName]\n      const lengthChanged = locals.lastKnownEditorContentAsString.length !== newDocumentFieldValue.length\n\n      const contentChanged = locals.lastKnownEditorContentAsString !== newDocumentFieldValue\n      const contentChangedExternally = lengthChanged || contentChanged\n\n      params.debug && console.log('watch', { contentChangedExternally })\n\n      if (contentChangedExternally) {\n        const field = newDocumentFieldValue.startsWith('{') ? JSON.parse(newDocumentFieldValue) : newDocumentFieldValue\n\n        params.debug && console.log('.  watch', { fieldName, field, newDocumentFieldValue })\n        locals.editor.commands.setContent(field)\n        locals.lastKnownEditorContentAsString = newDocumentFieldValue\n      } else {\n        params.debug && console.log('.    watch: NO need to change, Editor knows already the newst value')\n      }\n    })\n\n    watchParam('readonly', () => {\n      if (locals.editor) {\n        locals.editor.setOptions({\n          editable: !this.isReadonly,\n        })\n      }\n    })\n\n    $on('insert-reference', (value) => this.insertEmbed(value))\n    $on('smile', (event) => $refs.popupEmoji.show(event))\n    $on('save-now', (event) => this._saveNow())\n  }\n\n  selectedOption(option) {\n    const fakeClickEvent = { target: $refs.addOptions.$el }\n    params.debug && console.log(fakeClickEvent, { option })\n\n    if (option.id === 'ref-employee') {\n      $refs.refEmployee.$emit('show', fakeClickEvent)\n      return\n    }\n\n    if (option.id === 'ae-ref') {\n      $refs.ref.$emit('show', fakeClickEvent)\n      return\n    }\n\n    if (['h1', 'h2', 'h3'].includes(option.id)) {\n      locals.editor\n        .chain()\n        .focus()\n        .toggleHeading({ level: Number(option.id.slice(1, 2)) })\n        .run()\n      return\n    }\n\n    if (option.id === 'link') {\n      $refs.popupLink.show(fakeClickEvent)\n      return\n    }\n\n    if (option.id === 'horizontal-rule') {\n      locals.editor.chain().focus().setHorizontalRule().run()\n      return\n    }\n\n    if (option.id === 'table') {\n      locals.editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: false }).run()\n      return\n    }\n\n    if (option.id === 'attachment') {\n      $refs.fileInput.click()\n      return\n    }\n\n    locals.editor.chain().focus()[`toggle${option.id}`]().run()\n  }\n\n  selectedTableOption(option) {\n    params.debug && console.log({ option })\n\n    const fakeClickEvent = { target: $refs.tableOptions.$el }\n\n    locals.editor.chain().focus()[option.id]().run()\n  }\n\n  async createEditor() {\n    params.debug && console.log('### createEditor')\n    const field = getField()\n    const content = field.startsWith('{') ? JSON.parse(field) : field\n\n    params.debug && console.log({ field, content })\n\n    const hasQuillEditorContent = typeof content === 'object' && 'ops' in content\n    if (hasQuillEditorContent) {\n      locals.fallbackToQuillEditor = true\n      return\n    }\n\n    this._resetInsertTableMenuSelectedCells()\n\n    locals.lastKnownEditorContentAsString = field\n\n    this.debouncedUpdateField = Aeppic.debounce(() => this._updateField(), 2000)\n\n    let isFirstUpdate = true\n\n    let upgradedContent = null\n\n    if (content && typeof content === 'string') {\n      upgradedContent = this._parseStringContent(content)\n    } else if (content.tiptapVersion === '2.x') {\n      upgradedContent = content.content\n    } else {\n      upgradedContent = this._upgradeContent(content.content)\n    }\n\n    const { Editor, AllExtensions } = packages.Editor\n\n    locals.extensions = [\n      AllExtensions.configure({\n        placeholder: {\n          placeholder: 'Schreiben Sie hier etwas ...',\n        },\n        table: {\n          resizable: true,\n        },\n        aeDesign: {\n          defaultParams: {\n            editorBoundary: 'window',\n          },\n        },\n        aeDesignInline: {\n          defaultParams: {\n            editorBoundary: 'window',\n          },\n        },\n        bubbleMenu: {},\n      }),\n      //         Heading: { levels: [1, 2, 3] },\n    ]\n\n    let resolve = null\n    const readyPromise = new Promise((r) => (resolve = r))\n\n    const editor = new Editor({\n      // disablePasteRules: ['italic', 'bold', 'strike', 'underline'],\n      content: {\n        type: 'doc',\n        tiptapVersion: '2.x',\n        content: upgradedContent,\n      },\n      extensions: locals.extensions,\n      editable: !this.isReadonly,\n      onCreate: () => {\n        resolve(editor)\n      },\n      onUpdate: (event) => {\n        // console.log('onUpdate', {event, isFirstUpdate })\n\n        if (isFirstUpdate) {\n          isFirstUpdate = false\n          this._updateField()\n\n          return\n        }\n\n        this.debouncedUpdateField()\n      },\n      onFocus: () => {\n        locals.menuActive = true\n      },\n      onSelectionUpdate: (event) => {\n        locals.menuActive = event.editor.isFocused\n      },\n      onBlur: (event) => {\n        locals.menuActive = locals.mouseover\n      },\n      editorProps: {\n        // handlePaste(view, event, slice) {\n        //   // const content = [\n        //   //   {\n        //   //     type: 'paragraph',\n        //   //     attrs: {\n        //   //       class: 'pasted-content'\n        //   //     },\n        //   //     content: [\n        //   //       {\n        //   //         type: 'link',\n        //   //         text: 'First paragraph',\n        //   //         attrs: {\n        //   //           class: 'pasted-content'\n        //   //         },\n        //   //       },\n        //   //     ],\n        //   //   },\n        //   // ]\n        //   // const content = '<span class=\"test\" >First paragraph</span>'\n        //   const content = '<p><span style=\"\">But this one is wrapped in a &lt;span&gt; tag with an inline style attribute, so it’s kept - even if it’s empty for now.</span></p>'\n        //   const c = JSON.parse(JSON.stringify(slice.content))\n        //   console.log({ view, event, slice, content, c })\n        //   locals.editor.commands.insertContent(content)\n        //   return true\n        // }\n      },\n    })\n\n    return readyPromise\n  }\n\n  _parseStringContent(contentAsString) {\n    const content = []\n\n    const parts = contentAsString.split('\\n')\n\n    for (const part of parts) {\n      if (!part) {\n        continue\n      }\n      content.push({ type: 'paragraph', content: [{ type: 'text', text: part }] })\n    }\n\n    return content\n  }\n\n  _resetInsertTableMenuSelectedCells() {\n    locals.insertMenuSelectedCells = {\n      x: 2,\n      y: 2,\n    }\n  }\n\n  insertTableMenu() {\n    const tippy = Aeppic.Modules.Tippy.default\n    const self = this\n\n    return tippy($refs.insertTableMenu.$el, {\n      content: $refs.insertTableMenuTemplate,\n      onShow(instance) {\n        self._resetInsertTableMenuSelectedCells()\n      },\n    })\n  }\n\n  insertTable() {\n    const { x, y } = locals.insertMenuSelectedCells\n    const options = { rows: y + 1, cols: x + 1, withHeaderRow: false }\n\n    locals.editor.chain().focus().insertTable(options).run()\n    this._menus.hide()\n  }\n\n  insertEditTableMenu() {\n    const tippy = Aeppic.Modules.Tippy.default\n    const self = this\n\n    return tippy($refs.editTableMenu.$el, {\n      content: $refs.editTableMenuTemplate,\n    })\n  }\n\n  insertDocumentReferenceMenu() {\n    if (!$refs.insertDocumentReferenceMenu) {\n      return null\n    }\n\n    const tippy = Aeppic.Modules.Tippy.default\n    const self = this\n\n    return tippy($refs.insertDocumentReferenceMenu.$el, {\n      content: $refs.insertDocumentReferenceMenuTemplate,\n    })\n  }\n\n  insertChildMenu() {\n    if (!$refs.insertChildMenu) {\n      return null\n    }\n\n    const tippy = Aeppic.Modules.Tippy.default\n    const self = this\n\n    return tippy($refs.insertChildMenu.$el, {\n      content: $refs.insertChildMenuTemplate,\n    })\n  }\n\n  focusInsertDocumentReference() {\n    this._menus.show(this._insertDocumentReferenceMenu)\n\n    const input = $refs.insertDocumentReferenceMenuInput\n\n    if (input) {\n      input.focus()\n    }\n  }\n\n  _upgradeContent(content) {\n    const simpleTypeRename = {\n      bullet_list: 'bulletList',\n      code_block: 'codeBlock',\n      hard_break: 'hardBreak',\n      horizontal_rule: 'horizontalRule',\n      list_item: 'listItem',\n      ordered_list: 'orderedList',\n      table_cell: 'tableCell',\n      table_header: 'tableHeader',\n      table_row: 'tableRow',\n    }\n\n    if (!content) {\n      return []\n    }\n\n    for (const row of content) {\n      if (row.type === 'todo_list') {\n        row.type = 'taskList'\n      } else if (row.type === 'todo_item') {\n        row.type = 'taskItem'\n        row.attrs.checked = row.attrs.done\n        delete row.attrs.done\n      } else if (simpleTypeRename[row.type]) {\n        row.type = simpleTypeRename[row.type]\n      } else if (row.type === 'aeDesign') {\n        row.type = 'aeDesignInline'\n        row.attrs['document-id'] = row.attrs.documentId\n        delete row.attrs.documentId\n        delete row.attrs.name\n      }\n\n      if (row.content) {\n        this._upgradeContent(row.content)\n      }\n    }\n\n    return content\n  }\n\n  get isReadonly() {\n    return readonly || params.readonly\n  }\n\n  _saveNow() {\n    this._updateField()\n    $emit('save')\n  }\n\n  _updateField(reason) {\n    if (!locals.editor || this.isReadonly) {\n      return\n    }\n\n    this.pendingUpdateField += 1\n\n    const contentJSON = locals.editor.getJSON()\n\n    if (typeof contentJSON === 'object') {\n      contentJSON.tiptapVersion = '2.x'\n    }\n\n    const content = JSON.stringify(contentJSON)\n\n    locals.lastKnownEditorContentAsString = content\n\n    params.debug && console.log('_updateField', { reason, fieldName, tailedContent: content.slice(-100) })\n\n    const emptifiedContent = content === this.EMPTY_EDITOR_CONTENT_JSONSTRINGIFIED ? '' : content\n\n    setField(emptifiedContent)\n\n    if (params.htmlTo) {\n      document.data[params.htmlTo] = locals.editor.getHTML()\n    }\n\n    if (params.textTo) {\n      document.data[params.textTo] = locals.editor.getText()\n    }\n  }\n\n  showEmbed() {\n    locals.editor.commands.aeDesign({\n      documentId: 'root',\n      name: 'tiptap-embed',\n    })\n  }\n\n  insertEmbed(documentOrId) {\n    params.debug && console.log('insertEmbed', documentOrId)\n\n    const id = typeof documentOrId === 'string' ? documentOrId : documentOrId.id\n\n    locals.editor.commands.insertContent(`<ae-design-inline document-id=\"${id}\"></ae-design-inline>`)\n\n    // locals.editor.commands.aeDesign({\n    //   documentId: id,\n    //   name: 'tiptap-embed'\n    // })\n  }\n\n  readonlyUpdated() {\n    const editable = !this.isReadonly\n\n    if (typeof locals.editor?.isEditable === 'boolean' && locals.editor.isEditable !== editable) {\n      locals.editor.setEditable(editable)\n    }\n  }\n\n  showLinkCreator(clickEvent) {\n    locals.newLink = ''\n    $refs.popupLink.show(clickEvent)\n  }\n\n  insertLink() {\n    let link = locals.newLink\n\n    // add http if no protocol is selected\n    if (!link.includes(':')) {\n      link = 'http://' + link\n    }\n\n    locals.editor.commands.setLink({ href: link })\n    $refs.popupLink && $refs.popupLink.close()\n  }\n\n  destroy() {\n    if (this.debouncedUpdateField) {\n      clearTimeout(this.debouncedUpdateField)\n      this.debouncedUpdateField = null\n    }\n\n    if (this._menus) {\n      this._insertTableMenu?.destroy()\n      this._editTableMenu?.destroy()\n      this._insertDocumentReferenceMenu?.destroy()\n      this._insertChildMenu?.destroy()\n      this._menus.destroy()\n    }\n\n    if (locals.editor) {\n      locals.editor.destroy()\n      locals.editor = null\n    }\n\n    if (this._documentWatcher) {\n      this._documentWatcher.stop()\n      this._documentWatcher = null\n    }\n\n    if ($refs.domNode) {\n      $refs.domNode.ownerDocument?.removeEventListener('selectionchange', this.onSelectionChangeHandler)\n    }\n  }\n\n  visibilityInsertTable(event) {\n    if (locals.showTableMenus) {\n      return\n    }\n    params.debug && console.log(event)\n\n    this._insertTableMenu = this.insertTableMenu()\n    this._editTableMenu = this.insertEditTableMenu()\n    this._insertDocumentReferenceMenu = this.insertDocumentReferenceMenu()\n    this._insertChildMenu = this.insertChildMenu()\n\n    const boundary = $refs.page\n\n    this._menus = Aeppic.Modules.Tippy.createSingleton(\n      [this._insertTableMenu, this._editTableMenu, this._insertDocumentReferenceMenu, this._insertChildMenu].filter(\n        (e) => !!e\n      ),\n      {\n        moveTransition: 'transform 0.1s ease-out',\n        position: 'bottom',\n        boundary,\n        arrow: true,\n        theme: 'light-border',\n        delay: [0, 1000],\n        interactive: true,\n        offset: [0, 0],\n      }\n    )\n\n    locals.showTableMenus = true\n  }\n\n  get isInvalid() {\n    // check if any required is configured\n    if (!fieldInfo.tags?.required) {\n      return false\n    }\n\n    // check if simple text-field validation passes\n    if (validationErrors?.required) {\n      return true\n    }\n\n    // check if field is longer than empty or emptyEditorContent\n    const filledOut = getField()?.length > this.EMPTY_EDITOR_CONTENT_JSONSTRINGIFIED.length\n    if (filledOut) {\n      return false\n    }\n\n    return true\n  }\n\n  maybeUpdate() {\n    if (params.ctrlEnterSave) {\n      this._updateField()\n\n      $emit('save')\n    }\n  }\n\n  onFileInput(event) {\n    if (this.isReadonly) {\n      return\n    }\n\n    $refs.dropHandler.$emit('input', event)\n\n    $refs.fileInput.value = ''\n  }\n\n  addedFile(event) {\n    if (this.isReadonly) {\n      return\n    }\n\n    params.debug && console.log('added', event)\n\n    const ids = event.documentIds\n\n    for (const id of ids) {\n      this.insertEmbed(id)\n    }\n  }\n}\n","css":"._CSS-editor__content__component > .ProseMirror p>code {\n  padding: var(--ae-size-2);\n  border-radius: var(--ae-border-radius);\n  font-size: var(--ae-font-size-3);\n  font-weight: 700;\n\n  background: rgba(0,0,0,.1);\n  color: rgba(0,0,0,.8);\n}\n\n._CSS-editor__content__component > .ProseMirror pre {\n  padding: var(--ae-size-2);\n  border-radius: var(--ae-border-radius);\n  font-size: var(--ae-font-size-3);\n  font-weight: 700;\n  overflow-x: auto;\n\n  background: #000;\n  color: #fff;\n}\n\n._CSS-editor__content__component > .ProseMirror, \n._CSS-editor__content__component > .ProseMirror p {\n  margin: 0;\n  padding: 0.2rem var(--ae-size-1);\n}\n\n._CSS-editor__content__component .ProseMirror-focused {\n  outline: none;\n}\n\n._CSS-editor__content__component .embed__ae-design {\n  display: inline-block;\n}\n\n._CSS-popup-emoji {\n  padding: var(--ae-space-2);\n  display: flex;\n  flex-flow: column;\n\n  align-items: center;\n}\n\n._CSS-editor__content__component > .ProseMirror > h1 {\n  font-weight: 600;\n  font-size: 1.5rem;\n  line-height: 2rem;\n}\n\n._CSS-editor__content__component > .ProseMirror > h2 {\n  font-weight: 600;\n  font-size: 1.25rem;\n  line-height: 1.75rem;\n}\n\n._CSS-editor__content__component > .ProseMirror > h3 {\n  font-weight: 600;\n\tfont-size: 1.125rem;\n  line-height: 1.75rem;\n}\n\n\n._CSS-editor__content__component > .ProseMirror .tableWrapper > table {\n  border: 1px solid black;\n}\n\n._CSS-editor__content__component > .ProseMirror .tableWrapper > table tr {\n  border: 1px solid black;\n}\n\n._CSS-editor__content__component > .ProseMirror .tableWrapper > table td {\n  border: 1px solid black;\n  /* FIXME: only mark manual tables, not pasted content */\n  /* border: 1px solid transparent; */\n}\n\n._CSS-editor__content__component > .ProseMirror > blockquote {\n  background-color: #f4f4f5;\n  padding: 5px;\n  border-radius: 0.375rem;\n}\n\n\n._CSS-editor__content__component > .ProseMirror a {\n  color: blue;\n  text-decoration: underline;\n  cursor: pointer;\n}\n\n._CSS-editor__content__component > .ProseMirror ul[data-type=\"taskList\"] {\n  list-style: none;\n  padding: 0;\n}\n\n._CSS-editor__content__component > .ProseMirror ul[data-type=\"taskList\"] li {\n  display: flex;\n  align-items: center;\n}\n\n._CSS-editor__content__component > .ProseMirror ul[data-type=\"taskList\"] li > label {\n   flex: 0 0 auto;\n  margin-right: 0.5rem;\n  user-select: none;\n}\n\n._CSS-editor__content__component > .ProseMirror ul[data-type=\"taskList\"] li  > div{\n  flex: 1 1 auto;\n}\n\n._CSS-editor__content__component > .ProseMirror ul[data-type=\"taskList\"] li  input[type=\"checkbox\"] {\n  cursor: pointer;\n}\n\n._CSS-editor__content__component > .ProseMirror ul[data-type=\"taskList\"] {}\n\n._CSS-editor__content__component > .ProseMirror ul {\n  list-style-position: inside;\n  list-style-type: disc;\n}\n\n._CSS-editor__content__component > .ProseMirror ol {\n  list-style-position: inside;\n  list-style-type: decimal;\n}\n\n._CSS-editor__content__component > .ProseMirror ul > li > p,\n._CSS-editor__content__component > .ProseMirror ol > li > p {\n  display: inline;\n}\n\n._CSS-editor__content--empty {\n  pointer-events: none;\n  position: relative;\n}\n\n._CSS-editor__content--empty:before {\n  content: attr(data-empty-text);\n  float: left;\n  color: var(--ae-background-500);\n  pointer-events: none;\n  height: 0;\n  font-style: italic;\n  position: absolute;\n  left: 10px;\n  right: 10px;\n}\n","description":"","device":"ALL","label":"","limitTypes":false,"name":"editor","namespace":"rd","packages":[{"id":"31da2761-bf6b-40e8-af3c-7bbbbb473058","text":"Editor - TipTap2 ","v":"df4f89c2fdfce7d2"},{"id":"3ee197b0-c589-414a-9ef6-f7eed43a4dff","text":"vue-observe-visibilty (Directive)","v":"0c5f5b9139c2666e"}],"parameters":[],"standalone":"FIELD","template":"<ae-control v-if=\"locals.fallbackToQuillEditor\" name=\"editor-quill\" :document=\"document\" :field-name=\"fieldName\" :params=\"params\" v-on=\"$listeners\" :readonly=\"readonly\"></ae-control>\n<template v-else> \n  <div class=\"flex flex-col\" \n        :key=\"locals.key\"\n        @mouseover=\"locals.mouseover = true\"\n        @mouseleave=\"{ locals.mouseover = false; locals.menuActivexxxx = false } \"\n        :class=\"{ '_CSS-editor--readonly': readonly }\" \n        ref=\"page\"\n        v-show=\"locals.editor\"    \n        :data-test=\"fieldName\"\n  >\n    <!-- <section  v-show=\"!locals.menuActive\"\n              class=\"border-x border-t rounded-t-md flex gap-1 flex-wrap p-1 border-b text-sm\"\n          @xmouseover=\"locals.menuActive = true\"\n        @xmouseleave=\"locals.menuActive = false\" \n    >\n    </section> -->  \n    <section v-if=\"controller.isReadonly || params.withoutMenu\" :class=\"{ 'border-x border-t rounded-t-md': !params.withoutBorder }\">\n    </section>\n    <section  v-show=\"!controller.isReadonly && !params.withoutMenu\"\n              class=\"rounded-t-md flex gap-1 flex-wrap p-1   text-sm\"\n              :class=\"{ 'border-x-red-500 border-t-red-500': controller.isInvalid, 'border-x border-t border-b': !params.withoutBorder }\"\n\n          @mouseover=\"locals.menuActive = true\"\n        @xmouseleave=\"locals.menuActive = false\" \n    > \n      <ae-layout layout-id=\"8bc795f2-7ac8-4b95-9019-0b27b178a36e\"\n                :param-options=\"locals.addOptions\"\n                :param-button-params=\"{ icon: 'far fa-plus', classes: [locals.mouseover ? 'opacity-100' : 'opacity-30' ] }\" \n                @selected=\"controller.selectedOption($event)\"\n                ref=\"addOptions\"\n      ></ae-layout>\n\n      \n      <div v-if=\"(locals.commandsSet.has('bold') || locals.commandsSet.has('italic') || locals.commandsSet.has('underline') || locals.commandsSet.has('strikethrough') || locals.commandsSet.has('highlight'))\"\n          class=\"mx-1 \" :class=\"[!params.withoutBorder && 'border-l border-neutral-200']\"\n      ></div>\n      \n      <ae-layout v-if=\"locals.commandsSet.has('bold')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleBold().run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-bold\" :param-primary=\"locals.editor?.isActive('bold')\">\n      </ae-layout>\n      <ae-layout v-if=\"locals.commandsSet.has('italic')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleItalic().run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-italic\" :param-primary=\"locals.editor?.isActive('italic')\">\n      </ae-layout>\n      <ae-layout v-if=\"locals.commandsSet.has('underline')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleUnderline().run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-underline\" :param-primary=\"locals.editor?.isActive('underline')\">\n      </ae-layout>\n      <ae-layout v-if=\"locals.commandsSet.has('strikethrough')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleStrike().run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-strikethrough\" :param-primary=\"locals.editor?.isActive('strike')\">\n      </ae-layout>\n      <!-- <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleCodeBlock().run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-code\" :param-primary=\"locals.editor?.isActive('codeBlock')\">\n      </ae-layout> -->\n      <ae-layout v-if=\"locals.commandsSet.has('highlight')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleHighlight().run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-highlighter\" :param-primary=\"locals.editor?.isActive('highlight')\">\n      </ae-layout>\n\n      <!-- <ae-layout xv-if=\"locals.commandsSet.has('highlight')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.pastePlainTextOnly = !locals.pastePlainTextOnly\"\n      class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-paste\" :param-primary=\"locals.pastePlainTextOnly\">\n      </ae-layout> -->\n\n      <div class=\"mx-1 \" :class=\"[!params.withoutBorder && 'border-l border-neutral-200']\"></div>\n\n      <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  v-if=\"locals.insertChildTypes.length\" ref=\"insertChildMenu\" title=\"Insert document\"\n          @selected=\"controller.focusInsertChildDocument()\" param-icon=\"fa fa-plus\"  class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\">\n      </ae-layout>\n\n\n       <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().undo().run()\" :param-disabled=\"!locals.editor?.can().undo()\" param-icon=\"far fa-undo\" class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\">\n      </ae-layout>\n      <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().redo().run()\" :param-disabled=\"!locals.editor?.can().redo()\" param-icon=\"far fa-redo\" class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\">\n      </ae-layout>\n\n      <template v-if=\"locals.editor?.can().deleteTable()\">\n        <div class=\"mx-1 \" :class=\"[!params.withoutBorder && 'border-l border-neutral-200']\"></div>\n        <ae-layout layout-id=\"8bc795f2-7ac8-4b95-9019-0b27b178a36e\"\n                  :param-options=\"locals.tableOptions\"\n                  :param-button-params=\"{ icon: 'far fa-table', primary: true, title: 'Tabellen Werkzeuge' }\"\n                  @selected=\"controller.selectedTableOption($event)\"\n                  ref=\"tableOptions\"\n        ></ae-layout>\n      </template>\n \n\n      <ae-layout  \n          v-if=\"locals.commandsSet.has('ae-ref-employee')\"\n          v-show=\"false\"\n          layout-id=\"ef23b6bd-ec74-4701-ab8d-71a6d67fd9e4\"\n          :param-settings=\"locals.refButtonEmployee\"\n          :param-document=\"document\"\n          @selected=\"controller.insertEmbed($event)\"\n          :param-readonly=\"readonly\"\n          param-icon=\"fa fa-at\"\n          param-hide-path=\"true\" \n          param-title=\"Mitarbeiter-Verknüpfung einfügen\"\n          ref=\"refEmployee\"\n      ></ae-layout>\n\n\n      <ae-layout v-if=\"locals.commandsSet.has('ae-ref') && locals.company\"\n            layout-id=\"896c9789-d13f-4fdc-9b30-9b1d6df4a08b\" \n            :param-company=\"locals.company\"\n            ref=\"ref\"\n            :param-show-ui=\"false\"\n            @selected=\"controller.insertEmbed($event)\"\n            param-context-text=\"Dokument verknüpfen\"\n        ></ae-layout>\n\n\n      <!-- <ae-layout  \n          v-if=\"locals.commandsSet.has('ae-ref')\"\n          v-show=\"false\"\n          layout-id=\"ef23b6bd-ec74-4701-ab8d-71a6d67fd9e4\"\n          :param-settings=\"locals.refButtonSettings\"\n          :param-document=\"document\"\n          @selected=\"controller.insertEmbed($event)\"\n          :param-readonly=\"readonly\"\n          param-title=\"Verknüpfung einfügen\"\n          ref=\"ref\"\n      ></ae-layout> -->\n    </section> \n\n    <div ref=\"insertTableMenuTemplate\" style=\"width:128px;height:128px\" v-show=\"locals.showTableMenus\">  \n      <svg class=\"_CSS-table-size fill-white\" viewBox=\"0 0 256 256\" style=\"width:128px;height:128px\">\n        <g>\n          <rect\n              x=\"0\" \n              y=\"0\"\n              width=\"256\"\n              height=\"256\"\n          ></rect>\n          <template v-for=\"y of [0,1,2,3,4,5,6,7]\">\n            <rect\n              v-for=\"x of [0,1,2,3,4,5,6,7]\"\n              :x=\"x * 32\" \n              :y=\"y * 32\"\n              :width=\"32\"\n              :height=\"32\" \n              ref=\"insertTableMenuSizeCells\"\n              class=\"stroke-blue-200\"\n              :class=\"[locals.insertMenuSelectedCells && x <= locals.insertMenuSelectedCells.x && y <= locals.insertMenuSelectedCells.y && 'fill-blue-700']\"\n              @mouseenter=\"locals.insertMenuSelectedCells = { x, y }\"\n              @click=\"controller.insertTable()\">\n            </rect>\n              <!-- :class=\"x <= locals.insertMenuSelectedCells.x && y <= locals.insertMenuSelectedCells.y ? '_CSS_insertTableCellSelected' : ''\" -->\n          </template>\n        </g>\n      </svg>\n    </div>\n\n    <div ref=\"editTableMenuTemplate\" class=\"flex flex-wrap gap-2\" v-show=\"locals.showTableMenus\">\n      <button @click=\"locals.editor.chain().focus().addColumnBefore().run()\" :disabled=\"!locals.editor?.can().addColumnBefore()\" title=\"Insert column to the left\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-column-insert-left\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n          <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n          <path d=\"M14 4h4a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1v-14a1 1 0 0 1 1 -1z\"></path>\n          <line x1=\"5\" y1=\"12\" x2=\"9\" y2=\"12\"></line>\n          <line x1=\"7\" y1=\"10\" x2=\"7\" y2=\"14\"></line>\n        </svg>\n      </button>\n      <button @click=\"locals.editor.chain().focus().addColumnAfter().run()\" :disabled=\"!locals.editor?.can().addColumnAfter()\" title=\"Insert column to the right\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-column-insert-right\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n          <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n          <path d=\"M6 4h4a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1v-14a1 1 0 0 1 1 -1z\"></path>\n          <line x1=\"15\" y1=\"12\" x2=\"19\" y2=\"12\"></line>\n          <line x1=\"17\" y1=\"10\" x2=\"17\" y2=\"14\"></line>\n        </svg>\n      </button>\n      <button @click=\"locals.editor.chain().focus().addRowBefore().run()\" :disabled=\"!locals.editor?.can().addRowBefore()\" title=\"Insert row before\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-row-insert-top\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n          <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n          <path d=\"M4 18v-4a1 1 0 0 1 1 -1h14a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-14a1 1 0 0 1 -1 -1z\" />\n          <path d=\"M12 9v-4\" />\n          <line x1=\"10\" y1=\"7\" x2=\"14\" y2=\"7\" />\n        </svg>\n      </button>\n      <button @click=\"locals.editor.chain().focus().addRowAfter().run()\" :disabled=\"!locals.editor?.can().addRowAfter()\" title=\"Insert row after\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon icon-tabler icon-tabler-row-insert-bottom\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n          <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n          <path d=\"M20 6v4a1 1 0 0 1 -1 1h-14a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1h14a1 1 0 0 1 1 1z\" />\n          <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"19\" />\n          <line x1=\"14\" y1=\"17\" x2=\"10\" y2=\"17\" />\n        </svg>\n      </button>\n      <button @click=\"locals.editor.chain().focus().deleteColumn().run()\" :disabled=\"!locals.editor?.can().deleteColumn()\" title=\"Delete column\">\n        Spalte löschen\n      </button>\n      <button @click=\"locals.editor.chain().focus().deleteRow().run()\" :disabled=\"!locals.editor?.can().deleteRow()\">\n        Zeile löschen\n      </button>\n      <button @click=\"locals.editor.chain().focus().deleteTable().run()\" :disabled=\"!locals.editor?.can().deleteTable()\">\n        Tabelle löschen\n      </button>\n      <button @click=\"locals.editor.chain().focus().mergeCells().run()\" :disabled=\"!locals.editor?.can().mergeCells()\">\n        Zellen verbinden\n      </button>\n      <button @click=\"locals.editor.chain().focus().splitCell().run()\" :disabled=\"!locals.editor?.can().splitCell()\">\n        Zellen teilen\n      </button>\n      <button @click=\"locals.editor.chain().focus().toggleHeaderColumn().run()\" :disabled=\"!locals.editor?.can().toggleHeaderColumn()\">\n        Kopfspalte umschalten\n      </button>\n      <button @click=\"locals.editor.chain().focus().toggleHeaderRow().run()\" :disabled=\"!locals.editor?.can().toggleHeaderRow()\">\n        Kopfzeile umschalten\n      </button>\n      <button @click=\"locals.editor.chain().focus().toggleHeaderCell().run()\" :disabled=\"!locals.editor?.can().toggleHeaderCell()\">\n        Zelle als Kopfzelle umschalten\n      </button>\n      <button @click=\"locals.editor.chain().focus().mergeOrSplit().run()\" :disabled=\"!locals.editor?.can().mergeOrSplit()\">\n        Zellen teilen/verbinden umschalten\n      </button>\n    </div>\n\n    <div ref=\"insertChildMenuTemplate\" class=\"_CSS-insert-doc-ref-menu\" style=\"display:flex;flex-direction:column;position:relative;max-height:50vh;overflow-y:auto\">\n      <div class=\"_CSS-child-type\" v-for=\"childType of locals.insertChildTypes\">\n        <button class=\"ae-button\" @click=\"controller.insertNewChild(childType)\" style=\"display:flex;flex-direction:row;align-items:center\">\n          <div v-if=\"childType.list\" style=\"margin-right:1rem\" title=\"List\">\n            <ae-icon  class=\"far fa-th-list\" style=\"opacity:0.5\"></ae-icon>\n          </div>\n          <ae-design name=\"search-title\" :document-id=\"childType.form\" :param-minimized-info=\"true\" :param-hide-path=\"true\"></ae-design>\n        </button>\n      </div>\n    </div>\n \n    <bubble-menu  \n      v-if=\"locals.editor\"\n      :tippy-options=\"{ duration: 100, arrow: false, maxWidth: 'none' }\"\n      :editor=\"locals.editor\" \n    >\n      <div class=\"bg-white border rounded-md flex gap-1 text-xs shadow\">  \n        <ae-layout v-if=\"locals.commandsSet.has('bold')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleBold().run()\"\n        class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-bold\" :param-primary=\"locals.editor?.isActive('bold')\">\n        </ae-layout>\n        <ae-layout v-if=\"locals.commandsSet.has('italic')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleItalic().run()\"\n        class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-italic\" :param-primary=\"locals.editor?.isActive('italic')\">\n        </ae-layout>\n        <ae-layout v-if=\"locals.commandsSet.has('underline')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleUnderline().run()\"\n        class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-underline\" :param-primary=\"locals.editor?.isActive('underline')\">\n      </ae-layout>\n            <ae-layout v-if=\"locals.commandsSet.has('strikethrough')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleStrike().run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-strikethrough\" :param-primary=\"locals.editor?.isActive('strike')\">\n      </ae-layout>\n            <ae-layout v-if=\"locals.commandsSet.has('highlight')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleHighlight().run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-highlighter\" :param-primary=\"locals.editor?.isActive('highlight')\">\n      </ae-layout>\n\n      <div class=\"mx-1 \" :class=\"[!params.withoutBorder && 'border-l border-neutral-200']\"></div>\n\n      <ae-layout v-if=\"locals.commandsSet.has('list-ul')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleBulletList().run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-list-ul\" :param-primary=\"locals.editor?.isActive('bulletList')\">\n      </ae-layout>\n      <ae-layout v-if=\"locals.commandsSet.has('list-ol')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleOrderedList().run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-list-ol\" :param-primary=\"locals.editor?.isActive('orderedList')\">\n      </ae-layout>\n\n<div class=\"mx-1 \" :class=\"[!params.withoutBorder && 'border-l border-neutral-200']\"></div>\n\n        <ae-layout v-if=\"locals.commandsSet.has('h1')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleHeading({ level: 1 }).run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-h1\" :param-primary=\"locals.editor?.isActive('heading', { level: 1 })\">\n      </ae-layout>\n      <ae-layout v-if=\"locals.commandsSet.has('h2')\"  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleHeading({ level: 2 }).run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-h2\" :param-primary=\"locals.editor?.isActive('heading', { level: 2 })\">\n      </ae-layout>\n      <ae-layout v-if=\"locals.commandsSet.has('h3')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleHeading({ level: 3 }).run()\"\n          class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-h3\" :param-primary=\"locals.editor?.isActive('heading', { level: 3 })\">\n      </ae-layout>\n      <div class=\"mx-1 \" :class=\"[!params.withoutBorder && 'border-l border-neutral-200']\"></div>\n\n      <ae-layout v-if=\"locals.commandsSet.has('link')\" layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  title=\"Add external web reference\" param-icon=\"fa fa-link\"  class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" @selected=\"$refs.popupLink.show($event)\">\n      </ae-layout> \n      </div>\n    </bubble-menu>\n\n    <floating-menu  \n      v-if=\"locals.editor\"\n      :tippy-options=\"{ duration: 100, arrow: false, offset: [0, 30] }\"\n      :editor=\"locals.editor\"\n    > \n\n      <div class=\"bg-white border rounded-md flex gap-1 text-xs shadow\">\n        <ae-layout v-if=\"locals.commandsSet.has('h1')\"  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleHeading({ level: 1 }).run()\"\n            class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-h1\" :param-primary=\"locals.editor?.isActive('heading', { level: 1 })\">\n        </ae-layout>\n        <ae-layout v-if=\"locals.commandsSet.has('h2')\"  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleHeading({ level: 2 }).run()\"\n            class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-h2\" :param-primary=\"locals.editor?.isActive('heading', { level: 2 })\">\n        </ae-layout>\n        <ae-layout v-if=\"locals.commandsSet.has('h3')\"  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"  @selected=\"locals.editor.chain().focus().toggleHeading({ level: 3 }).run()\"\n            class=\"opacity-30\" :class=\"{ 'opacity-100': locals.mouseover }\" param-icon=\"far fa-h3\" :param-primary=\"locals.editor?.isActive('heading', { level: 3 })\">\n        </ae-layout>\n      </div>\n    </floating-menu>\n\n\n    <div class=\"rounded-b-md p-1\"\n        :class=\"{ 'border-red-500': controller.isInvalid, 'border-x border-b': !params.withoutBorder }\"\n    >\n      <div v-if=\"!readonly && field.length <= 47\" class=\"_CSS-editor__content--empty\" :data-empty-text=\"params.placeholderText\"></div>\n      <editor-content class=\"p-1 _CSS-editor__content__component\"\n                      :editor=\"locals.editor\"\n                      v-if=\"!readonly || field.length > 47\"\n                      @keydown.ctrl.enter.native.capture=\"controller.maybeUpdate()\"\n      ></editor-content>\n    </div>\n  </div>\n \n  <div style=\"display:none;\" :key=\"'_CSS-readonly_' + readonly\" >\n    {{ controller.readonlyUpdated() }}\n  </div>\n \n  <ae-popup ref=\"popupLink\">\n    <div>\n      <div class=\"w-full flex flex-col gap-1 p-1\"> \n        <input  class=\"flex-1 p-1 border rounded-md\"  type=\"text\" v-model=\"locals.newLink\" v-focus>\n        <ae-layout  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n                    @selected=\"controller.insertLink()\"\n                    param-icon=\"far fa-link\" \n                    param-text=\"Link einfügen\"\n                    :param-disabled=\"!locals.newLink\"\n                    :param-primary=\"true\"\n        >\n        </ae-layout>\n      </div>\n    </div>\n  </ae-popup>\n\n  <ae-popup ref=\"popupEmoji\">\n    <div class=\"_CSS-popup-emoji\">\n      <div style=\"margin-bottom:var(--ae-space-1);\">Emoji einfügen mit der Tastenkombination... </div>\n      <template v-if=\"Aeppic.navigator.userAgent.indexOf('Windows') >= 0\">\n        <ae-icon class=\"fab fa-windows\"></ae-icon> + Punkt (.)\n      </template>\n      <template v-else-if=\"Aeppic.navigator.userAgent.indexOf('Mac') >= 0\">\n        control + command + Leertaste\n      </template>\n      <template v-else>\n        Strg + Punkt (.)\n      </template>\n      \n    </div>\n  </ae-popup>\n</template>\n\n<!-- file-input -->\n<input v-show=\"false\" ref=\"fileInput\" type=\"file\" @change=\"controller.onFileInput($event)\" multiple>\n<ae-layout  layout-id=\"8ae0359f-4d25-4d17-baf0-f69c006bd4ad\"\n            :param-document=\"document\"\n            :param-show-ui=\"false\"\n            param-file-form-id=\"dbee6e16-d63a-493c-bba6-505f79918f9f\"\n            param-file-form-field=\"datei\"\n            param-file-name-to-field=\"name\"\n            :param-save-new-documents=\"true\"\n            :param-flatten=\"true\"\n            ref=\"dropHandler\"\n            @added=\"controller.addedFile($event)\"\n></ae-layout>\n\n\n<div ref=\"domNode\" v-show=\"false\"></div>","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"246d120a-bee3-4d78-919c-a9f6fb4629d5","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-03-12T14:21:38+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"b5c25f04d7436165"},"stamps":[],"t":1773325298203,"v":"81d8bb38e8653f36"},{"id":"515636d7-6865-439b-b2c6-47bc3335d5a9","v":"0724df15d6e8e842","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd-WIP","name":"editor","template":"<ae-control v-if=\"locals.fallbackToQuillEditor\" name=\"editor-quill\" :document=\"document\" :field-name=\"fieldName\" :params=\"params\" v-on=\"$listeners\" :readonly=\"readonly\"></ae-control>\n<template v-else>\n  <div class=\"flex flex-col\" \n        :key=\"locals.key\"\n        @mouseover=\"locals.mouseover = true\"\n        @mouseleave=\"locals.mouseover = false\"\n        :class=\"{ '_CSS-editor--readonly': readonly }\" \n  >\n    <section class=\"border-x border-t rounded-t-md\"> \n      <editor-menu-bar v-if=\"!readonly && !params.withoutMenu\"\n                        :editor=\"locals.editor\"\n                        v-slot=\"scope\"\n                         \n      >\n        <!-- NOTE: editor-menu-bar and this div will be merged to one single DOM element -->\n        <div class=\"flex gap-1 w-full flex-wrap\" :class=\"{ 'opacity-10': !locals.focus && !locals.mouseover}\">\n          <div v-if=\"locals.commandsSet.has('bold') || locals.commandsSet.has('italic') || locals.commandsSet.has('strikethrough') || locals.commandsSet.has('underline')\"\n                class=\"ae-button ae-button--small\"\n                :disabled=\"readonly\"\n                :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('bold') || locals.activeStatuses.includes('italic') || locals.activeStatuses.includes('strike') || locals.activeStatuses.includes('underline') }\"\n                @click.stop=\"$refs.formats.toggle($event)\"\n          >\n            <ae-icon class=\"fa fa-text\"></ae-icon>\n          </div>  \n\n          <ae-popup ref=\"formats\">\n            <template #default=\"scope\">\n              <div class=\"flex gap-1\">\n                <button v-if=\"locals.commandsSet.has('bold')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('bold') }\" @click.stop=\"locals.editor.commands.bold()\">\n                  <ae-icon class=\"fa fa-bold\"></ae-icon> \n                </button>   \n\n                <button v-if=\"locals.commandsSet.has('italic')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('italic') }\" @click.stop=\"locals.editor.commands.italic\">\n                  <ae-icon class=\"fa fa-italic\"></ae-icon>\n                </button>\n\n                <button v-if=\"locals.commandsSet.has('strikethrough')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('strike') }\" @click.stop=\"locals.editor.commands.strike\">\n                  <ae-icon class=\"fa fa-strikethrough\"></ae-icon>\n                </button>\n\n                <button v-if=\"locals.commandsSet.has('underline')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('underline') }\" @click.stop=\"locals.editor.commands.underline\">\n                  <ae-icon class=\"fa fa-underline\"></ae-icon>\n                </button>\n              </div>\n            </template>\n          </ae-popup>\n\n        <button v-if=\"locals.commandsSet.has('link')\" class=\"ae-button ae-button--small\" :disabled=\"readonly || locals.activeStatuses.includes('link')\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('link') }\" @click.stop=\"controller.showLinkCreator($event)\">\n          <ae-icon class=\"fa fa-link\"></ae-icon>\n        </button>\n\n        <div v-if=\"locals.commandsSet.has('link') || locals.commandsSet.has('list-ul') || locals.commandsSet.has('list-ol') || locals.commandsSet.has('tasks')\"\n                class=\"ae-button ae-button--small\"\n                :disabled=\"readonly\"\n                :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('bullet_list') || locals.activeStatuses.includes('ordered_list') || locals.activeStatuses.includes('todo_list')  }\"\n                @click.stop=\"$refs.formats.toggle($event)\"\n          >\n            <ae-icon class=\"fa fa-list-alt\"></ae-icon>\n          </div>  \n\n          <ae-popup ref=\"formats\">\n            <template #default=\"scope\">\n              <div class=\"flex gap-1\">\n                <button v-if=\"locals.commandsSet.has('list-ul')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('bullet_list') }\" @click.stop=\"locals.editor.commands.bullet_list\">\n                  <ae-icon class=\"fa fa-list-ul\"></ae-icon>\n                </button>\n                <button v-if=\"locals.commandsSet.has('list-ol')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('ordered_list') }\" @click.stop=\"locals.editor.commands.ordered_list\">\n                  <ae-icon class=\"fa fa-list-ol\"></ae-icon>\n                </button>\n                <button v-if=\"locals.commandsSet.has('tasks')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('todo_list') }\" @click.stop=\"locals.editor.commands.todo_list\">\n                  <ae-icon class=\"fa fa-tasks\"></ae-icon>\n                </button>\n              </div>\n            </template>\n          </ae-popup>\n\n\n          <button v-if=\"locals.commandsSet.has('horizontal-rule')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('horizontal_rule') }\" @click.stop=\"locals.editor.commands.horizontal_rule\">\n            <ae-icon class=\"fa fa-horizontal-rule\"></ae-icon>\n          </button>\n\n          <button v-if=\"locals.commandsSet.has('quote-right')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('blockquote') }\" @click.stop=\"locals.editor.commands.blockquote\">\n            <ae-icon class=\"fa fa-quote-right\"></ae-icon>\n          </button>\n          <button v-if=\"locals.commandsSet.has('undo')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" @click.stop=\"locals.editor.commands.undo\">\n            <ae-icon class=\"fa fa-undo\"></ae-icon>\n          </button>\n          <button v-if=\"locals.commandsSet.has('redo')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" @click.stop=\"locals.editor.commands.redo\">\n            <ae-icon class=\"fa fa-redo\"></ae-icon>\n          </button>\n\n          <div v-if=\"locals.commandsSet.has('h1') || locals.commandsSet.has('h2') || locals.commandsSet.has('h3')\"\n                class=\"ae-button ae-button--small\"\n                :disabled=\"readonly\"\n                :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('heading_1') || locals.activeStatuses.includes('heading_2') || locals.activeStatuses.includes('heading_3') }\"\n                @click.stop=\"$refs.headers.toggle($event)\"\n          >\n            <ae-icon class=\"fa fa-heading\"></ae-icon>\n          </div>\n\n          <ae-popup ref=\"headers\">\n            <template #default=\"scope\">\n              <div class=\"flex gap-1\">\n                <button v-if=\"locals.commandsSet.has('h1')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('heading_1') }\" @click.stop=\"locals.editor.commands.heading({ level: 1 })\">\n                  <ae-icon class=\"fa fa-h1\"></ae-icon>\n                </button>\n                <button v-if=\"locals.commandsSet.has('h2')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('heading_2') }\" @click.stop=\"locals.editor.commands.heading({ level: 2 })\">\n                  <ae-icon class=\"fa fa-h2\"></ae-icon>\n                </button>\n                <button v-if=\"locals.commandsSet.has('h3')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('heading_3') }\" @click.stop=\"locals.editor.commands.heading({ level: 3 })\">\n                  <ae-icon class=\"fa fa-h3\"></ae-icon>\n                </button>\n              </div>\n            </template>\n          </ae-popup>\n\n          <button v-if=\"locals.commandsSet.has('code')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('code') }\" @click.stop=\"locals.editor.commands.code\">\n            <ae-icon class=\"fa fa-code\"></ae-icon>\n          </button>\n          <button v-if=\"locals.commandsSet.has('file-code')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.activeStatuses.includes('code_block') }\" @click.stop=\"locals.editor.commands.code_block\">\n            <ae-icon class=\"fa fa-file-code\"></ae-icon>\n          </button>\n          <button v-if=\"locals.commandsSet.has('smile')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" @click.stop=\"$refs.popupEmoji.show($event)\">\n            <ae-icon class=\"far fa-smile\"></ae-icon>\n          </button>\n\n          <ae-layout  layout-id=\"36649f27-5869-49e6-9b49-3b5c3b9c795d\"\n                      :param-settings=\"locals.refButtonEmployee\"\n                      :param-document=\"document\"\n                      @selected=\"controller.insertEmbed($event)\"\n                      :param-readonly=\"readonly\"\n                      v-if=\"locals.commandsSet.has('ae-ref-employee')\"\n                      param-icon=\"fa fa-at\"\n                      param-hide-path=\"true\" \n                      param-title=\"Mitarbeiter erwähnen\"\n          ></ae-layout>\n\n          <ae-layout  layout-id=\"36649f27-5869-49e6-9b49-3b5c3b9c795d\"\n                      :param-settings=\"locals.refButtonSettings\"\n                      :param-document=\"document\"\n                      @selected=\"controller.insertEmbed($event)\"\n                      :param-readonly=\"readonly\"\n                      v-if=\"locals.commandsSet.has('ae-ref')\"\n          ></ae-layout>\n        </div>\n      </editor-menu-bar>\n    </section>\n    <div class=\"border-x border-b rounded-b-md p-1\"\n        :xclass=\"{ '_CSS-editor__content--readonly': readonly, '_CSS-editor__content--borderless': params.withoutBorder }\"\n    >\n    <div v-if=\"!readonly && field.length <= 47\" class=\"_CSS-editor__content--empty\" :data-empty-text=\"params.placeholderText\"></div>\n      <editor-content class=\"_CSS-editor__content__component\"\n                      :xclass=\"{ '_CSS-editor__content--readonly': readonly, '_CSS-editor__content--borderless': params.withoutBorder }\"\n                      :editor=\"locals.editor\"\n                      v-if=\"!readonly || field.length > 47\"\n      ></editor-content>\n    </div>\n  </div>\n \n  <div style=\"display:none;\" :key=\"'readonly_' + readonly\" >\n    {{ controller.readonlyUpdated() }}\n  </div>\n\n  <ae-popup ref=\"popupLink\">\n    <div>\n      <input type=\"text\" v-model=\"locals.newLink\" v-focus>\n      <button class=\"ae-button ae-button--small ae-button--primary\" @click.stop=\"controller.insertLink()\">\n        <ae-icon class=\"fa fa-link\"></ae-icon>\n      </button>\n    </div>\n  </ae-popup>\n  <ae-popup ref=\"popupEmoji\">\n    <div class=\"_CSS-popup-emoji\">\n      <div style=\"margin-bottom:var(--ae-space-1);\">Emoji einfügen mit der Tastenkombination... </div>\n      <template v-if=\"Aeppic.navigator.userAgent.indexOf('Windows') >= 0\">\n        <ae-icon class=\"fab fa-windows\"></ae-icon> + Punkt (.)\n      </template>\n      <template v-else-if=\"Aeppic.navigator.userAgent.indexOf('Mac') >= 0\">\n        control + command + Leertaste\n      </template>\n      <template v-else>\n        Strg + Punkt (.)\n      </template>\n      \n    </div>\n  </ae-popup>\n</template>\n\n\n<div ref=\"domNode\" v-show=\"false\"></div>","css":"._CSS-editor__content__component > .ProseMirror p>code {\n  padding: var(--ae-size-2);\n  border-radius: var(--ae-border-radius);\n  font-size: var(--ae-font-size-3);\n  font-weight: 700;\n\n  background: rgba(0,0,0,.1);\n  color: rgba(0,0,0,.8);\n}\n\n._CSS-editor__content__component > .ProseMirror pre {\n  padding: var(--ae-size-2);\n  border-radius: var(--ae-border-radius);\n  font-size: var(--ae-font-size-3);\n  font-weight: 700;\n  overflow-x: auto;\n\n  background: #000;\n  color: #fff;\n}\n\n._CSS-editor__content__component > .ProseMirror, \n._CSS-editor__content__component > .ProseMirror p {\n  margin: 0;\n  padding: 0.2rem var(--ae-size-1);\n}\n\n._CSS-editor__content__component .ProseMirror-focused {\n  outline: none;\n}\n\n._CSS-editor__content__component .embed__ae-design {\n  display: inline-block;\n}\n\n._CSS-popup-emoji {\n  padding: var(--ae-space-2);\n  display: flex;\n  flex-flow: column;\n\n  align-items: center;\n}\n\n\n._CSS-editor__content__component > .ProseMirror ul > li:not([data-type=\"todo_item\"]) {\n  position: relative;\n  padding-left: 1.3em; \n}\n\n._CSS-editor__content__component > .ProseMirror ol:not([data-type=\"todo_item\"]) {\n  position: relative;\n  list-style-type: decimal;\n  xlist-style-position: inside;\n  padding-left: 1.3em;\n}\n\n/*\n._CSS-editor__content__component > .ProseMirror ol:not([data-type=\"todo_item\"]) > li::marker {\n  position: absolute;\n  left: 10px;\n}\n*/\n\n._CSS-editor__content__component > .ProseMirror ul > li:not([data-type=\"todo_item\"])::before { \n  font-family: \"Font Awesome 5 Pro\"; \n  content: \"\\f2f8\"; \n  position: absolute;\n  left: 0;\n  top: 4px;\n}\n \n._CSS-editor__content__component > .ProseMirror ul > li:not([data-type=\"todo_item\"])::before { \n  font-family: \"Font Awesome 5 Pro\"; \n  content: \"\\f2f8\"; \n  position: absolute;\n  left: 0;\n  top: 4px;\n}\n\n._CSS-editor__content__component > .ProseMirror ul[data-type=\"todo_list\"] {\n  padding-left: 0;\n}\n\n._CSS-editor__content__component > .ProseMirror li[data-type=\"todo_item\"] {\n  display: flex;\n  flex-direction: row;\n  align-items: baseline;\n}\n._CSS-editor__content__component > .ProseMirror .todo-checkbox {\n  border: 2px solid var(--ae-background-900);\n  height: 0.9em;\n  width: 0.9em;\n  box-sizing: border-box;\n  margin-right: 8px; \n  margin-top: 0.3rem;\n  user-select: none;\n  -webkit-user-select: none;\n  cursor: pointer;\n  border-radius: var(--ae-border-radius);\n  background-color: transparent;\n  transition: 0.4s background;\n}\n._CSS-editor__content__component > .ProseMirror .todo-content {\n  flex: 1;\n}\n._CSS-editor__content__component > .ProseMirror .todo-content > p:last-of-type {\n  margin-bottom: 0;\n}\n._CSS-editor__content__component > .ProseMirror .todo-content > ul[data-type=\"todo_list\"] {\n  margin: .5rem 0;\n}\n._CSS-editor__content__component > .ProseMirror li[data-done=\"true\"] > .todo-content > p {\n  text-decoration: line-through;\n}\n\n._CSS-editor__content__component > .ProseMirror li[data-done=\"true\"] > .todo-checkbox {\n  background-color: var(--ae-background-900);\n}\n\n._CSS-editor__content__component > .ProseMirror li[data-done=\"false\"] {\n  text-decoration: none;\n}\n\n._CSS-editor__content__component > .ProseMirror > h1 {\n  font-weight: 600;\n  font-size: 1.5rem; /* 24px */\n  line-height: 2rem; /* 32px */\n}\n\n._CSS-editor__content__component > .ProseMirror > h2 {\n  font-weight: 600;\n  font-size: 1.25rem; /* 20px */\n  line-height: 1.75rem; /* 28px */\n}\n\n._CSS-editor__content__component > .ProseMirror > h3 {\n  font-weight: 600;\n\tfont-size: 1.125rem; /* 18px */\n  line-height: 1.75rem; /* 28px */\n}\n\n._CSS-editor__content--empty {\n  pointer-events: none;\n  position: relative;\n}\n\n._CSS-editor__content--empty:before {\n  content: attr(data-empty-text);\n  float: left;\n  color: var(--ae-background-500);\n  pointer-events: none;\n  height: 0;\n  font-style: italic;\n  position: absolute;\n  left: 10px;\n  right: 10px;\n}\n\n","controller":"class Controller {\n  async init() {\n    this.FORMS = {\n      CoreDatei: 'dbee6e16-d63a-493c-bba6-505f79918f9f',\n      CoreOrdner: 'a04c8913-db48-4e80-b20d-0f3cc17a778c',\n      CoreAufgabe: '0deb4864-cbb6-420c-99ee-337c7444ab24',  \n      Kontakt: '6f641574-7cb6-424a-9657-4b8d652a2cf7',\n      Protokoll: '8dbe21a7-7642-4884-986b-a119c6498bd3',\n      KnowledgeSeite: 'd6f1cc99-f34e-4cb8-99b3-11f917485c25',\n      KnowledgeOrdner: '24523534-125d-4ebe-9a83-bd7fed39ff92',\n      Vertrag: '3d45f20b-ecb3-4dc5-aa39-67a28e3923d8',\n      Angebot: 'ea3fb00c-4b3e-44ed-8bfb-7dce7f8a8f4c',\n      Rechnung: 'a40633b4-c769-4457-a267-830738d4b549',\n    }\n\n    this.EMPTY_EDITOR_CONTENT_JSONSTRINGIFIED = '{\"type\":\"doc\",\"content\":[{\"type\":\"paragraph\"}]}'\n\n    this.pendingUpdateField = 0\n\n    locals.activeStatuses = []\n\n    locals.lastKnownEditorContentAsString = ''\n\n    locals.focus = false\n    locals.mouseover = false\n    locals.commands = params.commands || [\n      'bold',\n      'italic',\n      'strikethrough',\n      'underline',\n      'link',\n      'list-ul',\n      'list-ol',\n      'tasks',\n      'horizontal-rule',\n      'quote-right',\n      'undo',\n      'redo',\n      'h1',\n      'h2',\n      'h3',\n      'code',\n      'file-code',\n      'smile',\n      'ae-ref',\n      'ae-ref-employee'\n    ]\n    locals.commandsSet = new Set([...locals.commands])\n\n    locals.key = Aeppic.uuid()\n\n    locals.refButtonSettings = {\n      filter: {\n        ancestorForm: 'c69a0c1c-6c53-40c2-87e8-5ace866bf1df',\n        targetDocument: document,\n        formIds: Object.values(this.FORMS).join(',')\n      },\n      search: {\n        sort: [{\n          field: 'f.id',\n          descending: true\n        }, {\n          field: 'data.name'\n        }, {\n          field: 'created.at'\n        }]\n      }\n    }\n\n    locals.refButtonEmployee = {\n      filter: {\n        ancestorForm: 'c69a0c1c-6c53-40c2-87e8-5ace866bf1df',\n        targetDocument: document,\n        formIds: 'd2ac3563-3c3f-4f02-b923-9b67abf98d10'\n      },\n      search: {\n        sort: [ {\n          field: 'data.nachname'\n        }, {\n          field: 'data.vorname'\n        },\n        {\n          field: 'id'\n        }]\n      }\n    }\n\n    locals.fallbackToQuillEditor = false\n\n    locals.editor = null\n    locals.newLink = ''\n\n    await Aeppic.nextRenderPass()\n\n    locals.editor = this.createEditor()\n    if (!locals.editor) {\n      // fallback to quill\n      return\n    }\n\n    this._documentWatcher = Aeppic.watch(document, (newDocument, reason) => {\n      params.debug && console.log('watch', { newDocument, reason }, { oldV: document.v, newV: newDocument.v, loadedV: locals.loadedV})\n\n\n      if (!reason.updated) { \n        return\n      }\n\nconsole.log({ pendingUpdateField: this.pendingUpdateField })\n\n      // do not compare editor content with change if change was made by an internal call. \n      // NOTE: be aware that this might not always be true. An external watch might be mis-interpreted.\n      //       But that should not be a problem here, because the later change will be processed anyway.\n      if (this.pendingUpdateField > 0) {\n        this.pendingUpdateField -= 1\n        return\n      }\n\n      const newDocumentFieldValue = newDocument.data[fieldName]\n      const lengthChanged = locals.lastKnownEditorContentAsString.length !== newDocumentFieldValue.length\n\n      console.log({ lengthChanged })\n\n      const contentChanged = locals.lastKnownEditorContentAsString !== newDocumentFieldValue\n\n      console.log({ contentChanged })\n       \n      const contentChangedExternally = lengthChanged || contentChanged\n\n      params.debug && console.log('watch', { contentChangedExternally })\n\n      if (contentChangedExternally) {\n        const field = newDocumentFieldValue.startsWith('{') ? JSON.parse(newDocumentFieldValue) : newDocumentFieldValue\n\n        params.debug && console.log('.  watch', { fieldName, field, newDocumentFieldValue })\n        locals.editor.setContent(field)\n        locals.lastKnownEditorContentAsString = newDocumentFieldValue\n      } else {\n        params.debug && console.log('.    watch: NO need to change, Editor knows already the newst value')\n      }\n\n      // return\n\n      // const contentChanged = newDocument.previous && newDocument.previous.patch && newDocument.previous.patch[fieldName]\n      // if (contentChanged) {\n      //   const newDocumentFieldValue = newDocument.data[fieldName]\n      //   const field = newDocumentFieldValue.startsWith('{') ? JSON.parse(newDocumentFieldValue) : newDocumentFieldValue\n\n      //   params.debug && console.log('.  watch', { fieldName, field, newDocumentFieldValue })\n\n      //   const currentEditorContent = JSON.stringify(locals.editor.getJSON())\n      //   const editorModelHasAlreadyNewestContent = newDocumentFieldValue === currentEditorContent\n      //   params.debug && console.log('.   watch.did content change or is already set within component?', {currentEditorContent, field, editorModelHasAlreadyNewestContent })\n\n      //   if (!editorModelHasAlreadyNewestContent) {\n      //     params.debug && console.log(`.    watch: will update editor content with updated document.data from field ${fieldName}`)\n      //     locals.editor.setContent(field)\n      //   }\n      // }\n    })\n\n    watchParam('readonly', () => {\n      if (locals.editor) {\n        locals.editor.setOptions({\n          editable: !params.readonly,\n        })\n      }\n    })\n\n    $on('insert-reference', (value) => this.insertEmbed(value))\n\n    $on('smile', (event) => $refs.popupEmoji.show(event))\n\n\n    // does not update placeholder. bug in package?\n    // watchParam('placeholderText', (newValue) => {\n    //   if (locals.editor) {\n    //     console.log(this._getEditorExtensionsOptions({ placeholderText: newValue }))\n    //     locals.editor.setOptions({\n    //       extensions: this._getEditorExtensionsOptions({ placeholderText: newValue })\n    //     })\n    //   }\n    // })\n  }\n\n  _updateActiveStatuses() {\n    console.log('_updateActiveStatuses')\n    const active = []\n\n    if (locals.editor?.isActive.bold()) {\n      active.push('bold')\n    }\n    if (locals.editor?.isActive.italic()) {\n      active.push('italic')\n    }\n    if (locals.editor?.isActive.strike()) {\n      active.push('strike')\n    }\n    if (locals.editor?.isActive.underline()) {\n      active.push('underline')\n    }\n\n    if (locals.editor?.isActive.link()) {\n      active.push('link')\n    }\n    if (locals.editor?.isActive.bullet_list()) {\n      active.push('bullet_list')\n    }\n    if (locals.editor?.isActive.ordered_list()) {\n      active.push('ordered_list')\n    }\n    if (locals.editor?.isActive.todo_list()) {\n      active.push('todo_list')\n    }\n\n    if (locals.editor?.isActive.horizontal_rule()) {\n      active.push('horizontal_rule')\n    }\n    if (locals.editor?.isActive.blockquote()) {\n      active.push('blockquote')\n    }\n    if (locals.editor?.isActive.heading({ level: 1 })) {\n      active.push('heading_1')\n    }\n    if (locals.editor?.isActive.heading({ level: 2 })) {\n      active.push('heading_2')\n    }\n    if (locals.editor?.isActive.heading({ level: 3 })) {\n      active.push('heading_3')\n    }\n    if (locals.editor?.isActive.code()) {\n      active.push('code')\n    }\n    if (locals.editor?.isActive.code_block()) {\n      active.push('code_block')\n    }\n\n    locals.activeStatuses = active\n  }\n\n  createEditor() {\n    console.log('### createEditor')\n    const field = getField()\n    const content = field.startsWith('{') ? JSON.parse(field) : field\n\n    const hasQuillEditorContent = typeof content === 'object' && 'ops' in content\n    if (hasQuillEditorContent) {\n      locals.fallbackToQuillEditor = true\n      return\n    } \n\n    locals.lastKnownEditorContentAsString = field\n\n    this.debouncedUpdateField = Aeppic.debounce(() => this._updateField(), 2000) \n    this.debouncedUpdateEdiorActiveStatuses = Aeppic.debounce(() => this._updateActiveStatuses(), 250) \n    \n    let isFirstUpdate = true\n\n    locals.loadedV = document.v\n\n    const editor = packages.Editor({\n      disablePasteRules: ['italic', 'bold', 'strike', 'underline'],\n      content: content,\n      editable: !params.readonly,\n      onUpdate: (event) => {\n        console.log('onUpdate', {event, isFirstUpdate })\n\n        if (isFirstUpdate) {\n          isFirstUpdate = false\n          this._updateField()\n\n          return\n        }\n\n        this.debouncedUpdateField()\n      },\n      onFocus: () => { locals.focus = true },\n      onBlur: () => { locals.focus = false },\n      // onSelection: () => { this.debouncedUpdateEdiorActiveStatuses() }, // NOT SUPPORTED in tiptap v1\n    }, this._getEditorExtensionsOptions())\n\n    this.onSelectionChangeHandler = () => {\n      // calling editor.isActive directly on selectionChange is slow\n      // workaround by https://github.com/ueberdosis/tiptap/issues/1930\n      this.debouncedUpdateEdiorActiveStatuses()\n    }\n\n    this.onSelectionChangeListener = $refs.domNode.ownerDocument.addEventListener('selectionchange', this.onSelectionChangeHandler)\n\n \n    return editor\n  }\n\n  _getEditorExtensionsOptions(options) {\n    return {\n        Heading: { levels: [1, 2, 3] },\n        // FIXME: is not updateable by params. only static\n        // Placeholder: {\n        //   emptyEditorClass: 'is-editor-empty',\n        //   emptyNodeClass: 'is-empty',\n        //   emptyNodeText: options?.placeholderText || params.placeholderText || '',\n        //   showOnlyWhenEditable: true,\n        //   showOnlyCurrent: true\n        // }\n    }\n  }\n\n  _updateField(reason) {\n    console.log('_updateField:start')\n\n    if (!locals.editor || params.readonly) {\n      return\n    }\n\n    this.pendingUpdateField += 1\n\n    const contentJSON = locals.editor.getJSON()\n    const content = JSON.stringify(contentJSON)\n\n    locals.lastKnownEditorContentAsString = content\n\n    console.log('_updateField', { reason, fieldName, tailedContent: content.slice(-100) })\n\n    const emptifiedContent = content === this.EMPTY_EDITOR_CONTENT_JSONSTRINGIFIED ? '' : content\n\n    setField(emptifiedContent)\n\n    if (params.htmlTo) {\n      document.data[params.htmlTo] = locals.editor.getHTML()\n    }\n  }\n\n  _throttle(fn, threshhold, scope) {\n    threshhold || (threshhold = 250)\n    let last, deferTimer\n\n    return function () {\n      let context = scope || this\n\n      let now = +new Date,\n          args = arguments\n      if (last && now < last + threshhold) {\n        // hold on to it\n        clearTimeout(deferTimer)\n        deferTimer = setTimeout(function () {\n          last = now\n          fn.apply(context, args)\n        }, threshhold)\n      } else {\n        last = now\n        fn.apply(context, args)\n      }\n    }\n  }\n\n\n  showEmbed() {\n    locals.editor.commands.aeDesign({\n      documentId: 'root',\n      name: 'tiptap-embed'\n    })\n  }\n\n  insertEmbed(documentOrId) {\n    const id = typeof documentOrId === 'string' ? documentOrId : documentOrId.id\n\n    locals.editor.commands.aeDesign({\n      documentId: id,\n      name: 'tiptap-embed'\n    })\n  }\n\n\n  readonlyUpdated() {\n    const editable = !readonly\n\n    if (locals.editor && locals.editor.options.editable !== editable) {\n      locals.editor.setOptions({\n        editable\n      })\n    }\n  }\n  \n  showLinkCreator(clickEvent) {\n    locals.newLink = ''\n    $refs.popupLink.show(clickEvent)\n  }\n\n  insertLink() {\n    const link = locals.newLink\n    locals.editor.commands.link({ href: link })    \n    $refs.popupLink && $refs.popupLink.close()\n  }\n\n\n  destroy() {\n    if (this.debouncedUpdateField) {\n      clearTimeout(this.debouncedUpdateField)\n      this.debouncedUpdateField = null\n    }\n\n    if (this.debouncedUpdateEdiorActiveStatuses) {\n      clearTimeout(this.debouncedUpdateEdiorActiveStatuses)\n      this.debouncedUpdateEdiorActiveStatuses = null\n    }\n\n    if (locals.editor) {\n      locals.editor.destroy()\n      locals.editor = null\n    }\n\n    if (this._documentWatcher) {\n      this._documentWatcher.stop()\n      this._documentWatcher = null\n    }\n\n    if ($refs.domNode) {\n      $refs.domNode.ownerDocument?.removeEventListener('selectionchange', this.onSelectionChangeHandler)\n    }\n  }\n}\n ","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"57774487-ec32-4326-a635-df4d878e5611","v":"a10f6c098b5103c4","text":"tiptap Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2022-08-23T14:33:25+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-11-28T10:30:25+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936835,"ct":1721209936835,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"04c8877c48f4627e","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"b0c9239f-c561-4063-9556-21292ea6271a","v":"0b243fe0a4a224ce","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"editor","template":"<!-- {{document.data.beschreibung}}\n<hr>\n{{field}} -->\n\n<ae-control v-if=\"locals.useTiptapEditorVersionTwo\" name=\"rd:editor\" :document=\"document\" :field-name=\"fieldName\" :params=\"params\" v-on=\"$listeners\" :readonly=\"readonly\"></ae-control>\n<ae-control v-else-if=\"locals.fallbackToQuillEditor\" name=\"editor-quill\" :document=\"document\" :field-name=\"fieldName\" :params=\"params\" v-on=\"$listeners\" :readonly=\"readonly\"></ae-control>\n<template v-else>\n  <div class=\"_CSS-editor\" :class=\"{ '_CSS-editor--readonly': readonly }\" :key=\"locals.key\">\n    <editor-menu-bar v-if=\"!readonly && !params.withoutMenu\" class=\"_CSS-editor__menu\" :editor=\"locals.editor\" v-slot=\"scope\">\n      <div class=\"_CSS-menu__bar\">\n        <button v-if=\"locals.commandsSet.has('bold')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.bold() }\" @click.stop=\"locals.editor.commands.bold()\">\n          <ae-icon class=\"fa fa-bold\"></ae-icon> \n        </button>   \n\n        <button v-if=\"locals.commandsSet.has('italic')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.italic() }\" @click.stop=\"locals.editor.commands.italic\">\n          <ae-icon class=\"fa fa-italic\"></ae-icon>\n        </button>\n\n        <button v-if=\"locals.commandsSet.has('strikethrough')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.strike() }\" @click.stop=\"locals.editor.commands.strike\">\n          <ae-icon class=\"fa fa-strikethrough\"></ae-icon>\n        </button>\n\n        <button v-if=\"locals.commandsSet.has('underline')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.underline() }\" @click.stop=\"locals.editor.commands.underline\">\n          <ae-icon class=\"fa fa-underline\"></ae-icon>\n        </button>\n\n        <button v-if=\"locals.commandsSet.has('link')\" class=\"ae-button ae-button--small\" :disabled=\"readonly || locals.editor.isActive.link()\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.link() }\" @click.stop=\"controller.showLinkCreator($event)\">\n          <ae-icon class=\"fa fa-link\"></ae-icon>\n        </button>\n\n        <button v-if=\"locals.commandsSet.has('list-ul')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.bullet_list() }\" @click.stop=\"locals.editor.commands.bullet_list\">\n          <ae-icon class=\"fa fa-list-ul\"></ae-icon>\n        </button>\n        <button v-if=\"locals.commandsSet.has('list-ol')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.ordered_list() }\" @click.stop=\"locals.editor.commands.ordered_list\">\n          <ae-icon class=\"fa fa-list-ol\"></ae-icon>\n        </button>\n        <button v-if=\"locals.commandsSet.has('tasks')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.todo_list() }\" @click.stop=\"locals.editor.commands.todo_list\">\n          <ae-icon class=\"fa fa-tasks\"></ae-icon>\n        </button>\n        <button v-if=\"locals.commandsSet.has('horizontal-rule')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.horizontal_rule() }\" @click.stop=\"locals.editor.commands.horizontal_rule\">\n          <ae-icon class=\"fa fa-horizontal-rule\"></ae-icon>\n        </button>\n\n        <button v-if=\"locals.commandsSet.has('quote-right')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.blockquote() }\" @click.stop=\"locals.editor.commands.blockquote\">\n          <ae-icon class=\"fa fa-quote-right\"></ae-icon>\n        </button>\n        <button v-if=\"locals.commandsSet.has('undo')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" @click.stop=\"locals.editor.commands.undo\">\n          <ae-icon class=\"fa fa-undo\"></ae-icon>\n        </button>\n        <button v-if=\"locals.commandsSet.has('redo')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" @click.stop=\"locals.editor.commands.redo\">\n          <ae-icon class=\"fa fa-redo\"></ae-icon>\n        </button>\n        <button v-if=\"locals.commandsSet.has('h1')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.heading({ level: 1 }) }\" @click.stop=\"locals.editor.commands.heading({ level: 1 })\">\n          <ae-icon class=\"fa fa-h1\"></ae-icon>\n        </button>\n        <button v-if=\"locals.commandsSet.has('h2')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.heading({ level: 2 }) }\" @click.stop=\"locals.editor.commands.heading({ level: 2 })\">\n          <ae-icon class=\"fa fa-h2\"></ae-icon>\n        </button>\n        <button v-if=\"locals.commandsSet.has('h3')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.heading({ level: 3 }) }\" @click.stop=\"locals.editor.commands.heading({ level: 3 })\">\n          <ae-icon class=\"fa fa-h3\"></ae-icon>\n        </button>\n        <button v-if=\"locals.commandsSet.has('code')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.code() }\" @click.stop=\"locals.editor.commands.code\">\n          <ae-icon class=\"fa fa-code\"></ae-icon>\n        </button>\n        <button v-if=\"locals.commandsSet.has('file-code')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" :class=\"{ 'ae-button--secondary': locals.editor.isActive.code_block() }\" @click.stop=\"locals.editor.commands.code_block\">\n          <ae-icon class=\"fa fa-file-code\"></ae-icon>\n        </button>\n        <button v-if=\"locals.commandsSet.has('smile')\" class=\"ae-button ae-button--small\" :disabled=\"readonly\" @click.stop=\"$refs.popupEmoji.show($event)\">\n          <ae-icon class=\"far fa-smile\"></ae-icon>\n        </button>\n\n        <ae-layout  layout-id=\"36649f27-5869-49e6-9b49-3b5c3b9c795d\"\n                    :param-settings=\"locals.refButtonEmployee\"\n                    :param-document=\"document\"\n                    @selected=\"controller.insertEmbed($event)\"\n                    :param-readonly=\"readonly\"\n                    v-if=\"locals.commandsSet.has('ae-ref-employee')\"\n                    param-icon=\"fa fa-at\"\n                    param-hide-path=\"true\" \n                    param-title=\"Mitarbeiter erwähnen\"\n        ></ae-layout>\n\n        <ae-layout  layout-id=\"36649f27-5869-49e6-9b49-3b5c3b9c795d\"\n                    :param-settings=\"locals.refButtonSettings\"\n                    :param-document=\"document\"\n                    @selected=\"controller.insertEmbed($event)\"\n                    :param-readonly=\"readonly\"\n                    v-if=\"locals.commandsSet.has('ae-ref')\"\n        ></ae-layout>\n      </div>\n    </editor-menu-bar>\n    <div class=\"_CSS-editor__content\"\n        :class=\"{ '_CSS-editor__content--readonly': readonly, '_CSS-editor__content--borderless': params.withoutBorder }\"\n    >\n    <div v-if=\"!readonly && field.length <= 47\" class=\"_CSS-editor__content--empty\" :data-empty-text=\"params.placeholderText\"></div>\n    <editor-content class=\"_CSS-editor__content__component\"\n                    xclass=\"{ '_CSS-editor__content--readonly': readonly, '_CSS-editor__content--borderless': params.withoutBorder }\"\n                    :editor=\"locals.editor\"\n                    v-if=\"!readonly || field.length > 47\"\n    ></editor-content>\n    </div>\n  </div>\n \n  <div style=\"display:none;\" :key=\"'readonly_' + readonly\" >\n    {{ controller.readonlyUpdated() }}\n  </div>\n\n  <ae-popup ref=\"popupLink\">\n    <div>\n      <input type=\"text\" v-model=\"locals.newLink\" v-focus>\n      <button class=\"ae-button ae-button--small ae-button--primary\" @click.stop=\"controller.insertLink()\">\n        <ae-icon class=\"fa fa-link\"></ae-icon>\n      </button>\n    </div>\n  </ae-popup>\n  <ae-popup ref=\"popupEmoji\">\n    <div class=\"_CSS-popup-emoji\">\n      <div style=\"margin-bottom:var(--ae-space-1);\">Emoji einfügen mit der Tastenkombination... </div>\n      <template v-if=\"Aeppic.navigator.userAgent.indexOf('Windows') >= 0\">\n        <ae-icon class=\"fab fa-windows\"></ae-icon> + Punkt (.)\n      </template>\n      <template v-else-if=\"Aeppic.navigator.userAgent.indexOf('Mac') >= 0\">\n        control + command + Leertaste\n      </template>\n      <template v-else>\n        Strg + Punkt (.)\n      </template>\n      \n    </div>\n  </ae-popup>\n</template>\n","css":"._CSS-editor {\n  min-height: var(--bp-input-content-min-height);\n}\n\n._CSS-editor__menu {\n  background: var(--ae-ui-background);\n\n  border: var(--ae-border-color) solid var(--ae-border-width);\n  border-width: var(--ae-border-width) var(--ae-border-width) 0 var(--ae-border-width) ;\n  border-radius: var(--ae-border-radius) var(--ae-border-radius) 0 0;\n}\n\n._CSS-menu__bar {\n  display: flex;\n  align-items: center;\n  flex-flow: row wrap;\n}\n\n._CSS-editor__content {\n  padding: var(--bp-input-content-padding);\n  background: var(--ae-ui-background); \n  border: var(--ae-border-color) solid var(--ae-border-width);\n  border-width: 0 var(--ae-border-width) var(--ae-border-width) var(--ae-border-width) ;\n\n  border-radius: 0 0 var(--ae-border-radius) var(--ae-border-radius);\n}\n\n._CSS-editor__content--borderless {\n  border: none !important;\n}\n \n._CSS-editor__content._CSS-editor__content--readonly {\n  border-width: var(--ae-border-width);\n  border-radius: var(--ae-border-radius);\n}\n\n._CSS-editor__content p>code {\n  padding: var(--ae-size-2);\n  border-radius: var(--ae-border-radius);\n  font-size: var(--ae-font-size-3);\n  font-weight: 700;\n\n  background: rgba(0,0,0,.1);\n  color: rgba(0,0,0,.8);\n}\n\n._CSS-editor__content pre {\n  padding: var(--ae-size-2);\n  border-radius: var(--ae-border-radius);\n  font-size: var(--ae-font-size-3);\n  font-weight: 700;\n  overflow-x: auto;\n\n  background: #000;\n  color: #fff;\n}\n\n._CSS-editor__content__component > .ProseMirror p {\n  margin: 0;\n  padding: 0.2rem var(--ae-size-1);\n}\n\n._CSS-editor .ProseMirror-focused {\n  outline: none;\n}\n\n._CSS-editor .embed__ae-design {\n  display: inline-block;\n}\n\n._CSS-popup-emoji {\n  padding: var(--ae-space-2);\n  display: flex;\n  flex-flow: column;\n\n  align-items: center;\n}\n\n._CSS-editor__content__component > .ProseMirror ul[data-type=\"todo_list\"] {\n  padding-left: 0;\n}\n._CSS-editor__content__component > .ProseMirror li[data-type=\"todo_item\"] {\n  display: flex;\n  flex-direction: row;\n  align-items: baseline;\n}\n._CSS-editor__content__component > .ProseMirror .todo-checkbox {\n  border: 2px solid var(--ae-background-900);\n  height: 0.9em;\n  width: 0.9em;\n  box-sizing: border-box;\n  margin-right: 10px;\n  margin-top: 0.3rem;\n  user-select: none;\n  -webkit-user-select: none;\n  cursor: pointer;\n  border-radius: var(--ae-border-radius);\n  background-color: transparent;\n  transition: 0.4s background;\n}\n._CSS-editor__content__component > .ProseMirror .todo-content {\n  flex: 1;\n}\n._CSS-editor__content__component > .ProseMirror .todo-content > p:last-of-type {\n  margin-bottom: 0;\n}\n._CSS-editor__content__component > .ProseMirror .todo-content > ul[data-type=\"todo_list\"] {\n  margin: .5rem 0;\n}\n._CSS-editor__content__component > .ProseMirror li[data-done=\"true\"] > .todo-content > p {\n  text-decoration: line-through;\n}\n\n._CSS-editor__content__component > .ProseMirror li[data-done=\"true\"] > .todo-checkbox {\n  background-color: var(--ae-background-900);\n}\n\n._CSS-editor__content__component > .ProseMirror li[data-done=\"false\"] {\n  text-decoration: none;\n}\n\n\n._CSS-editor__content--empty {\n  pointer-events: none;\n  position: relative;\n}\n\n._CSS-editor__content--empty:before {\n  content: attr(data-empty-text);\n  float: left;\n  color: var(--ae-background-500);\n  pointer-events: none;\n  height: 0;\n  font-style: italic;\n  position: absolute;\n  left: 10px;\n  right: 10px;\n}\n\n","controller":"class Controller {\n  init() {\n    this.FORMS = {\n      CoreDatei: 'dbee6e16-d63a-493c-bba6-505f79918f9f',\n      CoreOrdner: 'a04c8913-db48-4e80-b20d-0f3cc17a778c',\n      CoreAufgabe: '0deb4864-cbb6-420c-99ee-337c7444ab24',  \n      Kontakt: '6f641574-7cb6-424a-9657-4b8d652a2cf7',\n      Protokoll: '8dbe21a7-7642-4884-986b-a119c6498bd3',\n      KnowledgeSeite: 'd6f1cc99-f34e-4cb8-99b3-11f917485c25',\n      KnowledgeOrdner: '24523534-125d-4ebe-9a83-bd7fed39ff92',\n      Vertrag: '3d45f20b-ecb3-4dc5-aa39-67a28e3923d8',\n      Angebot: 'ea3fb00c-4b3e-44ed-8bfb-7dce7f8a8f4c',\n      Rechnung: 'a40633b4-c769-4457-a267-830738d4b549',\n    }\n\n    this.EMPTY_EDITOR_CONTENT_JSONSTRINGIFIED = '{\"type\":\"doc\",\"content\":[{\"type\":\"paragraph\"}]}'\n\n    locals.useTiptapEditorVersionTwo = false\n    locals.commands = params.commands || [\n      'bold',\n      'italic',\n      'strikethrough',\n      'underline',\n      'link',\n      'list-ul',\n      'list-ol',\n      'tasks',\n      'horizontal-rule',\n      'quote-right',\n      'undo',\n      'redo',\n      'h1',\n      'h2',\n      'h3',\n      'code',\n      'file-code',\n      'smile',\n      'ae-ref',\n      'ae-ref-employee'\n    ]\n    locals.commandsSet = new Set([...locals.commands])\n\n    locals.key = Aeppic.uuid()\n\n    locals.refButtonSettings = {\n      filter: {\n        ancestorForm: 'c69a0c1c-6c53-40c2-87e8-5ace866bf1df',\n        targetDocument: document,\n        formIds: Object.values(this.FORMS).join(',')\n      },\n      search: {\n        sort: [{\n          field: 'f.id',\n          descending: true\n        }, {\n          field: 'data.name'\n        }, {\n          field: 'created.at'\n        }]\n      }\n    }\n\n    locals.refButtonEmployee = {\n      filter: {\n        ancestorForm: 'c69a0c1c-6c53-40c2-87e8-5ace866bf1df',\n        targetDocument: document,\n        formIds: 'd2ac3563-3c3f-4f02-b923-9b67abf98d10'\n      },\n      search: {\n        sort: [ {\n          field: 'data.nachname'\n        }, {\n          field: 'data.vorname'\n        },\n        {\n          field: 'id'\n        }]\n      }\n    }\n\n    locals.fallbackToQuillEditor = false\n\n    locals.editor = null\n    locals.newLink = ''\n\n    locals.editor = this.createEditor()\n    if (!locals.editor) {\n      // fallback to quill\n      return\n    }\n\n\n    Aeppic.watch(document, (newDocument, reason) => {\n      params.debug && console.log('watch', newDocument, reason)\n\n      if (!reason.updated) {\n        return\n      }\n\n      const contentChanged = newDocument.previous && newDocument.previous.patch && newDocument.previous.patch[fieldName]\n      if (contentChanged) {\n        const newDocumentFieldValue = newDocument.data[fieldName]\n        const field = newDocumentFieldValue.startsWith('{') ? JSON.parse(newDocumentFieldValue) : newDocumentFieldValue\n\n        params.debug && console.log('.  watch', fieldName, field, newDocumentFieldValue)\n\n        const currentEditorContent = JSON.stringify(locals.editor.getJSON())\n        const editorModelHasAlreadyNewestContent = newDocumentFieldValue === currentEditorContent\n        params.debug && console.log('.   watch.did content change or is already set within component?', currentEditorContent, field, editorModelHasAlreadyNewestContent)\n\n        if (!editorModelHasAlreadyNewestContent) {\n          params.debug && console.log(`.    watch: will update editor content with updated document.data from field ${fieldName}`)\n          locals.editor.setContent(field)\n        }\n      }\n    })\n\n    watchParam('readonly', () => {\n      if (locals.editor) {\n        locals.editor.setOptions({\n          editable: !params.readonly,\n        })\n      }\n    })\n\n    $on('insert-reference', (value) => this.insertEmbed(value))\n\n    $on('smile', (event) => $refs.popupEmoji.show(event))\n\n\n    // does not update placeholder. bug in package?\n    // watchParam('placeholderText', (newValue) => {\n    //   if (locals.editor) {\n    //     console.log(this._getEditorExtensionsOptions({ placeholderText: newValue }))\n    //     locals.editor.setOptions({\n    //       extensions: this._getEditorExtensionsOptions({ placeholderText: newValue })\n    //     })\n    //   }\n    // })\n  }\n\n  createEditor() {\n    const field = getField()\n    let content = field.startsWith('{') ? JSON.parse(field) : field\n\n    const hasQuillEditorContent = typeof content === 'object' && 'ops' in content\n    if (hasQuillEditorContent) {\n      locals.fallbackToQuillEditor = true\n      return\n    }\n\n    if (content.tiptapVersion === '2.x') {\n      locals.useTiptapEditorVersionTwo = true\n      return\n    }\n\n    this.debouncedUpdateField = this._throttle(() => this._updateField(), 250)\n    \n    let isFirstUpdate = true\n\n    const editor = packages.Editor({\n      disablePasteRules: ['italic', 'bold', 'strike', 'underline'],\n      content: content,\n      editable: !params.readonly,\n      onUpdate: (event) => {\n        // console.log('onUpdate', event)\n\n        if (isFirstUpdate) {\n          isFirstUpdate = false\n          this._updateField()\n\n          return\n        }\n\n        this.debouncedUpdateField()\n      }\n    }, this._getEditorExtensionsOptions())\n\n\n    return editor\n  }\n\n  _getEditorExtensionsOptions(options) {\n    return {\n        Heading: { levels: [1, 2, 3] },\n        // FIXME: is not updateable by params. only static\n        // Placeholder: {\n        //   emptyEditorClass: 'is-editor-empty',\n        //   emptyNodeClass: 'is-empty',\n        //   emptyNodeText: options?.placeholderText || params.placeholderText || '',\n        //   showOnlyWhenEditable: true,\n        //   showOnlyCurrent: true\n        // }\n    }\n  }\n\n  _updateField(reason) {\n    if (!locals.editor || params.readonly) {\n      return\n    }\n\n    const contentJSON = locals.editor.getJSON()\n    const content = JSON.stringify(contentJSON)\n\n    // console.log('_updateField', reason, fieldName, contentJSON)\n\n    const emptifiedContent = content === this.EMPTY_EDITOR_CONTENT_JSONSTRINGIFIED ? '' : content\n\n    setField(emptifiedContent)\n\n    if (params.htmlTo) {\n      document.data[params.htmlTo] = locals.editor.getHTML()\n    }\n  }\n\n  _throttle(fn, threshhold, scope) {\n    threshhold || (threshhold = 250)\n    let last, deferTimer\n\n    return function () {\n      let context = scope || this\n\n      let now = +new Date,\n          args = arguments\n      if (last && now < last + threshhold) {\n        // hold on to it\n        clearTimeout(deferTimer)\n        deferTimer = setTimeout(function () {\n          last = now\n          fn.apply(context, args)\n        }, threshhold)\n      } else {\n        last = now\n        fn.apply(context, args)\n      }\n    }\n  }\n\n\n  showEmbed() {\n    locals.editor.commands.aeDesign({\n      documentId: 'root',\n      name: 'tiptap-embed'\n    })\n  }\n\n  insertEmbed(documentOrId) {\n    const id = typeof documentOrId === 'string' ? documentOrId : documentOrId.id\n\n    locals.editor.commands.aeDesign({\n      documentId: id,\n      name: 'tiptap-embed'\n    })\n  }\n\n  destroy() {\n    if (this.debouncedUpdateField) {\n      clearInterval(this.debouncedUpdateField)\n    }\n\n    if (locals.editor) {\n      locals.editor.destroy()\n      locals.editor = null\n    }\n  }\n\n  readonlyUpdated() {\n    const editable = !readonly\n\n    if (locals.editor && locals.editor.options.editable !== editable) {\n      locals.editor.setOptions({\n        editable\n      })\n    }\n  }\n  \n  showLinkCreator(clickEvent) {\n    locals.newLink = ''\n    $refs.popupLink.show(clickEvent)\n  }\n\n  insertLink() {\n    const link = locals.newLink\n    locals.editor.commands.link({ href: link })    \n    $refs.popupLink && $refs.popupLink.close()\n  }\n}\n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"57774487-ec32-4326-a635-df4d878e5611","v":"a10f6c098b5103c4","text":"tiptap Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-05-08T09:00:42+00:00","by":"47325375-27fb-4303-8bf5-9438a0524219","from":"","client":"server"},"modified":{"at":"2023-02-16T16:02:01+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936835,"ct":1721209936835,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"ab94caf55524859c","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"b6a55445-4257-43e2-a66f-c71d26d3bae2","v":"58f81d0365a1b932","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"editor-comment","template":"<ae-control ref=\"control\" v-if=\"locals.fallbackToQuillEditor\" name=\"editor-quill-comment\" :document=\"document\" :field-name=\"fieldName\" :params=\"params\" :readonly=\"readonly\"></ae-control>\n<ae-control ref=\"control\" v-else name=\"editor\" :document=\"document\" :field-name=\"fieldName\" :params=\"params\" :readonly=\"readonly\" :param-commands=\"['smile', 'ae-ref-employee', 'ae-ref']\"></ae-control>\n","css":"._CSS {\n  width: 100%;\n}\n","controller":"init() {\n  locals.fallbackToQuillEditor = getField().startsWith('{\"ops\":')\n\n  const events = ['insert-reference', 'smile']\n\n  for (const key of events) {\n    $on(key, (value) => $refs.control.$emit(key, value))\n  }\n}  \n        ","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-11-18T15:48:21+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-07-05T14:45:21+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936836,"ct":1721209936836,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"39246d8cc45dcda5","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"eb659345-693a-4cc1-8bbf-615a263e9f5c","v":"15bcebb9a9c0893f","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"editor-comment","template":"<ae-control ref=\"control\" v-if=\"locals.fallbackToQuillEditor\" name=\"editor-quill-comment\" :document=\"document\" :field-name=\"fieldName\" :params=\"params\" :readonly=\"readonly\"></ae-control>\n<ae-control ref=\"control\" \n            v-else\n            name=\"rd:editor\" \n            :document=\"document\" \n            :field-name=\"fieldName\" \n            :params=\"params\" \n            :readonly=\"readonly\"\n            @save=\"$emit('save')\"\n></ae-control>\n","css":"._CSS {\n  width: 100%;\n}\n","controller":"init() {\n  locals.fallbackToQuillEditor = getField().startsWith('{\"ops\":')\n\n  const events = ['insert-reference', 'smile', 'save-now']\n\n  for (const key of events) {\n    $on(key, (value) => $refs.control.$emit(key, value))\n  }\n}  \n        ","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2023-03-22T14:48:57+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2023-12-13T13:00:27+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936836,"ct":1721209936836,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"25f5bca2f595bf85","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-05-18T12:11:18+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936836,"data":{"cardinality":"BOTH","controller":"class Controller {\n  init() {\n    locals.rerender = Aeppic.uuid()\n    locals.fallbackToQuillEditor = false\n    locals.editor = null\n\n    locals.editor = this.createEditor()\n  }\n\n  createEditor() {\n    if (locals.editor) {\n      locals.editor.destroy()\n      locals.editor = null\n      locals.rerender = Aeppic.uuid()\n    }\n\n    const field = getField()\n    const content = field.startsWith('{') ? JSON.parse(field) : field\n\n    const hasQuillEditorContent = typeof content === 'object' && 'ops' in content\n    if (hasQuillEditorContent) {\n      locals.fallbackToQuillEditor = true\n      return\n    }\n\n    const { Editor, AllExtensions } = packages.Editor\n\n    const extensions = [ \n        AllExtensions.configure({ \n          table: {\n            resizable: true\n          },\n          aeDesign: {\n            defaultParams: {\n              editorBoundary: 'window',\n            }\n          }, \n          aeDesignInline: {\n            defaultParams: {\n              editorBoundary: 'window',\n            }\n          },\n        }),\n      ]\n\n    const editor = new Editor({\n      extensions,\n      content: content,\n      editable: false,\n    })\n\n    return editor\n  }\n\n  destroy() {\n    locals.editor?.destroy()\n    locals.editor = null\n    locals.rerender = Aeppic.uuid()\n  }\n} ","css":" ._CSS-editor__content__component > .ProseMirror > table {\n  border: 1px solid black;\n}\n\n._CSS-editor__content__component > .ProseMirror > table tr {\n  border: 1px solid black;\n}\n\n._CSS-editor__content__component > .ProseMirror table td {\n  border: 1px solid black;\n  /* FIXME: only mark manual tables, not pasted content */\n  /* border: 1px solid transparent; */\n}\n","description":"","device":"ALL","label":"","limitTypes":false,"name":"editor-content","namespace":"rd","packages":[{"id":"31da2761-bf6b-40e8-af3c-7bbbbb473058","text":"Editor - TipTap2 ","v":"9b3e053fe0317402"}],"parameters":[],"standalone":"FIELD","template":"<!-- FIXME: without the key, the editor-content MIGHT not be correctly destroyed on updates. This will lead to multiple instances of editor-content within this control-container -->\n<div :key=\"locals.rerender\">\n  <editor-content v-if=\"locals.editor && field && (field[0] === '{' ? field.length > 72 : field.length > 0 )\"\n    :editor=\"locals.editor\"\n    class=\"_CSS-editor__content__component\"\n    ></editor-content>\n</div>","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"517238f4-9c07-4699-b53b-15ec3679da8e","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-12-23T09:58:20+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"ff0e96a581f6e81c"},"stamps":[],"t":1766483900189,"v":"668c1d6b8bfe950f"},{"id":"da45e41e-4309-47cd-8a50-30a24f296a6e","v":"e1b84ef389f7ead4","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"editor-light","template":"<ae-control v-if=\"locals.fallbackToQuillEditor\" name=\"editor-quill-light\" :document=\"document\" :field-name=\"fieldName\" :params=\"params\" v-on=\"$listeners\" :readonly=\"readonly\"></ae-control>\n<ae-control v-else name=\"editor\" :document=\"document\" :field-name=\"fieldName\" :params=\"params\" v-on=\"$listeners\" :readonly=\"readonly\"></ae-control>\n","css":"","controller":"init() {\n  locals.fallbackToQuillEditor = getField().startsWith('{\"ops\":')\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-11-18T15:40:36+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2020-11-18T15:46:37+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936836,"ct":1721209936836,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"a44e7c742136b36d","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"00620ada-3264-4272-8122-1c3316729eac","v":"5d742ef6adaeae23","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"editor-quill","template":" <quill-editor\n  v-if=\"params.noExpand\"\n  @paste.native=\"controller.paste($event)\"\n  @drop.native.stop.prevent=\"controller.drop($event)\"\n  ref=\"editor\"\n  class=\"_CSS-editor\"\n  :class=\"{ '_CSS-editor--writeable': !(params.readonly != null ? params.readonly : readonly) }\"\n  :style=\"{ 'max-height': params.maxHeight || '60vh' }\" \n  v-model=\"field\"\n  :readonly=\"params.readonly != null ? params.readonly: readonly\"\n  :input-format=\"params.inputFormat || 'native'\"\n  :output-format=\"params.outputFormat || 'native'\"\n  :open-dialog=\"() => controller.openDialog()\"\n  @html=\"controller.onHtml($event)\"\n  vue-component-names=\"ae-design\"\n  :vue-component-creator=\"(...args) => Aeppic.createVueComponentElement(...args)\"\n  :completion-keys-and-components=\"{'@':{ name:'ae-layout',props:{ layoutId: '238d8927-fc51-4cf5-922f-0830fccc3292', params: { parentEditableDocument: document, ancestorSearchLimit: params.ancestorSearchLimit, searchFormId: params.searchFormId, hidePath: params.hidePath } }},'/':{ name:'ae-layout', props:{ layoutId: '238d8927-fc51-4cf5-922f-0830fccc3292', params: { parentEditableDocument: document, ancestorSearchLimit: params.ancestorSearchLimit, searchFormId: params.searchFormId, hidePath: params.hidePath } }} }\"\n  :embed-handler=\"{ detach: (detachParams) => controller.embedDetach(detachParams), create: (createParams) => controller.embedCreate(createParams) }\">\n   <div slot=\"toolbar\">\n         <span class=\"ql-formats\">\n          <select class=\"ql-font\"></select>\n          <select class=\"ql-size\"></select>\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-bold\"></button>\n          <button class=\"ql-italic\"></button>\n          <button class=\"ql-underline\"></button>\n          <button class=\"ql-strike\"></button>\n        </span>\n        <span class=\"ql-formats\">\n          <select class=\"ql-color\"></select>\n          <select class=\"ql-background\"></select>\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-emoji\">\n          </button>\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-script\" value=\"sub\"></button>\n          <button class=\"ql-script\" value=\"super\"></button>\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-header\" value=\"1\"></button>\n          <button class=\"ql-header\" value=\"2\"></button>\n          <button class=\"ql-blockquote\"></button>\n          <!-- // TODO: include 'syntax' module to make use of: <button class=\"ql-code-block\"></button>-->\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-list\" value=\"ordered\"></button>\n          <button class=\"ql-list\" value=\"bullet\"></button>\n          <button class=\"ql-indent\" value=\"-1\"></button>\n          <button class=\"ql-indent\" value=\"+1\"></button>\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-direction\" value=\"rtl\"></button>\n          <select class=\"ql-align\"></select>\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-link\"></button>\n          <!--<button class=\"ql-image\"></button>-->\n          <!--<button class=\"ql-video\"></button>-->\n          <!-- // TODO: include 'formula' module to make use of: <button class=\"ql-formula\"></button>-->\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-clean\"></button>\n        </span>\n        <!-- <span class=\"ql-formats\" v-show=\"openDialog\"> -->\n        <span class=\"ql-formats\">\n          <button class=\"ql-open-dialog\" @click=\"$refs.input.click()\">\n            <ae-icon class=\"fa fa-paperclip\"></ae-icon>\n          </button>\n        </span>\n    </div>\n</quill-editor>\n<ae-expandable v-else style=\"display:flex;flex-flow:column;height:100%;\">  \n  <quill-editor\n    @paste.native=\"controller.paste($event)\"\n    @drop.native.stop.prevent=\"controller.drop($event)\"\n    ref=\"editor\"\n    class=\"_CSS-editor\"\n    :class=\"{ '_CSS-editor--writeable': !(params.readonly != null ? params.readonly : readonly) }\"\n    :style=\"{ 'max-height': params.maxHeight || null }\"  \n    v-model=\"field\"\n    :readonly=\"params.readonly != null ? params.readonly: readonly\"\n    :input-format=\"params.inputFormat || 'native'\"\n    :output-format=\"params.outputFormat || 'native'\"\n    :open-dialog=\"() => controller.openDialog()\"\n    @html=\"controller.onHtml($event)\"\n    vue-component-names=\"ae-design\"\n    :vue-component-creator=\"(...args) => Aeppic.createVueComponentElement(...args)\"\n    :completion-keys-and-components=\"{'@':{ name:'ae-layout',props:{ layoutId: '238d8927-fc51-4cf5-922f-0830fccc3292', params: { parentEditableDocument: document, ancestorSearchLimit: params.ancestorSearchLimit, searchFormId: params.searchFormId, hidePath: params.hidePath } }},'/':{ name:'ae-layout', props:{ layoutId: '238d8927-fc51-4cf5-922f-0830fccc3292', params: { parentEditableDocument: document, ancestorSearchLimit: params.ancestorSearchLimit, searchFormId: params.searchFormId, hidePath: params.hidePath } }} }\"\n    :embed-handler=\"{ detach: (detachParams) => controller.embedDetach(detachParams), create: (createParams) => controller.embedCreate(createParams) }\">\n    \n    <div slot=\"toolbar\">\n    \n         <span class=\"ql-formats\">\n          <select class=\"ql-font\"></select>\n          <select class=\"ql-size\"></select>\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-bold\"></button>\n          <button class=\"ql-italic\"></button>\n          <button class=\"ql-underline\"></button>\n          <button class=\"ql-strike\"></button>\n        </span>\n        <span class=\"ql-formats\">\n          <select class=\"ql-color\"></select>\n          <select class=\"ql-background\"></select>\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-emoji\">\n          </button>\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-script\" value=\"sub\"></button>\n          <button class=\"ql-script\" value=\"super\"></button>\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-header\" value=\"1\"></button>\n          <button class=\"ql-header\" value=\"2\"></button>\n          <button class=\"ql-blockquote\"></button>\n          <!-- // TODO: include 'syntax' module to make use of: <button class=\"ql-code-block\"></button>-->\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-list\" value=\"ordered\"></button>\n          <button class=\"ql-list\" value=\"bullet\"></button>\n          <button class=\"ql-indent\" value=\"-1\"></button>\n          <button class=\"ql-indent\" value=\"+1\"></button>\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-direction\" value=\"rtl\"></button>\n          <select class=\"ql-align\"></select>\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-link\"></button>\n          <!--<button class=\"ql-image\"></button>-->\n          <!--<button class=\"ql-video\"></button>-->\n          <!-- // TODO: include 'formula' module to make use of: <button class=\"ql-formula\"></button>-->\n        </span>\n        <span class=\"ql-formats\">\n          <button class=\"ql-clean\"></button>\n        </span>\n        <!-- <span class=\"ql-formats\" v-show=\"openDialog\"> -->\n        <span class=\"ql-formats\">\n          <button class=\"ql-open-dialog\" @click=\"$refs.input.click()\">\n            <ae-icon class=\"fa fa-paperclip\"></ae-icon>\n          </button>\n        </span>\n    </div>\n    </quill-editor>\n</ae-expandable>\n\n<ae-dialog :full-screen=\"true\" ref=\"dialog\">\n\t<div slot=\"header\" class=\"_CSS-dialog-header\">\n\t  <div></div>\n\t\t<div class=\"bp-button\" @click=\"$refs.dialog.close()\">\n\t\t\t<ae-icon class=\"fa fa-2x fa-window-close\"></ae-icon>\n\t\t</div>\n\t</div>\n\t<div slot=\"body\" class=\"_CSS-dialog-body\">\n    <input class=\"_CSS-dialog-body__input\" ref=\"input\" type=\"text\" v-model=\"locals.filter\" @input=\"controller.filter()\"\n    placeholder=\"Suchen...\">\n\t\t<ae-list\n      :documents=\"locals.images\"\n      param-min-column-width=\"100%\"\n      class=\"_CSS-dialog-body__search-list\"\n      list-id=\"2dab232c-2c8a-4aa4-b3f9-6023a7116161\"\n      @selected=\"$refs.dialog.close(); controller.selected($event)\"\n      param-design=\"search-title,title\"\n      ></ae-list>\n\t</div>\n</ae-dialog>\n\n\n<ae-layout  layout-id=\"f5a5c2a0-bf9c-4b44-b05b-8e545156df10\"\n            :param-document=\"document\"\n            :param-show-ui=\"false\"\n            param-file-form-id=\"6d3f6460-c6d2-4df1-8c05-e8bb66c5b8d2\"\n            param-file-form-field=\"datei\"\n            param-file-name-to-field=\"name\"\n            :param-save-new-documents=\"true\"\n            :param-flatten=\"true\"\n            ref=\"dropHandler\"\n            @added=\"controller.added($event)\"\n></ae-layout>\n\n<input v-show=\"false\" ref=\"input\" type=\"file\" @change=\"controller.input($event)\">\n","css":"._CSS-editor {\n  flex: 1;\n  min-height: 300px;\n  border-radius: var(--ae-border-radius);\n  max-height: none;\n}\n\n._CSS-editor--writeable {\n  background: var(--ae-ui-background);\n}\n\n._CSS-editor--writeable > .scrollingContainer {\n  border-radius: 0 0 var(--ae-border-radius) var(--ae-border-radius);\n}\n._CSS-editor--writeable > .ql-toolbar {\n  border-radius: var(--ae-border-radius) var(--ae-border-radius) 0 0;\n}\n\n.ae-expandable--expanded ._CSS-editor {\n  max-height: none;\n}\n\n._CSS-editor .ql-editor {\n  font-size: 16px;\n  border-radius: var(--ae-border-radius);\n}\n\n/* \n* Dialog\n */\n\n._CSS-dialog-header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n._CSS-dialog-add-reference .ae-dialog__body {\n  overflow: hidden;\n}\n\n._CSS-dialog-body__search-list {\n  flex: 1;\n  flex-basis: 100%;\n  overflow: auto;\n}\n\n._CSS-dialog-body {\n  flex: 1;\n  display: flex;\n  flex-flow: column;\n}\n\n._CSS-dialog-body__input{\n  height: 5rem;\n}\n","controller":"class Controller {\n  init() {\n    locals.filter = ''\n    locals.images = []\n    this.embedPromiseResolver = null\n  }\n\n  onHtml(htmlString) {\n    if (!params.htmlTo) {\n      return\n    }\n\n    if (Aeppic.isEditableDocument(document)) {\n      document.data[params.htmlTo] = htmlString\n    }\n  }\n\n  //  async openDialog() {\n  //     locals.filter = ''\n  //     await this.filter()\n  //     $refs.dialog.show()\n\n  //     Aeppic.nextRenderPass(() => {\n  //       $refs.input && $refs.input.focus()\n  //     })\n\n  //     const promise = new Promise((resolve) => {\n  //       this.embedPromiseResolver = resolve\n  //     })\n  //     return promise\n  //   }\n\n  selected(event) {\n    this.embedPromiseResolver({\n      name: 'ae-design',\n      values: {\n        documentId: event.id,\n        name: 'quill-embed'\n      }\n    })\n    this.embedPromiseResolver = null\n  }\n\n  // async filter() {\n  //   const files = await Aeppic.find(this._searchString, {\n  //     size: 20\n  //   })\n\n  //   locals.images = files.filter((file) => {\n  //     return file.data.datei.type && file.data.datei.type.indexOf('image/') !== -1\n  //   })\n  // }\n\n  // get _searchString() {\n  //   return `f.id:6d3f6460-c6d2-4df1-8c05-e8bb66c5b8d2 ${locals.filter + '*'}`\n  // }\n\n  async embedDetach(removedEmbedDataset) {\n    // TODO\n    // const refDocument = await Aeppic.get(removedEmbedDataset.documentId)\n    // document.removeReference('references', refDocument)\n  }\n\n  async embedCreate(insertedEmbedDataset) {\n    // TODO\n    // const refDocument = await Aeppic.get(insertedEmbedDataset.documentId)\n    // document.addReference('references', refDocument)\n  }\n\n\n  async paste(event) {\n    $refs.dropHandler.$emit('paste', event)\n  }\n\n  async drop(event) {\n    $refs.dropHandler.$emit('drop', event)\n  }\n\n  async input(event) {\n    $refs.dropHandler.$emit('input', event)\n\n    $refs.input.value = ''\n  }\n\n  async added(pastedDocumentIds) {\n    console.log('added', pastedDocumentIds)\n\n    if (!$refs.editor) {\n      return\n    }\n\n    if ($refs.input && $refs.input.value) {\n      $refs.input.value = ''\n    }\n\n\n    const ids = pastedDocumentIds.documentIds\n\n    if (!ids) {\n      return\n    }\n\n    for (const id of ids) {\n      Aeppic.watch(id, (pastedDocument) => {\n\n        if (!pastedDocument || Aeppic.Uploads.isDocumentUploading(pastedDocument)) {\n          return\n        }\n\n        this._insertDocumentInEditor(pastedDocument.id)\n      })\n    }\n  }\n\n  async _insertDocumentInEditor(id) {\n    console.log('_insertDocumentInEditor', id)\n\n    const doc = await Aeppic.get(id)\n\n    if (this._canConvert(doc)) {\n      const name = doc.data.name\n\n      const range = $refs.editor.quill.getSelection(true)\n\n      if (doc.data.datei.type.startsWith('image/')) {\n        const url = Aeppic.Content.getUrl(doc, 'datei')\n        $refs.editor.quill.insertEmbed(range.index, 'image', url)\n\n        return\n      }\n\n      const url = Aeppic.Content.getPreviewSrc(doc, 'datei', { format: 'jpg' })\n      const resp = await Aeppic.fetch(url)\n\n      if (resp.headers.has('X-Pdf-Pages')) {\n        const pageCount = resp.headers.get('X-Pdf-Pages')\n\n        for (let i = 1; i <= pageCount; i++) {\n          const dedicatedPageUrl = `${url}&firstPage=${i}&lastPage=${i}`\n\n          $refs.editor.quill.insertEmbed(range.index + i - 1, 'image', dedicatedPageUrl)\n        }\n      } else {\n        $refs.editor.quill.insertEmbed(range.index, 'image', url)\n      }\n\n\n    } else {\n      console.warn('user tried to insert not convertable document', doc)\n    }\n  }\n\n  _canConvert(doc) {\n    const notConvertableTypes = ['application/gzip', 'application/zip']\n\n    return doc.data.datei.type && !doc.data.datei.type.startsWith('video/') && !notConvertableTypes.includes(doc.data.datei.type)\n  }\n\n\n  destroy() {\n    this.embedPromiseResolver = null\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"05ecc2b2-c603-43f1-b974-f0ca7df8e400","v":"576eb10da3afaa97","text":"Quill Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-12-28T12:39:12+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2022-03-15T15:31:23+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936836,"ct":1721209936836,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"c3e15d16648e99a6","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"31c67582-9bf4-4e46-ae2c-587bd64295ff","v":"962837d01c39cf45","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"editor-quill","template":"<ae-control name=\"editor-quill\" :document=\"document\" :params=\"params\" :field-name=\"fieldName\"></ae-control>","css":"","controller":"","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"05ecc2b2-c603-43f1-b974-f0ca7df8e400","v":"576eb10da3afaa97","text":"Quill Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2022-10-21T14:40:46+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-10-21T14:43:08+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936836,"ct":1721209936836,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"896e2938da02900d","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"1bb63c8c-175e-4a66-b594-7caad8de86cf","v":"5226d25693c32d10","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"editor-quill-comment","template":"<quill-editor class=\"_CSS-editor\"\n              v-model=\"field\"\n              :readonly=\"controller.readonly\"\n              input-format=\"native\"\n              output-format=\"native\"\n              :open-dialog=\"() => controller.openDialog()\"\n              vue-component-names=\"ae-design\"\n              :vue-component-creator=\"(...args) => Aeppic.createVueComponentElement(...args)\"\n              :completion-keys-and-components=\"{'@':{ name:'ae-layout',props:{ layoutId: '238d8927-fc51-4cf5-922f-0830fccc3292', params: { parentEditableDocument: document } }},'/':{ name:'ae-layout', props:{ layoutId: '238d8927-fc51-4cf5-922f-0830fccc3292', params: { parentEditableDocument: document } }} }\"\n              :embed-handler=\"{ detach: (detachParams) => controller.embedDetach(detachParams), create: (createParams) => controller.embedCreate(createParams) }\"\n              @html=\"controller.onHtml($event)\"\n              :toolbar=\"false\"\n              ref=\"editor\"\n></quill-editor>\n","css":".CSS {\n  display: flex;\n}\n\n._CSS-editor {\n  flex: 1;\n  max-height: 400px;\n  background: var(--ae-theme-input-on-form);\n  color:black;\n}\n\n._CSS .ql-editor {\n  font-size: 16px;\n}","controller":"class Controller {\n  init() {\n    this.insertText = (text) => {\n      let selection = $refs.editor.quill.getSelection()\n\n      if (!selection) {\n        selection = $refs.editor.quill.getSelection(true)\n      }\n\n      const { index, length } = selection\n\n      const positiveIndex = index - 1 || 0\n\n      $refs.editor.quill.editor.insertText(positiveIndex, text, 'api')\n      $refs.editor.quill.insertText(positiveIndex + 1, ' ', 'api')\n      $refs.editor.quill.setSelection(positiveIndex + 2, 'api')\n\n      $refs.editor.updateOutputValue()\n    }\n\n    $on('insert-text', (text) => this.insertText(text))\n  }\n\n  openDialog() {\n    return {}\n\n    // TODO: enable\n    return {\n        name: 'ae-design',\n        values: {\n          documentId: 'root',\n          name: 'quill-embed,embed,title'\n        }\n      }\n  }\n\n  onHtml(htmlString) {\n    if (!params.htmlTo) {\n      return\n    }\n\n    if (Aeppic.isEditableDocument(document)) {\n      document.data[params.htmlTo] = htmlString\n    }\n  }\n\n  async embedDetach(removedEmbedDataset) {\n    // const refDocument = await Aeppic.get(removedEmbedDataset.documentId)\n    // document.removeReference('references', refDocument)\n  }\n\n  async embedCreate(insertedEmbedDataset) {\n    // const refDocument = await Aeppic.get(insertedEmbedDataset.documentId)\n    // document.addReference('references', refDocument)\n  }\n\n\n  get readonly() {\n    if (params.readonly === true) {\n      return true\n    }\n\n    if (typeof readonly === 'boolean') {\n      return readonly\n    }\n\n    return false\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"05ecc2b2-c603-43f1-b974-f0ca7df8e400","v":"f26bbcd8a85a65b1","text":"Quill Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-12-28T11:44:36+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2022-01-19T09:32:57+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936836,"ct":1721209936836,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"d6418334cb6704ed","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"3d9aed7d-9592-437e-be42-98bd2d6f8ef4","v":"260b7f242fae5ed6","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"editor-quill-light","template":"\t<quill-editor :class=\"{ '_CSS-editor--no-overflow': params.hideOverflow }\" class=\"_CSS-editor\" v-model=\"field\" :readonly=\"params.readonly != null ? params.readonly: readonly\" input-format=\"native\" output-format=\"native\"\r\n\t vue-component-names=\"ae-design\" :vue-component-creator=\"(...args) => Aeppic.createVueComponentElement(...args)\" @html=\"controller.onHtml($event)\"\r\n   ></quill-editor>","css":"._CSS {\r\n  position: relative;\r\n  display: flex;\r\n  /* display: block; */\r\n  margin:0;\r\n  padding:0;\r\n}\r\n\r\n._CSS-editor {\r\n  margin:0;\r\n}\r\n\r\n._CSS-editor--no-overflow {\r\n  height: 100%;\r\n}\r\n\r\n._CSS .editor {\r\n  border:0 !important; \r\n  margin:0;\r\n}\r\n\r\n\r\n._CSS .scrollingContainer {\r\n  margin:0;\r\n  border: 0;\r\n}\r\n\r\n._CSS .ql-editor {\r\n  font-size: 16px;\r\n}","controller":"onHtml(htmlString) {\n  if (!params.htmlTo) {\n    return\n  }\n\n  if (Aeppic.isEditableDocument(document)) {\n    document.data[params.htmlTo] = htmlString\n  }\n}\n","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"05ecc2b2-c603-43f1-b974-f0ca7df8e400","v":"1d40271a1a6e8d02","text":"Quill Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-03-25T15:17:43+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2022-10-24T13:29:42+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936837,"ct":1721209936837,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"b4b7400145fdb542","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"7f9e5f1f-33bc-4cf7-826e-8232175f1ec2","v":"9517c865fa17feba","previous":{"v":"749b58dbe912097b"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"email","template":"<div class=\"_CSS-line\" >\n  <input v-if=\"locals.editing\"\n          type=\"email\"\n          :value=\"field\"\n          @input=\"controller.update($event.target.value)\"\n          :required=\"fieldInfo.required\"\n          :autocomplete=\"params.autocomplete || 'off'\"\n          :name=\"params.autocomplete\"\n          style=\"flex: 1;\"\n          class=\"_CSS-input\"\n  >\n  <div style=\"flex:1\" v-else class=\"ae-form-cell__input _CSS-value\">\n    <a class=\"ae-link\" :href=\"'mailto:' + field\" target=\"_blank\">{{field}}</a>\n  </div>\n\n  <button class=\"ae-button ae-button--small _CSS-button\" :class=\"{ 'ae-button--tertiary': locals.editing }\" @click.stop=\"locals.editing = !locals.editing\"> \n    <ae-icon class=\"fa fa-edit fa-flip-horizontal\" v-if=\"locals.editing\"></ae-icon>\n    <ae-icon class=\"fa fa-edit\" v-else></ae-icon>\n  </button>\n</div>  \n ","css":"._CSS-line {\n  display: flex;\n  align-items: center;\n}\n\n._CSS-button {\n  position: absolute;\n  right: 1px;\n  top: 1px;\n} \n\n._CSS ._CSS-value,\n._CSS ._CSS-input {\n  padding-right: 40px;\n}","controller":"class Controller {\n  init() {\n    locals.editing = !getField()\n\n    // source: https://ihateregex.io/expr/email/\n    this.simpleMailRegex = /[^@ \\t\\r\\n]+@[^@ \\t\\r\\n]+\\.[^@ \\t\\r\\n]+/gm\n  }\n\n  update(value) {\n    if (!fieldInfo.required) {\n      setField(value)\n      return\n    }\n\n    // TODO: this unsets the field if validation fails.\n    //       but it should not unset the field, but instead raise an validation error. \n    //       Currently there is only \"has a value/required\" and nothing else.\n    if (!value || value.search(this.simpleMailRegex) === -1) {\n      setField('')\n      return\n    }\n\n    setField(value)\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":true,"types":["text"],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-11-25T13:55:35+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2024-08-02T12:51:12+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1722603072520,"ct":1721209936837,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-09-08T14:23:05+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936837,"data":{"cardinality":"BOTH","controller":"class Controller {\n  init() {\n  }\n\n  get field() {\n    const field = getField() \n\n    if (Array.isArray(field)) {\n      return field\n    }\n\n    if (!field) {\n      return []\n    }\n\n    return [field]\n  }\n\n  add() {\n    this.edit(null)\n\n    const field = getField()\n\n    if (Array.isArray(field)) {\n      field.push('')\n\n      setField(field)\n\n      this.edit(field.length - 1)\n      return \n    } \n\n    if (!field) {\n      // extranous whitespace will be trimmed on update\n      setField(' ') \n    }\n\n    this.edit(0)\n  }\n\n  edit(index) {\n    $refs.layout.$emit('focus-at-index', index)\n  }\n\n  update(args) {\n    const { index, value } = args\n\n    let field = getField()\n    \n    const sanitized = this.sanitizeValue(value)\n\n    if (Array.isArray(field)) { \n      if (sanitized) {\n        field.splice(index, 1, sanitized)\n      } else {\n        field.splice(index, 1, '') \n      }\n    } else {\n      field = sanitized\n    }\n\n \n    setField(field) \n  }\n\n  sanitizeValue(value) {\n    const raw = `${value}`.trim()\n\n    // TODO: sanitize!?\n    const sanitized = raw // .replace(/[^0-9|\\+]/g, '')\n\n    return sanitized\n  }\n\n  remove(args) {\n    const { index } = args\n\n    let field = getField()\n\n    if (Array.isArray(field)) { \n      field.splice(index, 1) \n    } else {\n      field = ''\n    }\n\n    setField(field)\n\n    this.edit(null)\n  }\n}","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"email","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<div v-if=\"params.simpleInput\" \n    class=\"border rounded-md flex gap-2 min-h-[2em] overflow-hidden\"\n    :class=\"[validationErrors?.required && 'border-red-500']\"\n    data-ae-form-field-input\n>\n  <div class=\"flex items-center rounded-l-md border-r px-2 text-neutral-500 bg-white\" @click.stop>\n    <ae-icon class=\"far fa-envelope\"></ae-icon>\n  </div> \n\n  <ae-control class=\"flex-1 min-w-0\" name=\"rd:text\" :document=\"document\" :field-name=\"fieldName\" :param-without-border=\"true\"\n                :param-value-sanitizer-function=\"(value) => controller.sanitizeValue(value)\"\n  ></ae-control>\n</div>\n<ae-layout v-else\n           layout-id=\"866974ba-6e69-483b-8933-2ec6ac66ef1c\"\n          :param-entries=\"controller.field\"\n          :param-cardinality=\"fieldInfo.cardinality\"\n          param-icon=\"far fa-envelope\"\n          :param-readonly=\"readonly\"\n          @add=\"controller.add()\"\n          @remove=\"controller.remove($event)\"\n          @update=\"controller.update($event)\"\n          ref=\"layout\"\n          param-href-prefix=\"mailto:\"\n          :param-border-color=\"validationErrors?.required && 'border-red-500'\"\n></ae-layout>\n  ","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"9899df87-12ef-43a5-8495-7eebf4582c99","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-01-05T12:09:45+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"90e00a597ae2c156"},"stamps":[],"t":1767614985974,"v":"318071ed71cd015b"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2026-05-08T08:17:14+00:00","backedBy":"","by":"47325375-27fb-4303-8bf5-9438a0524219","client":"server","from":"","impersonatedBy":""},"ct":1778228234721,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    locals.loading = true\n    locals.document = null\n    locals.form = null\n    locals.fields = []\n    locals.readonly = true\n\n    const doc = await Aeppic.get(getField())\n    locals.document = doc\n\n    if (locals.document) {\n      locals.form = await Aeppic.getFormForDocument(locals.document)\n      locals.fieldNames = params.inlineOnlyFields\n      ? params.inlineOnlyFields.split(',').map(f => f.trim()).filter(f => !!f)\n      : locals.form.fields.map(f => f.name)\n      \n      if (locals.fieldNames) {\n        locals.fields = locals.fieldNames.map(name => this._readFieldInfo(name)).filter(info => !!info)\n      } else {\n        locals.fields = locals.form.fields.map(f => f)\n      }\n    }\n\n    locals.loading = false\n  }\n\n  _readFieldInfo(name) {\n    const field = locals.form.getField(name)\n\n    if (!field) {\n      console.error('Unknown field', name)\n      return null\n    }\n\n    const placeholder = locals.form.getPlaceholder(name)\n\n    if (!placeholder) {\n      return\n    }\n\n    const controlParams = locals.form.getPlaceholderControlParams(placeholder.index)\n    \n    return {\n      name,\n      label: placeholder.label,\n      controlName: placeholder.control?.fullName ?? field.subType,\n      controlParams,\n      field\n    }\n  }\n}\n","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"embed","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<ae-control v-if=\"params.inlineLayout\" name=\"rd:ref\" :document=\"document\" :field-name=\"fieldName\" :params=\"params\"\n  :param-inline=\"true\" :readonly=\"readonly\"></ae-control>\n<div v-else>\n  <div v-if=\"locals.document\">\n    <!-- negative margin to 'remove' form padding -->\n    <ae-form layout-id=\"efdb156e-9350-4cb3-8e00-4a69adec691b\" :document=\"locals.document\"\n      :view=\"{ only: { fields: locals.fields.map(f => f.name) }}\" :auto-save=\"true\" :auto-save-after=\"1000\"\n      class=\"-m-4\"\n      ></ae-form>\n  </div>\n  <div v-else-if=\"locals.loading === false\" class=\"italic\">\n    Kein Zugriff oder Verweis gelöscht.\n  </div>\n\n</div>","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"74943951-98a7-400e-815a-da9394599618","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-05-11T08:17:29+00:00","backedBy":"","by":"b7485498-f1f3-4ab1-87d1-9cb63ea890c8","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"00090f0fc168d030"},"stamps":[],"t":1778487449779,"v":"88796320a58fac24"},{"id":"78431008-133e-44db-83e6-9960ffc8d7ae","v":"a3940f65c7fc39f5","previous":{"v":"aba3ad27a1947aeb"},"p":"controls-folder","f":{"id":"control-form","v":"8f342d57244fe7b5","text":"Control"},"data":{"namespace":"","name":"embedded","template":"<ae-control v-if=\"params.inlineLayout\"\n            name=\"ref\"\n            :document=\"document\"\n            :field-name=\"fieldName\"\n            :params=\"params\"\n            :param-inline=\"true\"\n            :readonly=\"readonly\"\n></ae-control>\n<div v-else class=\"_CSS-container\"> \n  <div v-if=\"locals.document\" \n      class=\"grid\"\n      style=\"grid-template-columns: minmax(30px,auto) auto minmax(50px, 1fr) ; grid-gap: 4px 10px; align-items: center\"\n      >\n    <!-- <template > -->\n      <template v-for=\"field of locals.fields\">\n        <pre v-show=\"false\">{{field.name}} | {{ field.controlName }} | {{ field.controlParams}}</pre>\n        \n        <label :title=\"field.label\" class=\"text-ellipsis overflow-hidden whitespace-nowrap\">{{field.label}}</label>\n          <ae-control v-if=\"field.field.settings?.help.text\"\n          name=\"help\"\n          class=\"bg-\"\n          :field-name=\"field.name\"\n          :document=\"locals.document\"\n          :form=\"locals.form\"\n          :param-placeholder=\"locals.helpPlaceholder\" \n          :readonly=\"readonly\"\n        ></ae-control> \n        <div style=\"width:0;height:0\" v-else></div>\n        <ae-control :name=\"field.controlName\"\n                    :document=\"locals.document\"\n                    :field-name=\"field.name\"\n                    :params=\"field.controlParams\"\n                    :param-inline=\"true\"\n                    :readonly=\"readonly\"\n        ></ae-control>\n     \n      </template>\n    <!-- </template> -->\n    <!-- <ae-form v-else\n      :document=\"locals.document\"\n      param-fields=\"locals.fields\"\n      :readonly=\"locals.readonly\"></ae-form> -->\n\n  </div>\n  <pre v-else v-show=\"false\">\n    {{params.documentQuery}}\n  </pre>\n</div>","css":"._CSS-container {\n  padding: 10px;\n  border: 1px solid lightgray;\n  margin: 0px;\n  background-color: rgb(0 0 0 / 4%);\n  box-shadow: inset 1px 2px 4px 0px #de8c824a;\n  box-shadow: inset 1px 2px 4px 0px oklch(0.83 0.07 26.59 / 0.58);\n}","controller":"class Controller {\n  async init() {\n    locals.document = null\n    locals.form = null\n    locals.fields = []\n    locals.fieldNames = params.inlineOnlyFields\n                      ? params.inlineOnlyFields.split(',').map(f => f.trim()).filter(f => !!f)\n                      : []\n    locals.readonly = true\n \n    if (params.documentQuery) {\n      locals.document = await this.resolveDocumentPath(document, params.documentQuery)\n\n      if (!locals.document) {\n        console.warn('Document not accessible', document, params.documentQuery)\n      \n      } else {\n        locals.form = await Aeppic.getFormForDocument(locals.document)\n      }\n    } else {\n      locals.document = document\n      locals.form = form\n    }\n\n    if (!locals.document) {\n      console.warn('No document for embedding')\n      return\n    }\n\n    if (locals.fieldNames) {\n      locals.fields = locals.fieldNames.map(name => this._readFieldInfo(locals.document, name)).filter(info => !!info)\n    }\n  }\n\n  _readFieldInfo(d, name) {\n    const field = d.form.getField(name)\n\n    if (!field) {\n      console.error('Unknown field', name)\n      return null\n    }\n\n    const placeholder = d.form.getPlaceholder(name)\n\n    if (!placeholder) {\n      return\n    }\n\n    const controlParams = d.form.getPlaceholderControlParams(placeholder.index)\n    \n    return {\n      name,\n      label: placeholder.label,\n      controlName: placeholder.control?.fullName ?? field.subType,\n      controlParams,\n      field\n    }\n  }\n\n  async resolveDocumentPath(root, query) {\n    // Split the query into steps\n    console.log('resolving', root.id, query)\n    let steps = query.split('->').map(step => step.trim())\n\n    for (let i = 0; i < steps.length; i++) {\n        // Split the current step into parts\n        let parts = steps[i].split('.')\n\n        for (let j = 0; j < parts.length; j++) {\n            // Get the next part of the step\n            let part = parts[j]\n\n            // If the document is null or the part is not in the root, return null\n            if (root === null || !(part in root)) {\n                return null\n            }\n\n            // Update the root document\n            root = root[part]\n        }\n\n        // Get the document from Aeppic.get\n        root = await Aeppic.get(root)\n\n        // If the document is null, return null\n        if (root === null) {\n            return null\n        }\n    }\n\n    // Return the final document as editable\n\n    if (root) {\n      return Aeppic.edit(root)\n    } else {\n      return null\n    }    \n  }\n}\n","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-10-12T11:48:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2024-07-24T14:42:08+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721824928971,"ct":1721144004092,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2024-11-06T13:59:51+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"ct":1730901591708,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    this.FORMS = {\n      ACCOUNT: 'account',\n      MITARBEITER_FORM: 'd2ac3563-3c3f-4f02-b923-9b67abf98d10',\n      NEW_MITARBEITER_FORM: '333c8369-a915-4735-b108-293c0bcb742d',\n      AZURE_ENTRY_INFO_FORM: '365ca714-9a6d-4e74-9b37-060979447b52',\n    }\n\n    this.infos = new Map()\n    this.azureIdToAeppicUuidMap = new Map()\n\n    locals.rerender = 0\n    locals.ready = false\n\n    locals.refs = this._getField()\n\n    locals.filter = ''\n    locals.isMultiPicker = fieldInfo.cardinality\n\n    locals.filteredPeople = []\n    locals.popupOpen = false\n    locals.highlightedPerson = null\n    locals.searching = false\n    locals.error = null\n\n\n    Aeppic.watch(document, (d) => {\n      locals.selected = this._getField()\n    })\n\n    this.newPerson = await Aeppic.new(this.FORMS.MITARBEITER_FORM, document.id)  \n\n    locals.ready = true\n  }\n\n  _getField() {\n    const field = getField() \n\n    const refs = []\n\n    if (Array.isArray(field)) {\n      refs.push(...field)\n    } else if (field.id) {\n      refs.push(field)\n    }\n\n    // FIXME: removed on 2025-07-11. wtf? /rsch\n    // locals.refs = refs\n\n    return refs\n  }\n  \n  async remove(d) {\n    const oldField = this._getField()\n\n    const id = typeof d === 'string' ? d : d.id\n\n    const refs = this._getField().filter(r => r.id != id)\n\n    if (locals.isMultiPicker) {\n      setField(refs)\n    } else {\n      setField(refs[0] || { id: '', text: '', v: '' })\n    }\n\n    locals.refs = this._getField()\n\n  }\n\n  async addSelected(profile) {\n    const oldField = this._getField()\n    const field = this._getField()\n\n    const asReference = { id: profile.id, text: profile.data.name, v: profile.v }\n\n    const alreadyAdded = field.find(entry => entry.id === asReference.id && entry.v === asReference.v && entry.text === asReference.text)\n\n    if (alreadyAdded) {\n      return\n    }\n\n    if (locals.isMultiPicker) {\n      field.push(asReference)\n      setField(field)\n    } else {\n      field.splice(0, field.length, asReference)\n      setField(field[0])\n    }\n\n    locals.refs = this._getField()\n\n    await this.addDataFromReferencedDocumentToFieldGivenInAs()\n\n    this.close()\n  }\n\n  // TODO: support cardinality\n  async addDataFromReferencedDocumentToFieldGivenInAs() {\n    if (!params.fromField || !params.as) {\n      return\n    }\n    if (fieldInfo.cardinality) {\n      console.warn('[WARN]: params.fromField and params.as not supported with cardinality')\n    }\n    \n    const field = getField()\n    const referencedDocument = await Aeppic.get(field.id)\n    document.data[params.as] = referencedDocument.data[params.fromField]\n  }\n\n\n\n  clearSelection() {\n    if (locals.isMultiPicker) {\n      setField([])\n    } else {\n      setField({ id: '', text: '', v: '' })\n    }\n\n    locals.refs = this._getField()\n\n    this.close()\n  }\n\n  oninput() {\n    this.find(locals.filter)\n  }\n\n  open(evt) {\n    if (readonly) {\n      return\n    }\n\n    locals.filter = ''\n\n    $refs.popup.show({ target: $refs.documents.$el })\n  }\n\n  onOpen() {\n    locals.popupOpen = true\n\n    // $refs.filter.select()\n    // $refs.filter.focus()\n\n    this.find() \n  }\n\n\n  getPopupWidth() {\n    const readToRefresh = locals.popupOpen\n    return $refs.input?.offsetWidth + 'px' ?? ''\n  }\n\n  async select(person) {\n    // if (!person.id.startsWith('az-')) {\n    //   return\n    // }\n    \n    const info = this.infos.get(person.id)\n\n    if (!info) {\n      // console.error('missing info')\n      // is a regular aeppic employee or was already saved as a profile before\n      this.addSelected(person)\n      return\n    }\n    \n    const companyId = document.a[0]\n    const existingEntraInfo = await Aeppic.findOne(Aeppic.Query.descendants(companyId).form(this.FORMS.AZURE_ENTRY_INFO_FORM).where('data.name').is(info.id))\n    \n    // TODO: UPDATE when changed\n    if (existingEntraInfo) {\n      const account = await Aeppic.get(existingEntraInfo.p)\n      const profile = await Aeppic.get(account.p)\n\n      this.addSelected(profile)\n    } else {\n      const existingAccount = await Aeppic.findOne(Aeppic.Query.descendants(companyId).form(this.FORMS.ACCOUNT).where('data.email').is(info.mail))\n\n      if (existingAccount) {\n        const profile = await Aeppic.get(existingAccount.p)\n      \n        this.addSelected(profile)\n      } else {\n        this.addSelected(person)\n\n        // the ae-list does not re-render it's results currenctly. So watch for the requested profile to be created once /rsch 2025-07-15\n        const newPersonWatcher = Aeppic.watch(person.id, (doc, reason) => {\n          if (reason.updated) {\n            locals.rerender += 1\n            newPersonWatcher.stop()\n          }\n        })\n\n        const newAccountRequestForm = await Aeppic.new(this.FORMS.NEW_MITARBEITER_FORM, document)\n            \n        newAccountRequestForm.hidden = true\n        newAccountRequestForm.addReference('company', await Aeppic.get(companyId))\n        // use generated profile id because we already added `person` to the field\n        newAccountRequestForm.data.profile = {\n          id: person.id,\n          v: person.v,\n          text: person.data.name,\n        }\n        newAccountRequestForm.data.name = info.userPrincipalName\n        newAccountRequestForm.data.username = info.userPrincipalName\n        newAccountRequestForm.data.employeeName = info.displayName\n        newAccountRequestForm.data.email = info.mail || info.userPrincipalName\n        newAccountRequestForm.data.allowDirectLogin = true\n        newAccountRequestForm.data.entraUserInfo = JSON.stringify(info)\n\n        Aeppic.save(newAccountRequestForm)\n      }\n    }\n\n    this.close()\n  }\n\n  async find() {\n    if (locals.searching) {\n      return\n    }\n\n    if (locals.filter.trim() === '') {\n      locals.searching = true \n\n      const aeppicPersonsResult = this._findAeppicPersons()\n      locals.filteredPeople = (await aeppicPersonsResult).persons\n\n      locals.searching = false\n      return\n    }\n\n    const filterAtTimeOfSearch = locals.filter\n    \n    locals.searching = true \n    try {\n      const filter = locals.filter.trim()\n\n      const [\n        azurePersons,\n        aeppicPersonsResult,\n      ] = await Promise.all([\n          this._findAzurePersons(filter),\n          this._findAeppicPersons(filter),\n      ]) \n\n      // filter out already created azure persons/employees\n      const newAzurePersons = azurePersons.filter(p => {\n        const info = this.infos.get(p.id)\n        return aeppicPersonsResult.entryUserInfos.has(info.id) === false\n      })\n\n      const both = [...newAzurePersons, aeppicPersonsResult.persons]\n\n      const combined = Aeppic.Functions.sort(both.flat(), 'data.name')\n\n      locals.filteredPeople = combined\n\n    } finally {\n      locals.searching = false\n\n      if (filterAtTimeOfSearch != locals.filter) {\n        console.log('finding again. filter changed')\n        setTimeout(() => this.find(), 0)\n      } \n    }\n  }\n\n  async _findAzurePersons(filter) {\n    const response = await Aeppic.fetch(`/api/services/7acd77d2-3b2b-48e5-813f-aabbb50f7f35/users/_search?query=displayname:${filter}`)\n\n    if (!response.ok) {\n      locals.error = `Error searching`\n      return []\n    }\n\n    const results = await response.json()\n    const people = results.value\n\n    const azurePersons = people.map((f) => this._mapPerson(f))\n\n    return azurePersons\n  }\n\n  async _findAeppicPersons(filter) {\n    const companyId = document.a[0]\n    const nameFilter = filter ? `(${filter} OR *${filter}* OR ${filter}*)` : ''\n\n    const persons = await Aeppic.find(`a:${companyId} f.id:${this.FORMS.MITARBEITER_FORM} ${nameFilter}`)\n\n    \n    const entraUserInfoPersons = await Promise.all(persons.map(async p => await Aeppic.findOne(`a:${p.id} f.id:${this.FORMS.AZURE_ENTRY_INFO_FORM}`)))\n    \n    // add EntraUserInfo's email to Employee Document to allow the e-mail to display nicely    \n    const editablePersons = await Aeppic.editAll(persons)\n    editablePersons.map((person, index) => {\n      const mail = entraUserInfoPersons[index]?.data.email || ''\n      person.data.beschreibung += `${mail}\n${person.data.beschreibung}`\n    })\n\n    const adaptedPersons = editablePersons.map(editable => editable.cloneAsDocument())\n\n    // the EntryUserId is saved to the 'name' field\n    const entryUserInfos = new Set(entraUserInfoPersons.map(d => d && d.data.name))\n\n    return {\n      persons: adaptedPersons,\n      entryUserInfos\n    }\n  }\n\n  isSelectedPerson(person) {\n    return !!locals.refs.find(r => r.id == person.id)\n  }\n\n  _mapPerson(info) {\n    // info = \n    // {\n    //     \"businessPhones\": [],\n    //     \"displayName\": \"My Test\",\n    //     \"givenName\": \"My\",\n    //     \"id\": \"aa0e0e40-00f1-4911-b853-3027b1c06ecf\",\n    //     \"jobTitle\": \"Test User\",\n    //     \"mail\": \"my@gmail.com\",\n    //     \"mobilePhone\": null,\n    //     \"officeLocation\": null,\n    //     \"preferredLanguage\": null,\n    //     \"surname\": \"Test\",\n    //     \"userPrincipalName\": \"unique@email.com\"\n    // }\n\n    // generate a stable id to prevent placeholder avatar color from changing\n    const id = this._getIdForAzureInfo(info)\n\n    const newPerson = this.newPerson.cloneAsDocument()\n\n    newPerson.id = id\n    // NOTE: do not set v. It will be set on actual creation and thus allowing the ae-list to rerender properly\n    // FIXME: does not work anymore, see newPersonWatcher /rsch 2025-07-15\n    // newPerson.v = 'initial'\n    newPerson.data.vorname = info.givenName\n    newPerson.data.nachname = info.surname\n    newPerson.data.name = info.displayName\n    newPerson.data.beschreibung = [\n      `${info.mail || ''}`,\n    ].join(' ')\n\n    this.infos.set(newPerson.id, info)\n\n    return newPerson\n  }\n\n  _getIdForAzureInfo(info) {\n    if (!info.id) {\n      // azure info should contain an id-field. But make sure we get a clean UUID\n      return Aeppic.uuid()\n    }\n\n    if (this.azureIdToAeppicUuidMap.has(info.id)) {\n      return this.azureIdToAeppicUuidMap.get(info.id)\n    }\n\n    const uuid = Aeppic.uuid()\n    this.azureIdToAeppicUuidMap.set(info.id, uuid)\n    return uuid\n  }\n\n  mark(person) {\n    locals.highlightedPerson = person\n  }\n\n  unmark(person) {\n    if (person === locals.highlightedPerson) {\n      locals.highlightedPerson = null\n    }\n  }\n\n  isHighlightedPerson(person) {\n    return (person === locals.highlightedPerson) \n  }\n\n  popupEnter() {\n    $refs.popup?.cancelClose()\n  } \n\n  async close() {\n    this.infos = new Map()\n    \n    locals.highlightedPerson = null\n    locals.filteredPeople = []\n    \n    $refs.popup?.close()\n\n    await Aeppic.nextRenderPass()\n\n    $refs.input?.focus()\n  }\n}","css":"","description":"","device":"ALL","label":"","limitTypes":true,"name":"employee-ref","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<!-- ready: {{locals.ready}}\nsearing: {{locals.searching}}\nfP: {{locals.filteredPeople.length}}\nwc: {{ locals.watchCounter }} -->\n<div v-if=\"locals.ready\"\n      data-ae-form-field-input \n      :data-test=\"fieldName\"\n      class=\"relative h-full\"\n      :class=\"[!params.noBorder && 'border rounded-md px-2', \n              !params.noBorder && validationErrors?.required && 'border-red-500']\"\n>\n  <ae-list list-id=\"cf22c0e1-a656-4211-93c1-7661da07b882\"\n            ref=\"documents\"\n            :documents=\"locals.refs\"\n            @remove=\"controller.remove($event)\"\n            @selected=\"\"\n            @add=\"controller.open()\"\n            :param-readonly=\"readonly\"\n            :key=\"'_CSS-' + locals.rerender\"\n  ></ae-list>\n  <!-- <button ref=\"input\"  \n    type=\"button\"  \n    @click=\"controller.open($event)\"\n    @keydown.enter.prevent.stop=\"controller.enterOnInput($event)\"\n    @keydown.down.prevent.stop=\"controller.downOnInput($event)\"\n    @keydown=\"controller.find($event)\"        \n    class=\"relative w-full border rounded-md py-1 px-2 flex gap-1 bg-white dark:bg-slate-800\"\n    :class=\"[(params.readonly ?? readonly) && 'ae-form-cell__input--readonly', validationErrors?.required && 'border-red-500']\"\n    aria-haspopup=\"listbox\"  \n    :aria-expanded=\"locals.popupOpen\">\n    <div class=\"flex gap-2\">\n      <div class=\"flex flex-col flex-1 overflow-hidden\">\n        <div class=\"flex flex-row items-center\">\n          <template v-if=\"locals.isMultiPicker\">\n            <ae-design v-for=\"d of locals.selected\" :key=\"d.id\" :document=\"d\" name=\"rd:tag\" :readonly=\"readonly\" @remove=\"controller.remove(d)\"></ae-design>\n          </template>\n          <template v-else>\n            <ae-design v-if=\"locals.selected.id\" :document=\"locals.selected\" name=\"rd:tag\" :readonly=\"true\"></ae-design>\n          </template>\n        </div>\n        <span class=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2\">\n          <svg class=\"h-5 w-5 text-gray-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n            <path fill-rule=\"evenodd\" d=\"M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z\" clip-rule=\"evenodd\" />\n          </svg>\n        </span>\n      </div>\n    </div>\n  </button>    -->\n\n  <ae-popup v-if=\"!readonly\"\n            ref=\"popup\" \n            placement=\"bottom-start\"\n            :max-width=\"400\"\n            @close=\"locals.popupOpen = false\"\n            @show=\"controller.onOpen()\"\n    >\n\n    <div  \n      class=\"max-h-80 xp-2 z-20 flex flex-col border shadow-lg overflow-y-auto rounded-md bg-white text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm\"\n      :data-test=\"'control_' + control.data.name + '_documentForm_' + document.f.text\"\n    >\n      <div slot=\"popup\" class=\"overflow-hidden flex flex-col\">\n        <div class=\"pt-2 px-2\">\n          <input v-if=\"locals.popupOpen\"  v-focus v-model=\"locals.filter\" ref=\"input\" placeholder=\"Namen von Personen suchen...\" @input=\"controller.find()\"\n          class=\"w-full border rounded-md py-1 px-2 flex gap-1 bg-white placeholder:italic\"\n          @keydown.down=\"$refs.list.$emit('focus-entry')\"\n          > \n        </div>\n\n        <div class=\"flex-1 min-h-8 overflow-auto\">\n          <ae-list\n          class=\"m-1 ease-in duration-300 p-2\"\n          :class=\"{ 'opacity-25': locals.searching }\"\n          ref=\"list\"\n          :list-id=\"params.listId || '922e8251-6e60-45a4-9df6-3d434363ac01'\"\n          @selected=\"controller.select($event)\"\n          :documents=\"locals.filteredPeople\"\n          :param-selected-items=\"params.selectedItems\"\n          :param-item-labels=\"params.itemLabels\"\n          :param-design=\"params.design || 'rd:search-card'\"\n          :param-design-params=\"{ ...params.designParams, hidePath: params.hidePath, useDocumentIcon: true }\"\n          :param-divide=\"true\"\n          param-empty-text=\"Keine Ergebnisse.\" \n          @focus-out=\"$refs.input.focus()\"\n          :watch-documents=\"false\"\n          ></ae-list>\n          <!-- @remove=\"controller.onRemove($event)\" -->\n        </div>\n      </div>\n    </div>\n  </ae-popup>\n</div>","types":["ref"]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"3e8fe68b-bf8c-4ad2-bf78-1e1a3e5004e4","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-08-29T13:15:19+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"75695129a04e6c08"},"stamps":[],"t":1756473319784,"v":"3363eaa87a4eb78f"},{"id":"3c713638-310d-4220-a3f9-824d201657c6","v":"5084cf4813f65004","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"prototype","name":"fake-buttons","template":"<button class=\"ae-button ae-button--primary\" v-for=\"name of locals.names\">\n  {{ name }}\n</button>","css":"","controller":"class Controller {\n  init() {\n    locals.names = params.names.split(',')\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-04-15T07:34:07+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2021-04-15T07:36:40+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936838,"ct":1721209936838,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"80a127faff5f2cdd","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"1138c53c-7bb7-460e-b05e-29d462993d6a","v":"813d67e15218a00a","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"maus","name":"fauser-customer-lookup","template":"<div class=\"block relative\" ref=\"rootElement\">\n  <div class=\"flex w-full items-center justify-between\">\n    <input type=\"text\"\n      @keydown=\"controller.handleInputKeyPress($event)\"\n      class=\"block\n          placeholder:italic placeholder:text-slate-400\n          focus:outline-none\n          focus:shadow-outline-blue\n          px-3\n          py-1.5\n          text-base\n          font-normal\n          text-gray-700\n          bg-white bg-clip-padding\n          border border-solid border-gray-300\n          rounded\n          transition\n          ease-in-out\n          m-0\n          focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none\"\n      v-model=\"locals.text\"\n      placeholder=\"Kundenname eingeben ...\"\n      @input=\"controller.onTextChanged()\">\n    <button ref=\"lastElement\" style=\"display:none\" class=\"fa fa-sync\n          mx-2 p-4 rounded-md margin: hover:bg-gray-300 hover:border-gray-800 focus:bg-gray-300 focus:border-gray-800\" >\n    </button>\n  </div>\n  <ul v-show=\"locals.searchShow\" ref=\"searchList\"\n    class=\"fixed m-1 max-h-64 block divide-y divide-slate-200 z-10 bg-white border border-gray-300 rounded-lg flex flex-col text-sm py-4 px-2 text-gray-500 drop-shadow-lg overflow-y-auto\"\n    @focusin=\"controller.enteringSearchList()\"\n    @focusout=\"controller.leavingSearchList()\">\n    <li v-if=\"locals.searching > 0\" :style=\"{ 'opacity': locals.searching > 0 ? 100:10 }\" class=\"transition-opacity delay-100 duration-200\"><ae-icon class=\"fa fa-spinner-third fa-spin\"></ae-icon></li>\n    <li v-for=\"r of locals.searchResults\"\n      tabindex=\"0\"\n      @keydown.prevent.stop=\"controller.handleKeyDownInSearchList($event, r)\"\n      @click.prevent.stop=\"controller.selectSearchResult(r)\"      \n      class=\"flex hover:bg-gray-100 py-1 px-2 rounded focus:border-blue-300 first:pt-0 last:pb-0\">\n      <div class=\"text-gray-500 sm:pr-2 w-full\">\n        <h3 class=\"text-l font-bold text-gray-900 text-ellipsis overflow-hidden\">{{r.NAME}}<small class=\"w-auto float-right\">&nbsp;{{r.CUSTNO}} ({{r.CNTRYSIGN}})</small></h3>\n        <div>{{r.POSTCODE}}&nbsp;{{r.PLACE}}</div>\n        <p v-if=\"r.ADDITION && r.ADDITION.trim() != ''\" class=\"mt-2 hidden text-sm sm:block\">\n          {{r.ADDITION}}\n        </p>\n      </div>\n    </li>\n  </ul> \n</div>\n","css":"","controller":"class Controller {\n  init() {\n    locals.params = params\n    locals.text = getField()\n\n    locals.searchShow = false\n    locals.searchListFocused = false\n    locals.searching = 0\n    locals.searchResults = []\n    locals.searchFailure = false\n    locals.ignoreTextChangeOnce = false\n\n    locals.searchResults = []\n\n    this.debouncedCloseSearchListWhenNotFocused = Aeppic.debounce(() => {\n      if (!locals.searchListFocused) {\n      this.closeSearchList()\n      }\n    }, 100)\n\n    this.debouncedSearch = Aeppic.debounce(async () => {\n      const searchText = locals.text\n      console.log(searchText)\n\n      locals.searching ++\n      \n      try {\n        const searchResult = await Aeppic.Commands.execute(params.lookupCommandId, document, { searchText })\n      \n        if (searchText !== locals.text) {\n          return\n        }\n\n        console.log('sr', searchResult)\n        \n        if (searchResult.ok) {\n          locals.searchResults = searchResult.value\n        } else {\n          locals.searchResults = []\n        }\n\n      } finally {\n        locals.searching --\n      }\n\n      await Aeppic.nextRenderPass()\n\n      $refs.searchList.scrollTop = 0\n    }, 100)\n  }\n\n  async handleInputKeyPress(e) {\n    if (e.keyCode === 40) {\n      console.log('keypress', e.code)\n\n      locals.searchShow = true\n      await Aeppic.nextRenderPass()\n      \n      const FOCUSABLE_SELECTOR = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n      //const firstFocusableChild = $refs.searchList.querySelector(':focusable:not([tabindex=\"-1\"])')\n      const firstFocusableChild = $refs.searchList.querySelector(FOCUSABLE_SELECTOR)\n\n      firstFocusableChild.focus()\n    }\n  }\n\n  // getAllFocusableElementsInSearchList() {\n  //   const FOCUSABLE_SELECTOR = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n  //   return $refs.searchList?.querySelectorAll(FOCUSABLE_SELECTOR) ?? []\n  // }\n\n  handleKeyDownInSearchList(e, r) {\n    if (e.code === 'ArrowDown') {\n      this.focusNextElement(e.srcElement, { containIn: $refs.searchList })\n    } else if (e.code === 'ArrowUp') {\n      this.focusPreviousElement(e.srcElement, { containIn: $refs.searchList })\n    } else if (e.code == 'Tab') {\n      locals.searchListFocused = false\n      this.closeSearchList()\n    } else if (e.code == 'Enter') {\n      this.selectSearchResult(r)\n    } else {\n      console.log('kd', e) \n    }\n  }\n\n  async selectSearchResult(searchResult) {\n    console.log('selecting', searchResult)\n    this.closeSearchList()\n\n    if (!searchResult) {\n      return\n    }\n    \n    if (params.selectedCommandId) {\n      const result = await Aeppic.Commands.execute(params.selectedCommandId, document, { selected: searchResult })\n      console.log(result)\n\n      if (result.ok) {\n        setField(result.value)\n        locals.text = result.value\n        locals.ignoreTextChangeOnce = true\n\n        //setField()\n        return \n      }\n    }\n\n    locals.ignoreTextChangeOnce = true\n    locals.text = searchResult.NAME\n\n    const addressField = document.data[params.addressField]\n\n    if (addressField) {\n      console.log(addressField)\n\n      addressField.city = searchResult.PLACE\n      addressField.street = searchResult.STREET\n      addressField.postalCode = searchResult.POSTCODE\n      addressField.country = searchResult.CNTRYSIGN\n\n      document.data[params.addressField] = addressField\n    }\n\n    if (document.data[params.idField] !== null) {\n      document.data[params.idField] = searchResult.CONO\n    }\n  }\n\n  async closeSearchList() {\n    locals.searchShow = false\n    await Aeppic.nextRenderPass()\n\n    const lastElement = $refs.lastElement\n    \n    if (lastElement) {\n      this.focusNextElement(lastElement)\n    }\n  }\n\n  enteringSearchList() {\n    locals.searchListFocused = true \n  }\n\n  async leavingSearchList() {\n    locals.searchListFocused = false\n    this.debouncedCloseSearchListWhenNotFocused()\n  }\n\n  onTextChanged() {\n    if (locals.ignoreTextChangeOnce) {\n      locals.ignoreTextChangeOnce = false\n      return\n    }\n\n    if (locals.text === '') {  \n      locals.searchShow = false\n    } else {\n      locals.searchShow = true\n    }\n    \n    this.debouncedSearch()\n    // $refs.results.querySelector('button').focus()\n    \n    // var buttons = $refs.results.querySelectorAll('button')\n    // buttons.forEach(function)\n\n  }\n\n  focusNextElement(activeElem, options) {\n    const element = this.findNextFocusableElement(activeElem, false)\n    \n    if (options?.containIn) {\n      if (!options.containIn.contains(element)) {\n        console.log('outside of container')\n        return\n      }\n    }\n\n    element?.focus()\n  }\n\n  focusPreviousElement(activeElem) {\n    const element = this.findNextFocusableElement(activeElem, true)\n    element?.focus()\n  }\n\n  findNextFocusableElement( activeElem, reverse ) {\n    const rootElement = $refs.rootElement?.ownerDocument\n\n    let queryString = [\n        'a:not([disabled]):not([tabindex=\"-1\"])',\n        'button:not([disabled]):not([tabindex=\"-1\"])',\n        'input:not([disabled]):not([tabindex=\"-1\"])',\n        'select:not([disabled]):not([tabindex=\"-1\"])',\n        '[tabindex]:not([disabled]):not([tabindex=\"-1\"])'\n        /* add custom queries here */\n      ].join(',')\n\n    const queryResult = Array.prototype.filter.call(rootElement.querySelectorAll(queryString), elem => {\n      /*check for visibility while always include the current activeElement*/\n      return elem.offsetWidth > 0 || elem.offsetHeight > 0 || elem === activeElem;\n    })\n\n    const indexedList = queryResult.slice().filter(elem => {\n      /* filter out all indexes not greater than 0 */\n      return elem.tabIndex == 0 || elem.tabIndex == -1 ? false : true;\n    }).sort((a, b) => {\n      /* sort the array by index from smallest to largest */\n      return a.tabIndex != 0 && b.tabIndex != 0 \n        ? (a.tabIndex < b.tabIndex ? -1 : b.tabIndex < a.tabIndex ? 1 : 0) \n        : a.tabIndex != 0 ? -1 : b.tabIndex != 0 ? 1 : 0;\n    })\n\n    const focusable = [].concat(indexedList, queryResult.filter(elem => {\n      /* filter out all indexes above 0 */\n      return elem.tabIndex == 0 || elem.tabIndex == -1 ? true : false;\n    }));\n\n    /* if reverse is true return the previous focusable element\n      if reverse is false return the next focusable element */\n    return reverse ? (focusable[focusable.indexOf(activeElem) - 1] || focusable[focusable.length - 1]) \n      : (focusable[focusable.indexOf(activeElem) + 1] || focusable[0]);\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2023-05-16T15:51:21+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","from":"","client":"server"},"modified":{"at":"2023-05-22T14:32:28+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936838,"ct":1721209936838,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"ccd0de412779e66f","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"bd7b5d4e-33b6-4fa8-8db7-47fa2f987157","v":"772c72b5849a62b1","p":"controls-folder","f":{"id":"control-form","v":"8f342d57244fe7b5","text":"Control"},"data":{"namespace":"maus","name":"fauser-customer-lookup","template":"<div class=\"block\" ref=\"rootElement\">\n  <div class=\"flex w-full items-center justify-between\">\n    <input type=\"text\"\n      @keydown=\"controller.handleInputKeyPress($event)\"\n      class=\"block\n          placeholder:italic placeholder:text-slate-400\n          focus:outline-none\n          focus:shadow-outline-blue\n          px-3\n          py-1.5\n          text-base\n          font-normal\n          text-gray-700\n          bg-white bg-clip-padding\n          border border-solid border-gray-300\n          rounded\n          transition\n          ease-in-out\n          m-0\n          focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none\"\n      v-model=\"locals.text\"\n      placeholder=\"Kundenname eingeben ...\"\n      @input=\"controller.onTextChanged()\">\n    <button ref=\"lastElement\" style=\"display:none\" class=\"fa fa-sync\n          mx-2 p-4 rounded-md margin: hover:bg-gray-300 hover:border-gray-800 focus:bg-gray-300 focus:border-gray-800\" >\n    </button>\n  </div>\n  <ul v-show=\"locals.searchShow\" ref=\"searchList\"\n    class=\"absolute max-h-64 block divide-y divide-slate-200 z-10 bg-white border border-gray-300 rounded-lg flex flex-col text-sm py-4 px-2 text-gray-500 drop-shadow-lg overflow-y-auto\"\n    @focusin=\"controller.enteringSearchList()\"\n    @focusout=\"controller.leavingSearchList()\">\n    <li v-for=\"r of locals.searchResults\"\n      tabindex=\"0\"\n      @keydown.prevent.stop=\"controller.handleKeyDownInSearchList($event, r)\"\n      @click.prevent.stop=\"controller.selectSearchResult(r)\"      \n      class=\"flex hover:bg-gray-100 py-1 px-2 rounded focus:border-blue-300 first:pt-0 last:pb-0\">\n      <div class=\"text-gray-500 sm:pr-2 w-full\">\n        <h3 class=\"text-l font-bold text-gray-900 text-ellipsis overflow-hidden\">{{r.NAME}}<small class=\"w-auto float-right\">&nbsp;{{r.CUSTNO}} ({{r.CNTRYSIGN}})</small></h3>\n        <div>{{r.POSTCODE}}&nbsp;{{r.PLACE}}</div>\n        <p v-if=\"r.ADDITION && r.ADDITION.trim() != ''\" class=\"mt-2 hidden text-sm sm:block\">\n          {{r.ADDITION}}\n        </p>\n      </div>\n    </li>\n  </ul> \n</div>\n","css":"","controller":"class Controller {\n  init() {\n    locals.params = params\n    locals.text = getField()\n\n    locals.searchShow = false\n    locals.searchListFocused = false\n    locals.searching = false\n    locals.searchResults = []\n    locals.searchFailure = false\n    locals.ignoreTextChangeOnce = false\n\n    this.debouncedCloseSearchListWhenNotFocused = Aeppic.debounce(() => {\n      if (!locals.searchListFocused) {\n      this.closeSearchList()\n      }\n    }, 100)\n\n    this.debouncedSearch = Aeppic.debounce(async () => {\n      const searchText = locals.text\n      console.log(searchText)\n\n      locals.searching = true\n      const searchResult = await Aeppic.Commands.execute(params.lookupCommandId, document, { searchText })\n      \n      if (searchText !== locals.text) {\n        return\n      }\n\n      console.log('sr', searchResult)\n\n      locals.searching = false\n\n      if (searchResult.ok) {\n        locals.searchResults = searchResult.value\n      } else {\n        locals.searchResults = []\n      }\n\n      await Aeppic.nextRenderPass()\n\n      $refs.searchList.scrollTop = 0\n    }, 100)\n  }\n\n  async handleInputKeyPress(e) {\n    if (e.keyCode === 40) {\n      console.log('keypress', e.code)\n\n      locals.searchShow = true\n      await Aeppic.nextRenderPass()\n      \n      const FOCUSABLE_SELECTOR = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n      //const firstFocusableChild = $refs.searchList.querySelector(':focusable:not([tabindex=\"-1\"])')\n      const firstFocusableChild = $refs.searchList.querySelector(FOCUSABLE_SELECTOR)\n\n      firstFocusableChild.focus()\n    }\n  }\n\n  // getAllFocusableElementsInSearchList() {\n  //   const FOCUSABLE_SELECTOR = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n  //   return $refs.searchList?.querySelectorAll(FOCUSABLE_SELECTOR) ?? []\n  // }\n\n  handleKeyDownInSearchList(e, r) {\n    if (e.code === 'ArrowDown') {\n      this.focusNextElement(e.srcElement, { containIn: $refs.searchList })\n    } else if (e.code === 'ArrowUp') {\n      this.focusPreviousElement(e.srcElement, { containIn: $refs.searchList })\n    } else if (e.code == 'Tab') {\n      locals.searchListFocused = false\n      this.closeSearchList()\n    } else if (e.code == 'Enter') {\n      this.selectSearchResult(r)\n    } else {\n      console.log('kd', e) \n    }\n  }\n\n  async selectSearchResult(searchResult) {\n    console.log('selecting', searchResult)\n    this.closeSearchList()\n\n    if (!searchResult) {\n      return\n    }\n    \n    if (params.selectedCommandId) {\n      const result = await Aeppic.Commands.execute(params.selectedCommandId, document, { selected: searchResult })\n      console.log(result)\n\n      if (result.ok) {\n        setField(result.value)\n        locals.text = result.value\n        locals.ignoreTextChangeOnce = true\n\n        //setField()\n        return \n      }\n    }\n\n    locals.ignoreTextChangeOnce = true\n    locals.text = searchResult.NAME\n\n    const addressField = document.data[params.addressField]\n\n    if (addressField) {\n      console.log(addressField)\n\n      addressField.city = searchResult.PLACE\n      addressField.street = searchResult.STREET\n      addressField.postalCode = searchResult.POSTCODE\n      addressField.country = searchResult.CNTRYSIGN\n\n      document.data[params.addressField] = addressField\n    }\n\n    if (document.data[params.idField] !== null) {\n      document.data[params.idField] = searchResult.CONO\n    }\n  }\n\n  async closeSearchList() {\n    locals.searchShow = false\n    await Aeppic.nextRenderPass()\n\n    const lastElement = $refs.lastElement\n    \n    if (lastElement) {\n      this.focusNextElement(lastElement)\n    }\n  }\n\n  enteringSearchList() {\n    locals.searchListFocused = true \n  }\n\n  async leavingSearchList() {\n    locals.searchListFocused = false\n    this.debouncedCloseSearchListWhenNotFocused()\n  }\n\n  onTextChanged() {\n    if (locals.ignoreTextChangeOnce) {\n      locals.ignoreTextChangeOnce = false\n      return\n    }\n\n    if (locals.text === '') {  \n      locals.searchShow = false\n    } else {\n      locals.searchShow = true\n    }\n    \n    this.debouncedSearch()\n    // $refs.results.querySelector('button').focus()\n    \n    // var buttons = $refs.results.querySelectorAll('button')\n    // buttons.forEach(function)\n\n  }\n\n  focusNextElement(activeElem, options) {\n    const element = this.findNextFocusableElement(activeElem, false)\n    \n    if (options?.containIn) {\n      if (!options.containIn.contains(element)) {\n        console.log('outside of container')\n        return\n      }\n    }\n\n    element?.focus()\n  }\n\n  focusPreviousElement(activeElem) {\n    const element = this.findNextFocusableElement(activeElem, true)\n    element?.focus()\n  }\n\n  findNextFocusableElement( activeElem, reverse ) {\n    const rootElement = $refs.rootElement?.ownerDocument\n\n    let queryString = [\n        'a:not([disabled]):not([tabindex=\"-1\"])',\n        'button:not([disabled]):not([tabindex=\"-1\"])',\n        'input:not([disabled]):not([tabindex=\"-1\"])',\n        'select:not([disabled]):not([tabindex=\"-1\"])',\n        '[tabindex]:not([disabled]):not([tabindex=\"-1\"])'\n        /* add custom queries here */\n      ].join(',')\n\n    const queryResult = Array.prototype.filter.call(rootElement.querySelectorAll(queryString), elem => {\n      /*check for visibility while always include the current activeElement*/\n      return elem.offsetWidth > 0 || elem.offsetHeight > 0 || elem === activeElem;\n    })\n\n    const indexedList = queryResult.slice().filter(elem => {\n      /* filter out all indexes not greater than 0 */\n      return elem.tabIndex == 0 || elem.tabIndex == -1 ? false : true;\n    }).sort((a, b) => {\n      /* sort the array by index from smallest to largest */\n      return a.tabIndex != 0 && b.tabIndex != 0 \n        ? (a.tabIndex < b.tabIndex ? -1 : b.tabIndex < a.tabIndex ? 1 : 0) \n        : a.tabIndex != 0 ? -1 : b.tabIndex != 0 ? 1 : 0;\n    })\n\n    const focusable = [].concat(indexedList, queryResult.filter(elem => {\n      /* filter out all indexes above 0 */\n      return elem.tabIndex == 0 || elem.tabIndex == -1 ? true : false;\n    }));\n\n    /* if reverse is true return the previous focusable element\n      if reverse is false return the next focusable element */\n    return reverse ? (focusable[focusable.indexOf(activeElem) - 1] || focusable[focusable.length - 1]) \n      : (focusable[focusable.indexOf(activeElem) + 1] || focusable[0]);\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2023-01-18T15:59:52+00:00","by":"427610a9-39dc-4014-9176-5f72aeb23263","client":"server","from":""},"modified":{"at":"2023-02-08T08:33:21+00:00","backedBy":null,"by":"427610a9-39dc-4014-9176-5f72aeb23263","client":"server","from":"","impersonatedBy":null},"t":1721144004093,"ct":1721144004093,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"bc0e3fe2612d9bbb","f":{"id":"control-form","v":"7672ea0b1b6d4c70","text":"Control"}},"meta":{"modified":{"at":"2024-07-16T17:33:24+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"47b22131-914a-44f6-b43b-3ee83e22936d","v":"c5ed2db560eea0bd","p":"controls-folder","f":{"id":"control-form","v":"8f342d57244fe7b5","text":"Control"},"data":{"namespace":"","name":"field-help","template":"<template v-if=\"locals.helpExtended?.html\" >\n  <button  \n    tabindex=\"-1\"\n    class=\"flex flex-col block m-auto hover:text-black\"\n    :class=\"'text-gray-400'\"\n    @mouseenter.prevent.stop=\"controller.openDetailedHelp($event)\"\n    @mouseleave.prevent.stop=\"$refs.detail.close()\"\n    @click.prevent.stop=\"controller.openDetailedHelp($event)\"\n    >\n    <ae-icon class=\"far fa-info-circle\"></ae-icon>\n  </button>\n    \n  <ae-popup ref=\"detail\" :arrow=\"false\" :close-delay=\"150\" :max-width=\"1200\" >\n    <div slot=\"popup\"\n      v-if=\"locals.helpExtended\"\n      class=\"w-full overflow-auto rounded-md bg-white p-3 text-base shadow-lg \"\n      @mouseenter.prevent.stop=\"controller.openDetailedHelp($event)\"\n      @mouseleave.prevent.stop=\"$refs.detail.close()\"\n      v-html=\"locals.helpExtended.html\">\n    </div>\n  </ae-popup>\n</template>","css":"","controller":"init() {\n  locals.help = fieldInfo.settings.help\n  locals.helpExtended = fieldInfo.settings.helpExtended\n}\n\nopenDetailedHelp(event) {\n  $refs.detail.show(event)\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-07-04T07:20:52+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-07-08T09:27:25+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721144004093,"ct":1721144004093,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"8b899017384c8984","f":{"id":"control-form","v":"7672ea0b1b6d4c70","text":"Control"}},"meta":{"modified":{"at":"2024-07-16T17:33:24+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"428c1ea9-9648-4528-a33c-6198b681ea65","v":"e3eca1c8f6801215","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"field-picker","template":"<div  ref=\"select\"\n      @click.stop=\"controller.showPicker()\"\n      class=\"ae-form-cell__input _CSS-multiselect\"\n      v-if=\"locals.sourceForm\"\n>\n  <!-- TODO: use sorted items -->\n  <div class=\"_CSS-multiselect__items\">\n    <template v-for=\"(value, index) in field\" v-if=\"value\">\n      <div v-if=\"value !== null\" class=\"_CSS-multiselect__item\">\n        <span class=\"_CSS-multiselect__item-text\">\n          {{ controller.label(value) && Aeppic.translate(controller.label(value)) }}\n        </span>\n        <span class=\"_CSS-multiselect__item-text\" v-if=\"field.length > 1 && index !== field.length -1\">,&nbsp;</span>\n      </div>\n      <div v-else></div>\n    </template>\n  </div>\n  <button v-if=\"!readonly && field.length\" class=\"ae-button ae-button--tertiary\" @click.stop=\"field = []\">\n    <ae-icon class=\"fa fa-times\"></ae-icon>\n  </button>\n  <button v-if=\"!readonly\" class=\"ae-button ae-button--secondary\" @click.stop=\"controller.showPicker()\">\n    <ae-icon class=\"fa fa-caret-down\"></ae-icon>\n  </button>\n</div> \n   \n<ae-layout\n  ref=\"popup\"\n  layout-id=\"ed2cc4c2-d2a0-4969-8da7-49fb3d539cc9\"\n  param-list-id=\"495e2332-fb63-445a-a2ff-957d03508e98\"\n  :param-nofilter=\"true\"\n  :params=\"{ readonly: readonly, items: locals.fieldNames, selectedItems: field, itemLabels: locals.labels, keepOpenAfterSelection: !params.closeOnSelected, watchItems: false }\"\n  @selected=\"controller.selected($event)\"\n  ></ae-layout>\n\n<ae-layout\n  ref=\"dialog\"\n  layout-id=\"32dbb3b6-f883-4647-8d21-6f1e0fb83b90\"\n  param-list-id=\"495e2332-fb63-445a-a2ff-957d03508e98\"\n  :param-nofilter=\"true\"\n  :params=\"{ readonly: readonly, items: locals.fieldNames, selectedItems: field, itemLabels: locals.labels, label: label, keepOpenAfterSelection: !params.closeOnSelected, watchItems: false }\"\n  @selected=\"controller.selected($event)\"\n  ></ae-layout>\n\n","css":"._CSS-multiselect {\n  display: flex;\n  align-items: center;\n  flex-wrap: wrap;\n}\n\n._CSS-multiselect__items {\n  flex: 1;\n\n  display: flex;\n  align-items: center;\n  flex-wrap: wrap;\n}\n\n.ae-desktop ._CSS-multiselect > *, .ae-tablet ._CSS-multiselect > * {\n  overflow-x: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n._CSS-button-add {\n  display: inline-block;\n  padding: 0.3rem;\n}\n\n._CSS-button-icon {\n  color: var(--ae-theme-text-icon-on-background);\n}\n\n._CSS_button-caret {\n  margin-left: auto;\n  overflow: visible !important;\n}\n\n._CSS-multiselect__item {\n  display: flex;\n}\n\n._CSS-multiselect__item-text {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}","controller":"class Controller {\n  async init() {\n    locals.fieldNames = []\n    locals.labels = []\n    locals.sourceForm = null\n    locals.sourceForm = await Aeppic.getForm(params.sourceFormId) \n\n\n    const allFields = params.fields?.split(',') || locals.sourceForm.fields.map(field => field.name)\n    const labels = []\n    const usedFields = []\n\n    for (const fieldName of allFields) {\n      if (fieldName === 'created.at') {\n        labels.push('Erstelldatum')\n      } else if (fieldName === 'modified.at') {\n        labels.push('Änderungsdatum')\n      } else {\n        const placeholder = locals.sourceForm.getPlaceholder(fieldName.replace('data.', ''))\n \n        if (!placeholder) {\n          console.log('no placeholder', { fieldName })\n          continue \n        }\n\n        if (!params.fields && this.hidePlaceholderFromUser(placeholder)) {\n          console.log('hide', { fieldName })\n          continue \n        } \n  \n        labels.push(placeholder.label)\n      }\n\n      usedFields.push(fieldName)\n    }\n \n    const sortedLabels = labels.slice().sort()\n    const sortedFieldNames = sortedLabels.map((label) => usedFields[labels.findIndex(l => l === label)])\n   \n console.log({ sourceForm: locals.sourceForm, sortedFieldNames, sortedLabels, usedFields })\n    \n    locals.labels = sortedLabels\n    locals.fieldNames = sortedFieldNames\n  }\n\n  hidePlaceholderFromUser(placeholder) {\n    return form.shouldApplyStyleToPlaceholder(placeholder, 'hidden') //&& placeholder.field.required\n  }\n\n  label(nameOfField) {\n    // console.log(nameOfField, locals.labels, locals.fieldNames, locals.fieldNames.findIndex(f => f === nameOfField))\n    return locals.labels[locals.fieldNames.findIndex(f => f === nameOfField)]\n  }  \n\n  showPicker() {\n    if (readonly) {\n      return\n    }\n\n    if (Aeppic.Device === 'Phone') {\n      $refs.dialog.$emit('show')\n    } else {\n      $refs.popup.$emit('toggle', {\n        target: $refs.select\n      })\n    }\n  }\n\n  closePicker() {\n    if (Aeppic.Device === 'Phone') {\n      $refs.dialog.$emit('close')\n      return\n    }\n\n    $refs.popup.$emit('close', {\n      target: $refs.select\n    })\n  }\n\n  selected(item) {\n    if (!item) {\n      return\n    } \n\n    this.addRemoveItem(item)\n  }\n\n  addRemoveItem(item) {\n    const field = getField()\n\n    const valueIndexInField = field.indexOf(item)\n\n    if (valueIndexInField !== -1) {\n      field.splice(valueIndexInField, 1)\n      setField(field)\n\n      return\n    }\n\n    while (field.length >= this.max) {\n      field.pop()\n    }\n\n    field.push(item)\n\n    setField(field)\n  }\n\n  removeItem(itemValue) {\n    const field = getField()\n    field.splice(field.indexOf(itemValue), 1)\n    setField(field)\n  }\n\n  get max() {\n    let max = fieldInfo.cardinality.max\n    if (max) {\n      max = max.toLowerCase() === 'n' ? 10000 : parseInt(max)\n    } else {\n      max = 1\n    }\n    return max\n  }\n\n  get min() {\n    const min = fieldInfo.cardinality.min\n    return min ? parseInt(min) : 0\n  }\n}\n","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-10-12T11:48:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2023-06-14T15:24:18+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936838,"ct":1721209936838,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"62afceeb67ecdb45","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"cloneOf":{"id":"428c1ea9-9648-4528-a33c-6198b681ea65","text":"field-picker","v":"e3eca1c8f6801215"},"created":{"at":"2025-12-05T12:18:32+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"ct":1764937112697,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    locals.fieldNames = []\n    locals.labels = []\n    locals.sourceForm = null\n    locals.sourceForm = await Aeppic.getForm(params.sourceFormId) \n\n    const allFields = params.fields?.split(',') || locals.sourceForm.fields.map(field => field.name)\n    const labels = []\n    const usedFields = []\n\n    for (const fieldName of allFields) {\n      if (fieldName === 'created.at') {\n        labels.push('Erstelldatum')\n      } else if (fieldName === 'modified.at') {\n        labels.push('Änderungsdatum')\n      } else {\n        const placeholder = locals.sourceForm.getPlaceholder(fieldName.replace('data.', ''))\n \n        if (!placeholder) {\n          console.log('no placeholder', { fieldName })\n          continue \n        }\n\n        if (!params.fields && this.hidePlaceholderFromUser(placeholder)) {\n          console.log('hide', { fieldName })\n          continue \n        } \n  \n        labels.push(placeholder.label)\n      }\n\n      usedFields.push(fieldName)\n    }\n \n    const sortedLabels = labels.slice().sort()\n    const sortedFieldNames = sortedLabels.map((label) => usedFields[labels.findIndex(l => l === label)])\n   \n console.log({ sourceForm: locals.sourceForm, sortedFieldNames, sortedLabels, usedFields })\n    \n    locals.labels = sortedLabels\n    locals.fieldNames = sortedFieldNames\n  }\n\n  hidePlaceholderFromUser(placeholder) {\n    return form.shouldApplyStyleToPlaceholder(placeholder, 'hidden') //&& placeholder.field.required\n  }\n\n  label(nameOfField) {\n    // console.log(nameOfField, locals.labels, locals.fieldNames, locals.fieldNames.findIndex(f => f === nameOfField))\n    return locals.labels[locals.fieldNames.findIndex(f => f === nameOfField)]\n  }  \n\n  showPicker() {\n    if (readonly) {\n      return\n    }\n\n    $refs.popup.show({\n      target: $refs.select\n    })\n  }\n\n  closePicker() {\n    $refs.popup.close()\n  }\n\n  selected(item) {\n    if (!item) {\n      return\n    } \n\n    this.addRemoveItem(item)\n  }\n\n  addRemoveItem(item) {\n    const field = getField()\n\n    const valueIndexInField = field.indexOf(item)\n\n    if (valueIndexInField !== -1) {\n      field.splice(valueIndexInField, 1)\n      setField(field)\n\n      return\n    }\n\n    while (field.length >= this.max) {\n      field.pop()\n    }\n\n    field.push(item)\n\n    setField(field)\n  }\n\n  removeItem(itemValue) {\n    const field = getField()\n    field.splice(field.indexOf(itemValue), 1)\n    setField(field)\n  }\n\n  get max() {\n    let max = fieldInfo.cardinality.max\n    if (max) {\n      max = max.toLowerCase() === 'n' ? 10000 : parseInt(max)\n    } else {\n      max = 1\n    }\n    return max\n  }\n\n  get min() {\n    const min = fieldInfo.cardinality.min\n    return min ? parseInt(min) : 0\n  }\n}\n","css":"._CSS-multiselect {\n  display: flex;\n  align-items: center;\n  flex-wrap: wrap;\n}\n\n._CSS-multiselect__items {\n  flex: 1;\n\n  display: flex;\n  align-items: center;\n  flex-wrap: wrap;\n}\n\n.ae-desktop ._CSS-multiselect > *, .ae-tablet ._CSS-multiselect > * {\n  overflow-x: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n._CSS-button-add {\n  display: inline-block;\n  padding: 0.3rem;\n}\n\n._CSS-button-icon {\n  color: var(--ae-theme-text-icon-on-background);\n}\n\n._CSS_button-caret {\n  margin-left: auto;\n  overflow: visible !important;\n}\n\n._CSS-multiselect__item {\n  display: flex;\n}\n\n._CSS-multiselect__item-text {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}","description":"","device":"ALL","label":"","limitTypes":false,"name":"field-picker","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<div  ref=\"select\"\n      @click.stop=\"controller.showPicker()\"\n      class=\"ae-form-cell__input _CSS-multiselect\"\n      v-if=\"locals.sourceForm\"\n>\n  <!-- TODO: use sorted items -->\n  <div class=\"_CSS-multiselect__items\">\n    <template v-for=\"(value, index) in field\" v-if=\"value\">\n      <div v-if=\"value !== null\" class=\"_CSS-multiselect__item\">\n        <span class=\"_CSS-multiselect__item-text\">\n          {{ controller.label(value) && Aeppic.translate(controller.label(value)) }}\n        </span>\n        <span class=\"_CSS-multiselect__item-text\" v-if=\"field.length > 1 && index !== field.length -1\">,&nbsp;</span>\n      </div>\n      <div v-else></div>\n    </template>\n  </div>\n  <button v-if=\"!readonly && field.length\" class=\"ae-button ae-button--tertiary\" @click.stop=\"field = []\">\n    <ae-icon class=\"fa fa-times\"></ae-icon>\n  </button>\n  <button v-if=\"!readonly\" class=\"ae-button ae-button--secondary\" @click.stop=\"controller.showPicker()\">\n    <ae-icon class=\"fa fa-caret-down\"></ae-icon>\n  </button>\n</div> \n \n <ae-popup ref=\"popup\">\n   <div class=\"flex flex-col divide-y\">\n     <div v-for=\"(label, index) of locals.labels\"\n          @click.stop=\"controller.selected(locals.fieldNames[index])\"  \n       class=\"px-1 py-2 hover:bg-neutral-200 cursor-pointer\"\n       :class=\"{ 'font-bold': field.includes(locals.fieldNames[index] )}\"\n       >\n       {{label}}\n     </div>\n   </div>\n </ae-popup>\n   \n<!-- <ae-layout\n  ref=\"popup\"\n  layout-id=\"ed2cc4c2-d2a0-4969-8da7-49fb3d539cc9\"\n  param-list-id=\"495e2332-fb63-445a-a2ff-957d03508e98\"\n  :param-nofilter=\"true\"\n  :params=\"{ readonly: readonly, items: locals.fieldNames, selectedItems: field, itemLabels: locals.labels, keepOpenAfterSelection: !params.closeOnSelected, watchItems: false }\"\n  @selected=\"controller.selected($event)\"\n  ></ae-layout>\n\n<ae-layout\n  ref=\"dialog\"\n  layout-id=\"32dbb3b6-f883-4647-8d21-6f1e0fb83b90\"\n  param-list-id=\"495e2332-fb63-445a-a2ff-957d03508e98\"\n  :param-nofilter=\"true\"\n  :params=\"{ readonly: readonly, items: locals.fieldNames, selectedItems: field, itemLabels: locals.labels, label: label, keepOpenAfterSelection: !params.closeOnSelected, watchItems: false }\"\n  @selected=\"controller.selected($event)\"\n  ></ae-layout> -->\n\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"d9a66219-e795-4904-8fcf-126b68a0b45d","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-12-05T13:07:25+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"originalCloneOf":{"id":"428c1ea9-9648-4528-a33c-6198b681ea65","text":"field-picker","v":"e3eca1c8f6801215"},"p":"controls-folder","previous":{"v":"a61254dc54831614"},"stamps":[],"t":1764940045323,"v":"fa7ab40a49fa3173"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-08-24T12:19:51+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936838,"data":{"cardinality":"BOTH","controller":"class FileControl {\n  async init() {\n    this.form = null\n    locals.upload = null \n    locals.datei = null\n    // locals.upload = {progressPercent:0.3,status:'started'}\n\n    this.form = await Aeppic.getFormForDocument(document)\n\n    $on('field-updated', (update) => {\n      console.log(123, getField(), update)\n      this.updateFieldAsFakeFile()\n    })\n\n    this.updateFieldAsFakeFile()\n  }\n\n  async updateFieldAsFakeFile() {\n    const field = getField()\n    console.log('updateFieldAsFakeFile', field )\n    const datei = await Aeppic.new('dbee6e16-d63a-493c-bba6-505f79918f9f', document)\n\n    datei.data.name = field.name\n    datei.data.datei = field\n\n\n    locals.datei = datei\n  }\n\n  isRequiredFailing() {\n    if (!this.form || readonly) {\n      return false\n    }\n\n    const validation = this.form.validateDocumentDataField(document.data, fieldName)\n    return validation && !!validation.required\n  }\n\n  get isUploading() {\n    const status = Aeppic.Uploads.getUploadStatusForField(document, fieldName)\n\n    if (status) {\n      locals.upload = status\n    } else {\n      locals.upload = null\n    }\n\n    return !!locals.upload\n  }\n\n  resetField() {\n    setField(fieldInfo.defaultValue)\n  }\n\n  get size() {\n    const GIGABYTE_AS_BYTE = 1000000000\n    const MEGABYTE_AS_BYTE = 1000000\n    const KILOBYTE_AS_BYTE = 1000\n\n    const field = getField()\n    const size = field && field.size\n\n    if (!size) {\n      return {\n        size: 0,\n        suffix: 'B'\n      }\n    }\n\n    let divisor = 1\n    let suffix = 'B'\n\n    if (size > GIGABYTE_AS_BYTE) {\n      divisor = GIGABYTE_AS_BYTE\n      suffix = 'GB'\n    } else if (size > MEGABYTE_AS_BYTE) {\n      divisor = MEGABYTE_AS_BYTE\n      suffix = 'MB'\n    } else if (size > KILOBYTE_AS_BYTE) {\n      divisor = KILOBYTE_AS_BYTE\n      suffix = 'KB'\n    }\n\n    return `${(size / divisor).toFixed(0)} ${suffix}`\n  }\n\n  get readonly() {\n    if (params.readonly === true) {\n      return true\n    }\n\n    if (typeof readonly === 'boolean') {\n      return readonly\n    }\n\n    return false\n  }\n\n\n  get hasAnyAttachment() {\n    if (!fieldInfo.cardinality) {\n      return !!document.data[fieldName].id\n    }\n\n    if (fieldInfo.cardinality) {\n      return !!document.data[fieldName].length\n    }\n  }\n\n  get allowedTypes() {\n    if (params.types) {\n      return params.types\n    }\n\n    if (fieldInfo.subType === 'image') {\n      return 'image/*'\n    }\n\n    return ''\n  }\n}","css":"._CSS {\n  display: flex;\n}\n\n._CSS-field {\n  display:flex;\n  flex-direction: column;\n  flex: 1;\n}\n\n._CSS-field--expanded {\n  flex: 1;\n}\n\n._CSS-file {\n  display: flex;\n  align-items: center;\n}\n\n._CSS-file__info {\n  min-width: 0;\n  margin-left: var(--ae-size-4);\n  display: flex;\n  flex-flow: column;\n}\n\n._CSS-file__description {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  flex: 1;\n  display: flex;\n  flex-flow: column;\n}\n","description":"","device":"ALL","label":"","limitTypes":false,"name":"file","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<div class=\"border rounded-md bg-white flex flex-col gap-2 w-full\"\n    :class=\"{ 'border-transparent': params.noBorder, 'border-dashed p-2': !params.noBorder }\"\n>\n  <ae-layout  v-show=\"params.fileInputLayoutId || !field.dataUrl\"\n              layout-id=\"8ae0359f-4d25-4d17-baf0-f69c006bd4ad\"\n              :param-show-ui=\"true\"\n              :param-readonly=\"controller.readonly\"\n              :param-document=\"document\"\n              :param-field-to-add-to=\"fieldName\"\n              :param-flatten=\"true\"\n              :param-file-form-id=\"params.fileFormId\"\n              :param-file-form-field=\"params.fileFormField\"\n              :param-file-name-to-field=\"params.fileNameToField\"\n              :param-types=\"controller.allowedTypes\"\n              :param-field-name=\"fieldName\"\n              :param-file-input-layout-id=\"params.fileInputLayoutId\"\n              ref=\"container\"\n              class=\"flex-1 w-full\"\n              @added=\"controller.updateFieldAsFakeFile()\"\n              \n  ></ae-layout>\n  <div v-if=\"!params.fileInputLayoutId && field.dataUrl\"\n      @click.stop=\"$refs.preview.$emit('show')\" \n      class=\"flex-1 flex items-center\"\n  >\n    <div class=\"flex gap-2  items-center \">\n      <ae-design name=\"rd:thumbnail\" class=\"rounded-md overflow-hidden shrink-0 border border-neutral-100\" \n                :document=\"document\" :param-field-name=\"fieldName\" param-size=\"64px\"\n                :param-debug=\"false\"\n      ></ae-design>\n\n      <div class=\"flex flex-col gap-1 overflow-hidden\">\n        <div class=\"flex gap-2\">\n          <ae-design class=\"text-neutral-600 w-full overflow-hidden\" name=\"title\" :document=\"locals.datei\" :watch=\"false\"></ae-design>\n        </div>\n        <div class=\"flex gap-2 text-sm text-neutral-600 items-center\">\n          <ae-design name=\"rd:icon\" :document=\"locals.datei\" :watch=\"false\"></ae-design>\n          <ae-design name=\"rd:size\" :document=\"locals.datei\" :watch=\"false\"></ae-design>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"flex-1\"></div>\n\n    <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n              param-icon=\"far fa-trash\"\n              @selected=\"controller.resetField()\"\n              ref=\"button\"\n              :param-disabled=\"controller.readonly\"\n              class=\"text-neutral-600 shrink-0\"\n    ></ae-layout> \n  </div>\n\n</div>\n\n<!-- <div class=\"_CSS-field xae-form-cell__input\" \n    :class=\"{ 'ae-form-cell__input--invalid': controller.isRequiredFailing(), '_CSS-field--expanded': controller.hasAnyAttachment,  'ae-form-cell__input--no-border': params.withoutBorder || params.noBorder }\"\n>\n  <ae-layout  v-show=\"params.fileInputLayoutId || !field.dataUrl\"\n              layout-id=\"8ae0359f-4d25-4d17-baf0-f69c006bd4ad\"\n              :param-show-ui=\"true\"\n              :param-readonly=\"controller.readonly\"\n              :param-document=\"document\"\n              :param-field-to-add-to=\"fieldName\"\n              :param-flatten=\"true\"\n              :param-file-form-id=\"params.fileFormId\"\n              :param-file-form-field=\"params.fileFormField\"\n              :param-file-name-to-field=\"params.fileNameToField\"\n              :param-types=\"controller.allowedTypes\"\n              :param-field-name=\"fieldName\"\n              :param-file-input-layout-id=\"params.fileInputLayoutId\"\n              ref=\"container\"\n              class=\"_CSS-input\"\n  ></ae-layout>\n\n  <div v-if=\"!params.fileInputLayoutId && field.dataUrl\" class=\"_CSS-file\" @click=\"$refs.preview.$emit('show')\">\n    <ae-design name=\"thumbnail\" :document=\"document\" :watch=\"false\" :param-field-name=\"fieldName\"></ae-design>\n    <div class=\"_CSS-file__info\">\n      <span class=\"_CSS-file__description\">\n        <span v-if=\"field.name\" class=\"_CSS-file__name\">{{field.name}}</span>\n        <span v-if=\"field.size\">{{controller.size}}</span>\n      </span>\n    </div>\n    <div style=\"flex: 1;\"></div>\n    <ae-design name=\"ref-remove\" :document=\"document\" v-if=\"!controller.readonly\" @remove=\"controller.resetField()\"></ae-design>\n  </div>\n\n  <div class=\"ae-progress--indeterminate\" title=\"uploading\" v-if=\"controller.isUploading && locals.upload.progressPercent == 0\">\n  </div>\n  <div v-else-if=\"locals.upload\" style=\"display:flex;transform:scaleX(-1)\">\n    <div class=\"ae-progress\" :style=\"{ 'padding-left': (100 - (locals.upload.progressPercent * 100)).toString() + '%' }\" ></div>\n  </div>\n</div>\n-->\n\n<ae-layout\n  ref=\"preview\"\n  layout-id=\"bbd6d99f-571d-422f-a2bc-15d3f8ecfae8\"\n  :param-file-name=\"field.name\"\n  :param-field-name=\"fieldName\"\n  :param-document=\"document\"\n></ae-layout> \n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"945e7650-7883-4552-a73d-f047404eabdd","inheritedLocks":[null,null,null],"locks":[],"meta":{},"modified":{"at":"2026-02-06T09:42:20+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"e77082565d0ef2fc"},"stamps":[],"t":1770370940179,"v":"40ee3543dc85807e"},{"id":"cfb24716-00f2-4597-be43-11a975793bdf","v":"6d0ecacdc578470a","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"file","template":"\n<div class=\"_CSS-field ae-form-cell__input\" \n    :class=\"{ 'ae-form-cell__input--invalid': controller.isRequiredFailing(), '_CSS-field--expanded': controller.hasAnyAttachment,  'ae-form-cell__input--no-border': params.withoutBorder || params.noBorder }\"\n>\n  <ae-layout  v-show=\"params.fileInputLayoutId || !field.dataUrl\"\n              layout-id=\"f5a5c2a0-bf9c-4b44-b05b-8e545156df10\"\n              :param-show-ui=\"true\"\n              :param-readonly=\"controller.readonly\"\n              :param-document=\"document\"\n              :param-field-to-add-to=\"fieldName\"\n              :param-flatten=\"true\"\n              :param-file-form-id=\"params.fileFormId\"\n              :param-file-form-field=\"params.fileFormField\"\n              :param-file-name-to-field=\"params.fileNameToField\"\n              :param-types=\"params.types\"\n              :param-field-name=\"fieldName\"\n              :param-file-input-layout-id=\"params.fileInputLayoutId\"\n              ref=\"container\"\n              class=\"_CSS-input\"\n  ></ae-layout>\n\n  <div v-if=\"!params.fileInputLayoutId && field.dataUrl\" class=\"_CSS-file\" @click=\"$refs.preview.$emit('show')\">\n    <ae-design name=\"thumbnail\" :document=\"document\" :watch=\"false\" :param-field-name=\"fieldName\"></ae-design>\n    <div class=\"_CSS-file__info\">\n      <span class=\"_CSS-file__description\">\n        <span v-if=\"field.name\" class=\"_CSS-file__name\">{{field.name}}</span>\n        <span v-if=\"field.size\">{{controller.size}}</span>\n      </span>\n    </div>\n    <div style=\"flex: 1;\"></div>\n    <ae-design name=\"ref-remove\" :document=\"document\" v-if=\"!controller.readonly\" @remove=\"controller.resetField()\"></ae-design>\n  </div>\n\n  <div class=\"ae-progress--indeterminate\" title=\"uploading\" v-if=\"controller.isUploading && locals.upload.progressPercent == 0\">\n  </div>\n  <div v-else-if=\"locals.upload\" style=\"display:flex;transform:scaleX(-1)\">\n    <!-- status: {{locals.upload.status}} -->\n    <!-- {{locals.upload.progressPercent}} -->\n    <div class=\"ae-progress\" :style=\"{ 'padding-left': (100 - (locals.upload.progressPercent * 100)).toString() + '%' }\" ></div>\n  </div>\n</div>\n\n<ae-layout\n  ref=\"preview\"\n  layout-id=\"bbd6d99f-571d-422f-a2bc-15d3f8ecfae8\"\n  :param-file-name=\"field.name\"\n  :param-field-name=\"fieldName\"\n  :param-document=\"document\"></ae-layout>\n","css":"._CSS {\n  display: flex;\n}\n\n._CSS-field {\n  display:flex;\n  flex-direction: column;\n  flex: 1;\n}\n\n._CSS-field--expanded {\n  flex: 1;\n}\n\n._CSS-file {\n  display: flex;\n  align-items: center;\n}\n\n._CSS-file__info {\n  min-width: 0;\n  margin-left: var(--ae-size-4);\n  display: flex;\n  flex-flow: column;\n}\n\n._CSS-file__description {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  flex: 1;\n  display: flex;\n  flex-flow: column;\n}\n","controller":"class FileControl {\n  async init() {\n    this.form = null\n    locals.upload = null \n    // locals.upload = {progressPercent:0.3,status:'started'}\n\n    this.form = await Aeppic.getFormForDocument(document)\n  }\n\n  isRequiredFailing() {\n    if (!this.form || readonly) {\n      return false\n    }\n\n    const validation = this.form.validateDocumentDataField(document.data, fieldName)\n    return validation && !!validation.required\n  }\n\n  get isUploading() {\n    const status = Aeppic.Uploads.getUploadStatusForField(document, fieldName)\n\n    if (status) {\n      locals.upload = status\n    } else {\n      locals.upload = null\n    }\n\n    return !!locals.upload\n  }\n\n  resetField() {\n    setField(fieldInfo.defaultValue)\n  }\n\n  get size() {\n    const GIGABYTE_AS_BYTE = 1000000000\n    const MEGABYTE_AS_BYTE = 1000000\n    const KILOBYTE_AS_BYTE = 1000\n\n    const field = getField()\n    const size = field && field.size\n\n    if (!size) {\n      return {\n        size: 0,\n        suffix: 'B'\n      }\n    }\n\n    let divisor = 1\n    let suffix = 'B'\n\n    if (size > GIGABYTE_AS_BYTE) {\n      divisor = GIGABYTE_AS_BYTE\n      suffix = 'GB'\n    } else if (size > MEGABYTE_AS_BYTE) {\n      divisor = MEGABYTE_AS_BYTE\n      suffix = 'MB'\n    } else if (size > KILOBYTE_AS_BYTE) {\n      divisor = KILOBYTE_AS_BYTE\n      suffix = 'KB'\n    }\n\n    return `${(size / divisor).toFixed(0)} ${suffix}`\n  }\n\n  get readonly() {\n    if (params.readonly === true) {\n      return true\n    }\n\n    if (typeof readonly === 'boolean') {\n      return readonly\n    }\n\n    return false\n  }\n\n\n  get hasAnyAttachment() {\n    if (!fieldInfo.cardinality) {\n      return !!document.data[fieldName].id\n    }\n\n    if (fieldInfo.cardinality) {\n      return !!document.data[fieldName].length\n    }\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-01-31T15:15:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2022-07-04T13:41:49+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936838,"ct":1721209936838,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"967ca0c360bbb403","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"ff9ae3cf-9143-48f6-8ba7-b551819c8505","v":"4ea88a94815087fb","p":"91f205bc-9989-4fbe-8966-bd4ba9777c26","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"file","template":"<div v-if=\"readonly\">\n\t<a target=\"_blank\" :href=\"field.dataUrl\">\n\t\t<img :src=\"field.thumbnailUrl || '/assets/images/iconmonstr-blank-file-3.svg'\" :alt=\"label\" width=\"96px\" height=\"96px\">\n\t</a>\n</div>\n\n<div v-else>\n    <!-- // TODO: add file dropdown here  -->\n\n\t<label>name\n\t<input type=\"text\" v-model=\"field.name\">\n\t</label>\n\t<label>size\n\t<input type=\"number\" v-model=\"field.size\">\n\t</label>\n\t<label>type\n\t<input type=\"text\" v-model=\"field.type\">\n\t</label>\n\t<label>sha1\n\t<input type=\"text\" v-model=\"field.sha1\">\n\t</label>\n\t<label>dataUrl\n\t<input type=\"text\" v-model=\"field.dataUrl\">\n\t</label>\n\t<label>thumbnailUrl\n\t<input type=\"text\" v-model=\"field.thumbnailUrl\">\n\t</label>\n\t<label>iconUrl\n\t<input type=\"text\" v-model=\"field.iconUrl\">\n\t</label>\n\t<label>fileInfo.created\n\t<input type=\"text\" v-model=\"field.fileInfo.created\">\n\t</label>\n\t<label>fileInfo.read\n\t<input type=\"text\" v-model=\"field.fileInfo.read\">\n\t</label>\n\t<label>fileInfo.modified\n\t<input type=\"text\" v-model=\"field.fileInfo.modified\">\n\t</label>\n\t<label>fileInfo.imported\n\t<input type=\"text\" v-model=\"field.fileInfo.imported\">\n\t</label>\n\t<label v-if=\"fieldInfo.subType === 'image'\">mediaInfo.height\n\t\t<input type=\"text\" v-model=\"field.mediaInfo.height\">\n\t</label>\n\t<label v-if=\"fieldInfo.subType === 'image'\">mediaInfo.width\n\t\t<input type=\"text\" v-model=\"field.mediaInfo.width\">\n\t</label>\n</div>","css":"","controller":"","parameters":[],"standalone":"FIELD","cardinality":"SINGLE","limitTypes":true,"types":["file","image"],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2017-08-30T16:59:20+02:00","by":"","from":"","client":"228ea4ad-90b9-4cb1-b79f-66e86a07b75f"},"modified":{"at":"2024-07-17T09:50:17+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936839,"ct":1721209936839,"a_depth":3,"a":["system","controls-folder","91f205bc-9989-4fbe-8966-bd4ba9777c26"],"a_forms":["system-folder-form","repository-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null,null],"stamps":[],"previous":{"v":"ef282ea833934dbe","f":{"id":"control-form","v":"9bddead515b3a305","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"2cc2579e-b0fe-419e-93eb-a3479281dfd1","v":"164fd64fbb8df712","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"file-viewer","template":"<template v-if=\"field.dataUrl\" >\n  <div v-if=\"!controller.isDocumentDisplayable\" class=\"_CSS-error\">\n    <p> Diese Datei kann nicht in Bizpin angezeigt werden.</p>\n    <p>\n      Sie können diese allerdings herunterladen\n    <ae-design name=\"download-and-print-buttons\" style=\"display: inline-block;\" :document=\"document\"></ae-design></p>\n    </p>\n  </div> \n  <div v-else-if=\"locals.isCad\" style=\"display:flex;flex:1;position:relative;width:100%;height:100%\">\n    <div ref=\"forgeViewer\" style=\"display:flex;flex:1;position:relative;position:absolute;left:0;top:0;right:0;bottom:0\"></div>\n  </div>\n  <div v-else-if=\"locals.error\" class=\"_CSS-error\">\n    <ae-icon class=\"fa fa-file-exclamation\"></ae-icon>\n    <p>Beim Laden der Datei ist ein Fehler aufgetreten.</p>\n  </div>\n  <div v-else-if=\"locals.followingImagePreviewUrls.length\" class=\"_CSS-image-previews\" :style=\"{ 'background-color': locals.color }\">\n    <template v-for=\"(imageUrl, index) of locals.followingImagePreviewUrls\">\n      <div class=\"_CSS-image-previews__spinner-container\" v-show=\"!locals.followingImageLoaded[index]\">\n        <ae-layout class=\"_CSS-spinner\" layout-id=\"fefa8352-0e9a-4ba8-8349-c9402ac8aefa\"></ae-layout>\n      </div>\n      <img v-show=\"locals.followingImageLoaded[index]\" :src=\"imageUrl\" @load=\"locals.followingImageLoaded.splice(index, 1, true)\">\n    </template>\n  </div>\n  <div v-else class=\"_CSS-file-viewer\">\n    <!-- <div class=\"_CSS-spinner-container\" v-show=\"locals.loading\">\n      <ae-layout class=\"_CSS-spinner\" layout-id=\"fefa8352-0e9a-4ba8-8349-c9402ac8aefa\"></ae-layout>\n    </div> --> \n    <file-viewer  \n        ref=\"viewer\"\n        :key=\"field.dataUrl + document.v\"\n        :content-url=\"Aeppic.Content.getSrc(document, fieldName)\"\n        style=\"flex:1;\"\n        :name=\"field.name\"\n        :preview-url=\"controller.previewUrl\"\n        :filename=\"field.name\"\n        :mime-type=\"field.type\"\n        :image-maximize-ratio-switch=\"10\"\n        :image-max-scale-factor=\"5\"\n        :image-maximize-zoom=\"params.zoom || null\"\n        @loading=\"controller.onLoading()\"\n        @loaded=\"controller.onLoaded()\"\n        @error=\"controller.onError($event)\"\n        :background-color=\"locals.color\"\n        :pdf-overwrite-viewer=\"(params.useBuiltinViewer || controller.isElectron)? 'pdfjs' : null\"\n        :use-preview-url=\"controller.usePreviewUrl\"\n  ></file-viewer>\n  </div> \n</template>\n<div v-else class=\"_CSS-error\">\n  <ae-icon class=\"fa fa-file-exclamation fa-2x\"></ae-icon>\n  <p>Diese Datei hat keine Daten.</p>\n</div>\n\n<div class=\"_CSS-color\" title=\"Hintergrundfarbe ändern\" v-if=\"controller.isImage\"\n@click.stop=\"controller.toggleColor()\">\n  <div\n    class=\"_CSS-color--current\"\n    :style=\"{ 'background-color': locals.colors[0] }\"></div>\n</div>","css":"._CSS {\n  position: relative;\n\n  height: 100%;\n  width: 100%;\n}\n\n._CSS-spinner-container {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n\n._CSS-spinner {\n  width: 5rem;\n  height: 5rem;\n}\n\n._CSS-color {\n  position: absolute;\n  top: 5px;\n  left: 5px;\n  background: transparent;\n}\n\n._CSS-color:hover {\n  cursor: initial;\n}\n\n._CSS-color--current {\n  height: 42px;\n  width: 42px;\n  border-radius: 50%;\n}\n\n._CSS-error {\n  flex:1;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  flex-flow: column;\n  font-size: 2em;\n  opacity: var(--ae-opacity-1);\n\n  padding: var(--bp-page-content-padding);\n}\n\n._CSS-image-previews {\n  flex: 1;\n  display: flex;\n  align-items: center;\n  flex-flow: column;\n}\n\n._CSS-image-previews > img {\n  width: 100%;\n  padding: 8px;\n}\n\n._CSS-image-previews__spinner-container {\n  height: 100px;\n  width: 100px;\n}\n\n._CSS-file-viewer {\n  position: absolute;\n  height: 100%;\n  width: 100%;\n}\n","controller":"class Controller {\n  async init() {\n    locals.followingImagePreviewUrls = []\n    locals.followingImageLoaded = []\n    locals.colors = ['black', 'white']\n    locals.color = locals.colors.shift()\n    locals.loading = false\n    locals.backgroundColor = 'black'\n    locals.isCad = false\n    locals.cadViewerDocument = null\n    locals.done = false\n\n    this.cadViewer = null\n    // locals.renderCount = 0 \n\n    // Aeppic.watch(document, () => {\n    //   locals.renderCount = locals.renderCount + 1 \n    // })\n    this.cadExtensions = [\n      '.igs',\n      '.step'\n    ]\n \n    this.displayableTypes = [\n      'application/pdf'\n    ]\n \n    this.displayableTypesStartingWith = [\n      'image/',\n      'video/',\n      'application/vnd.oasis.opendocument'\n    ]\n\n    this.displayableTypesIncluding = [\n      'word',\n      'spreadsheet',\n      'powerpoint',\n      'presentation',\n      'mspublisher',\n      'excel'\n    ]\n\n    const { name } = getField()\n\n    for (const ext of this.cadExtensions) {\n      if (name.endsWith(ext)) {\n        locals.isCad = true\n      }\n    }\n \n    if (locals.isCad) {\n      Aeppic.nextRenderPass().then(async () => {\n        await this.loadViewer()\n        await this.loadCadFileIntoViewer()\n      })\n    }  \n\n    this.loadFollowingImagePreviewUrls()\n\n    $on('print', () => $refs.viewer && $refs.viewer.print())\n  }\n\n  destroy() {\n    locals.done = true\n  }\n\n  async loadFollowingImagePreviewUrls() {\n    if (!this.showFileAsMultipleImages) {\n      return\n    }\n\n    const url = Aeppic.Content.getPreviewSrc(document, fieldName, { format: 'jpg' })\n\n    const resp = await Aeppic.fetch(url)\n\n    if (resp.headers.has('X-Pdf-Pages')) {\n      const pageCount = resp.headers.get('X-Pdf-Pages')\n\n      const followingImagePreviewUrls = []\n\n      for (let i = 1; i <= pageCount; i++) {\n        followingImagePreviewUrls.push(`${url}&firstPage=${i}&lastPage=${i}`)\n      }\n\n      locals.followingImagePreviewUrls = followingImagePreviewUrls\n      locals.followingImageLoaded = new Array(followingImagePreviewUrls.length).fill(false)\n    }\n  }\n\n  get isElectron() {\n    return typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0\n  }\n\n  toggleColor() {\n    locals.colors.push(locals.color)\n    locals.color = locals.colors.shift()\n  }\n\n  get isImage() {\n    const field = getField()\n    return field && field.type && field.type.startsWith('image/')\n  }\n\n  get usePreviewUrl() {\n    const field = getField()\n    if (field.type === 'image/heic' || field.type === 'image/heif') {\n      return true\n    }\n\n    if (params.previewFormat !== 'jpg') {\n      // let fileViewer package decide\n      return null\n    }\n\n    return !field.type.startsWith('video/') && !field.type.startsWith('image/')\n  }\n\n  get showFileAsMultipleImages() {\n    if (params.previewFormat !== 'jpg') {\n      // let fileViewer package decide\n      return null\n    }\n\n    const field = getField()\n    return !field.type.startsWith('video/') && !field.type.startsWith('image/')\n  }\n\n  get previewUrl() {\n    const field = getField()\n    if (field.type === 'image/heic' || field.type === 'image/heif') {\n      return `${Aeppic.Content.getThumbnailSrc(document, fieldName)}&width=auto&height=auto&format=jpg`\n    }\n\n    if (params.previewFormat === 'jpg') {\n      return Aeppic.Content.getPreviewSrc(document, fieldName, { format: 'jpg' })\n    }\n\n    return Aeppic.Content.getPdfUrl(document, fieldName)\n  }\n\n  onLoading() {\n    locals.error = false\n    locals.loading = true\n  }\n\n  onError(event) {\n    console.log(event) \n    locals.error = true\n    locals.loading = false\n  }\n\n  onLoaded() {\n    locals.error = false\n    locals.loading = false\n  }\n\n  get isDocumentDisplayable() {\n    if (locals.isCad) {\n      return true\n    }\n\n    const field = getField()\n\n    const type = field.type\n\n    if (type == null || typeof type !== 'string') {\n      return false\n    }\n\n    for (const t of this.displayableTypes) {\n      if (t === type) {\n        return true\n      }\n    }\n\n    for (const t of this.displayableTypesStartingWith) {\n      if (type.startsWith(t)) {\n        return true\n      }\n    }\n\n    for (const t of this.displayableTypesIncluding) {\n      if (type.includes(t)) {\n        return true\n      }\n    }\n\n    return false\n  }\n\n  async loadViewer() {\n    // return\n    await this.loadAutodeskViewer()\n\n    const getAccessToken = async (ready) => {\n      const token = await this.getCadToken()\n      console.log('getAccessToken', token)\n      ready(token.access_token, token.expires_in)\n    } \n\n    const viewerOptions = {\n      env: 'AutodeskProduction', \n      api: 'derivativeV2_EU',\n      getAccessToken\n    }\n\n    return new Promise((resolve) => {\n      Autodesk.Viewing.Initializer(viewerOptions, () => {\n        this.cadViewer = new Autodesk.Viewing.GuiViewer3D($refs.forgeViewer)\n        const startedCode = this.cadViewer.start()\n        \n        if (startedCode > 0) {\n          reject('Could not start viewer')\n        } else {\n          resolve()\n        }\n      })\n    })\n  }\n\n  async loadAutodeskViewer() {\n    return new Promise(async (resolve, reject) => {\n      //<link rel=\"stylesheet\" href=\"https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css\" type=\"text/css\">\n      //<script src=\"https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.min.js\"></script>\n      if (typeof Autodesk !== 'undefined') {\n        return resolve()\n      }\n    \n      const htmlDocument = $refs.forgeViewer.ownerDocument\n      const script = htmlDocument.createElement('script')\n      \n      script.addEventListener('load' , async () => {\n        console.log('loaded script')\n        resolve()\n      })\n\n      script.onerror = reject\n\n      script.type = 'text/javascript'\n      script.src = 'https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.min.js'\n      htmlDocument.head.appendChild(script)          \n\n      const link = htmlDocument.createElement('link')\n      link.rel='stylesheet'\n      link.href = 'https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css'\n      htmlDocument.head.appendChild(link)              \n    })\n  }\n   \n  async loadCadFileIntoViewer() {\n    // return\n    const { access_token } = await this.getCadToken()\n \n    try {\n      const documentId = await this.loadCadFile(access_token)\n      this.showCadDocumentInViewer(documentId)\n    } catch (error) {\n      await this.resetConvertedCad()\n\n      const documentId = await this.loadCadFile(access_token)\n\n      try {\n      this.showCadDocumentInViewer(documentId)\n      } catch (error) {\n        console.error('Could not load CAD document even after retry', error)\n      }\n    }\n  }\n\n  async getCadToken() {\n    const params = new URLSearchParams()\n    params.set('client_id', 'ofYlw7a9YNvcGLYyopz6JkJzSsRNfA5W')\n    params.set('client_secret', 'zyS7wUYXaKtHiEuG')\n    params.set('grant_type', 'client_credentials')\n    params.set('scope', 'data:write data:read bucket:create bucket:delete')\n\n    const response = await Aeppic.fetch('https://developer.api.autodesk.com/authentication/v1/authenticate', { method: 'POST', body: params })\n    // return ''\n    const token = await response.json()\n\n    console.log(token)\n    // alert(token.access_token)\n    return token\n  }\n\n  getConvertedCadId(fileDataUrl) {\n    const convertedMetaDataId = encodeURIComponent(`AutoCadMeta-${document.id}#${fileDataUrl}`)\n    return convertedMetaDataId\n  }\n\n  async resetConvertedCad() {\n    const fileDataUrl = getField().dataUrl\n    const convertedCadId = this.getConvertedCadId(fileDataUrl)\n    const converted = await Aeppic.edit(convertedMetaDataId)\n    \n    if (converted) {\n      converted.data.fileDataUrl = ''\n      Aeppic.save(converted)\n    }\n    return this.sleep(10)\n  }\n\n  async loadCadFile(token) {\n    if (!this.cadViewer) {\n      return\n    }\n\n    // return\n    const fileDataUrl = getField().dataUrl\n\n    if (!fileDataUrl) {\n      throw new Error('No file to convert')\n    }\n\n    const convertedMetaDataId = this.getConvertedCadId(fileDataUrl)\n    \n    let converted \n\n    if (await Aeppic.get(convertedMetaDataId)) {\n      converted = await Aeppic.edit(convertedMetaDataId)\n    } else {\n      converted = await Aeppic.new('0b3eead6-797c-47dc-b810-da988aa7f44f', document.id, { id: convertedMetaDataId })\n    }\n    \n    if (converted.data.fileDataUrl && converted.data.fileDataUrl !== fileDataUrl) {\n      converted.data.objectUrn = ''\n      converted.data.objectId = ''\n      converted.data.documentId = '' \n    } \n\n    converted.data.fileDataUrl = fileDataUrl\n    if (converted.data.documentId) {\n      return converted.data.documentId\n    }  \n \n    converted.data.fieldName = fieldName\n    converted.addReference('original', document, { updateVersionIfAlreadyKnown: true })\n\n    if (!converted.data.objectUrn) {\n      const name = getField().name\n      const bucketKey = 'cad-upload-emea-' + document.id + document.v\n \n      converted.data.progress = 'UPLOADING'\n      converted.data.bucketKey = bucketKey\n      Aeppic.save(converted)\n\n      const buckedCreated = await Aeppic.fetch('https://developer.api.autodesk.com/oss/v2/buckets', {\n        method: 'POST',\n        headers: {\n          Authorization: `Bearer ${token}`,\n          'Content-Type': 'application/json',\n          // 'x-ads-region': 'EMEA',\n        },\n        body: JSON.stringify({\n          bucketKey,\n          access: 'full', \n          policyKey: 'transient'\n        })\n      })\n  \n      if (buckedCreated.status !== 200 && buckedCreated.status !== 409) {\n        throw new Error('Could not create bucket:' + buckedCreated.status)\n      } \n\n      const fileSrc = Aeppic.Content.getSrc(document, fieldName)\n      const fileResponse = await Aeppic.fetch(fileSrc)\n      const fileBlob = await fileResponse.blob()\n\n      const deleted = await Aeppic.fetch(`https://developer.api.autodesk.com/oss/v2/buckets/${bucketKey}/objects/${name}`, {\n        method: 'DELETE',\n        headers: {\n          Authorization: `Bearer ${token}`,\n        }\n      })\n      \n      const deletedResult = await deleted.text()\n\n      const uploaded = await Aeppic.fetch(`https://developer.api.autodesk.com/oss/v2/buckets/${bucketKey}/objects/${name}`, {\n        method: 'PUT',\n        headers: {\n          Authorization: `Bearer ${token}`,\n          'Content-Length': fileBlob.size\n        },\n        body: fileBlob\n      })\n  \n      // if (buckedCreated.status !== 200 && buckedCreated.status !== 409) {\n      //   throw new Error('Could not uploiad:' + buckedCreated.status)\n      // }\n\n      const { objectId  } = await uploaded.json()\n      \n      converted.data.progress = 'UPLOADED'\n      converted.data.objectName = name\n      converted.data.objectId = objectId\n      converted.data.objectUrn = this.base64encode(objectId)\n      Aeppic.save(converted)\n      \n      console.log('uploaded ', uploaded, converted.data.objectUrn, objectId)\n    }\n \n    const translate = await Aeppic.fetch('https://developer.api.autodesk.com/modelderivative/v2/designdata/job', {\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${token}`,\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify(\n        {\n          input: {\n            urn: converted.data.objectUrn\n          },\n          output: {\n            destination: {\n              // region: 'us',\n              region: 'emea'\n            },\n            formats: [\n              {\n                \"type\": \"svf2\",\n                \"views\": [\n                  \"2d\",\n                  \"3d\"\n                ],\n                \"advanced\": {\n                  \"generateMasterViews\": true\n                }\n              }\n            ]\n          }\n        }\n      )\n    }) \n\n    const translatedFileResponse = await translate.json()\n\n    converted.data.progress = 'CONVERSION_STARTED'\n    converted.data.format = 'svf2'\n    Aeppic.save(converted)\n\n    // console.log(translatedFileResponse)\n    // alert(JSON.stringify(translatedFileResponse))\n\n    await this.sleep(100)\n\n    for (let i=500; i>0 && !locals.done; i--) {\n      const job = await Aeppic.fetch(`https://developer.api.autodesk.com/modelderivative/v2/designdata/${converted.data.objectUrn}/manifest`, {\n        headers: {\n          Authorization: `Bearer ${token}`\n        }\n      })\n\n      const jobStatus = await job.json()\n      converted.data.conversionResult = JSON.stringify(jobStatus, null, 2)\n\n      if (jobStatus.progress === 'complete') {\n        converted.data.progress = 'CONVERSION_COMPLETED'\n        \n        if (jobStatus.status === 'success') {\n          const documentId = `urn:${converted.data.objectUrn}`\n        \n          converted.data.format = 'svf2'\n          converted.data.success = true\n          converted.data.documentId = documentId\n        } else if (jobStatus.status === 'failed' || jobStatus.status === 'timeout') {\n          console.log(jobStatus)\n\n          converted.data.format = 'svf2'\n          converted.data.success = false\n          converted.data.progress = 'CONVERSION_COMPLETED'\n          converted.data.documentId = ''\n        }\n\n        break\n      }\n\n      Aeppic.save(converted)\n      await this.sleep(1000)\n    }\n\n    Aeppic.save(converted)\n    \n    if (!converted.data.documentId) {\n      throw new Error('Could not convert')\n    }\n\n    return converted.data.documentId\n  }\n\n  showCadDocumentInViewer(documentId) {\n    return new Promise((resolve, reject) => {\n      Autodesk.Viewing.Document.load(\n        documentId,\n        (viewerDocument) => {\n          const defaultModel = viewerDocument.getRoot().getDefaultGeometry()\n          this.cadViewer.loadDocumentNode(viewerDocument, defaultModel)\n\n          // locals.cadViewerDocument = viewerDocument\n          console.log('loaded', locals.cadViewerDocument)\n          resolve(locals.cadViewerDocument)\n        },\n        (error) => {\n          console.error('failed to show cad', error)\n          reject(error)\n        }) \n      }\n    )\n  }\n\n  sleep(ms) {\n    return new Promise((resolve) => setTimeout(resolve, ms))\n  }\n\n  base64encode(str) {\n    const ret = btoa(str)\n    // Remove ending '=' signs\n    // Use _ instead of /\n    // Use - insteaqd of +\n    // Have a look at this page for info on \"Unpadded 'base64url' for \"named information\" URI's (RFC 6920)\"\n    // which is the format being used by the Model Derivative API\n    // https://en.wikipedia.org/wiki/Base64#Variants_summary_table\n    return ret.replace(/=/g, '').replace(/[/]/g, '_').replace(/[+]/g, '-')\n  }\n}\n","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"5fc69801-77c4-4bd6-bd40-133901427367","v":"97d60f16fbce16bc","text":"File Viewer"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-02-22T10:11:38+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2023-12-13T16:02:36+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936839,"ct":1721209936839,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"bb4e49fce302aa57","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"99087527-a782-47b1-9e4e-6d140265d873","v":"17b56bfe37861323","p":"controls-folder","f":{"id":"control-form","v":"8f342d57244fe7b5","text":"Control"},"data":{"namespace":"ae","name":"files","template":" <ul role=\"list\" class=\"divide-y divide-gray-100 rounded-md border border-gray-200\">\n  <li class=\"flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6\"\n    v-for=\"file in locals.files\">\n    <div class=\"flex w-0 flex-1 items-center\">\n      <svg class=\"h-5 w-5 flex-shrink-0 text-gray-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n        <path fill-rule=\"evenodd\" d=\"M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z\" clip-rule=\"evenodd\" />\n      </svg>\n      <div class=\"mx-4 flex min-w-0 flex-1 flex-col\">\n        <div class=\"truncate font-medium flex flex-row gap-1 justify-start\">\n          <ae-design name=\"title\" class=\"truncate\" :document=\"file\"></ae-design>\n        </div>\n        <span class=\"flex flex-row text-gray-400 justify-start\">\n          <ae-design name=\"stamp-info\" :document=\"file\"></ae-design>\n          <ae-design name=\"date-info\" :document=\"file\"></ae-design>\n          <ae-design name=\"modified-info\" :document=\"file\"></ae-design>\n        </span>\n      </div>\n    </div>\n    <div class=\"flex-shrink-0 bg-slate-200 p-2 border\" >\n      <a :href=\"controller.downloadUrl(file)\" target=\"_blank\" class=\"cursor-pointer font-medium text-indigo-600 hover:text-indigo-500\">Download</a>\n    </div>\n  </li>\n</ul>\n","css":"","controller":"async init() {\n  locals.files = null\n\n  const refreshFiles = async () => {\n    locals.files = await Aeppic.getAll(getField())\n  }\n\n  Aeppic.watch(document, refreshFiles)\n  refreshFiles()\n} \n\ngetFiles() {\n  return getField().length\n}\n\ndownloadUrl(file) {\n  const downloadFileName = file.data.name || file.data.datei.name\n  return Aeppic.Content.getSrc(file, 'datei', { downloadFileName })\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-07-05T13:32:52+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-07-08T07:17:41+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721144004093,"ct":1721144004093,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"46858b3a7d4ea855","f":{"id":"control-form","v":"7672ea0b1b6d4c70","text":"Control"}},"meta":{"modified":{"at":"2024-07-16T17:33:24+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"883ec8a7-9639-4b35-b914-b8a480250f95","v":"1e1170eaadc0df99","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"form","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\">\n  <monaco-editor style=\"min-height:40em\" v-model=\"field\" :readonly=\"readonly\" theme=\"vs-light\" language=\"markdown\" :exports-html=\"true\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>    \n\n","css":"","controller":"class Controller {\n  async getHoverContent(word) {\n    const infos = await Aeppic.find(`id:${word}`)\n    if (!infos || !infos.length) {\n      return []\n    }\n\n    const doc = infos[0]\n\n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0a99614c3b79c920","text":"Monaco Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-01-10T10:42:21+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2021-06-08T11:35:00+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936839,"ct":1721209936839,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"0956058a95bbdb6c","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"b3813d96-c1c8-4cae-8f39-198231a9ab61","v":"37bd7d10bc136c6d","previous":{"v":"d180eec39b79aa20"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"form-builder","template":"<div>\n  <div v-if=\"field.id\" class=\"border border-neutral-200 rounded-md\">\n    <ae-design name=\"rd:card\" :document-id=\"field.id\"\n    @selected=\"Aeppic.navigateTo($event, { view: 'builder' })\"></ae-design>\n  </div>\n</div>","css":"","controller":"","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-09-27T07:56:48+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-09-27T08:20:44+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1727425244407,"ct":1727423808264,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"ccd9a3c1-05c1-493f-95e7-b7f2d26ddebc","v":"e304a4008f62bb12","previous":{"v":"ed8c052dc7604026"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"form-builder-add","template":"<div class=\"flex gap-1\">\n  <ae-layout  \n    layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n    @selected=\"controller.createChildAndShow()\"\n    param-text=\"Neu\"\n    param-icon=\"far fa-plus\"\n    param-classes=\"border border-neutral-200 text-xs rounded-full\"\n    ref=\"button\"\n  ></ae-layout>  \n</div>\n\n<!-- <div v-if=\"controller.canAdd\" class=\"flex gap-1\"></div>\n  <ae-control v-if=\"params.additionalAddControl\"\n                :name=\"params.additionalAddControl\"\n               :document=\"document\"\n               :field-name=\"fieldName\"\n               :params=\"params\"\n               :readonly=\"!controller.canAdd\"\n  ></ae-control>\n\n  <ae-layout v-if=\"params.showTemplates || params.onlyTemplate\"\n    layout-id=\"8bc795f2-7ac8-4b95-9019-0b27b178a36e\" \n    :param-options=\"locals.templatesOptions\" \n    @show-picker=\"controller.loadTemplatesOptions()\"\n    @selected=\"controller.createChildAndShow($event.documentId, { clone: true })\"\n    :param-button-params=\"{ text: 'Aus Vorlage', icon: 'far fa-layer-plus', classes: 'text-xs', border: 'border' }\"\n  ></ae-layout>\n\n  <template v-if=\"!params.onlyTemplate\">\n    <ae-layout v-if=\"locals.forms.length >= 2\"\n              layout-id=\"8bc795f2-7ac8-4b95-9019-0b27b178a36e\" \n              :param-options=\"locals.formsOptions\" \n              @selected=\"controller.createChildAndShow($event.documentId)\"\n              :param-button-params=\"{ text: 'Neu', icon: 'far fa-plus-octagon', classes: 'text-xs', border: 'border' }\"\n    ></ae-layout>\n    <ae-layout  v-else\n                layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n                @selected=\"controller.createChildAndShow()\"\n                param-text=\"Neu\"\n                param-icon=\"far fa-plus\"\n                param-classes=\"border border-neutral-200 text-xs rounded-full\"\n                ref=\"button\"\n    ></ae-layout>  \n  </template>\n</div>      -->\n\n\n<ae-layout  layout-id=\"5886531d-7ae8-47ec-8b16-adc3378fcd1f\" \n            ref=\"executionDialog\"\n></ae-layout>","css":"","controller":"class Controller {\n  async createChildAndShow() {\n    const newForm = await Aeppic.new('form', document)\n\n    const editable = await Aeppic.edit(document)\n    editable.addReference(fieldName, newForm)\n\n    Aeppic.saveAll([newForm, editable])\n\n\n    Aeppic.navigateTo(newForm, { view: 'builder' })\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-09-27T08:11:14+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-09-27T08:21:22+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1727425282461,"ct":1727424674362,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"b10a0de0-d71b-46f6-be95-d7fb67667def","v":"ab07103df4050268","previous":{"v":"initial"},"p":"controls-folder","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"},"data":{"namespace":"","name":"form-version","template":"<div v-if=\"locals.ready\" class=\"relative flex flex-row items-center\">\n  <div v-if=\"fieldInfo.tags['colored-square']\" class=\"w-9 h-9 border border-gray-400 rounded mr-2\" :style=\"'background-color:' + fieldInfo.tags['colored-square']\"></div>\n  <div ref=\"input\" \n    v-if=\"locals.isMultiSelect\"\n    tabindex=\"0\"\n    @click=\"controller.open($event)\"\n    @keydown.enter.prevent.stop=\"controller.enterOnInput($event)\"\n    @keydown.down.prevent.stop=\"controller.downOnInput($event)\"\n    @keydown=\"controller.find($event)\"       \n    class=\"mt-1 relative w-full cursor-default min-h-9 rounded-md py-2 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6 flex flex-wrap gap-2 hover:bg-gray-50\"\n    :class=\"(params.readonly ?? readonly) ? '' : 'bg-white'\"\n    aria-haspopup=\"listbox\" \n    :aria-expanded=\"locals.popupOpen\"\n    >\n    <div \n      v-for=\"selectedOption of controller.getSelectedOptions()\"\n      class=\"inline-flex items-center gap-x-0.5 rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10\">\n      <label v-if=\"selectedOption.tags?.['colored-pill']\" aria-label=\"Pink\" class=\"relative mr-2 text-sm -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 ring-current focus:outline-none\"\n              :style=\"{ 'color': selectedOption.tags?.['colored-pill'] }\"\n        >\n        <span aria-hidden=\"true\" class=\"h-4 w-4 rounded-full border border-black border-opacity-10 bg-current\"></span>\n      </label>\n      <span>{{selectedOption.display}}</span>\n      <button type=\"button\"  \n        v-if=\"!(params.readonly ?? readonly)\"\n        class=\"group relative -mr-1 h-3.5 w-3.5 rounded-sm hover:bg-gray-500/20\"\n        @click=\"controller.deselectOption(selectedOption)\"\n        @keydown.enter.prevent.stop=\"controller.deselectOption(selectedOption)\"\n        > \n        <span class=\"sr-only\">Remove</span>\n        <svg viewBox=\"0 0 14 14\" class=\"h-3.5 w-3.5 stroke-gray-600/50 group-hover:stroke-gray-600/75\">\n          <path d=\"M4 4l6 6m0-6l-6 6\" />\n        </svg>\n        <span class=\"absolute -inset-1\" />\n      </button>\n      <span class=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2\">\n        <svg class=\"h-5 w-5 text-gray-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n          <path fill-rule=\"evenodd\" d=\"M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z\" clip-rule=\"evenodd\" />\n        </svg>\n      </span>\n    </div>\n  </div>\n  <button ref=\"input\" \n    v-else\n    type=\"button\" \n    @click=\"controller.open($event)\"\n    @keydown.enter.prevent.stop=\"controller.enterOnInput($event)\"\n    @keydown.down.prevent.stop=\"controller.downOnInput($event)\"\n    @keydown=\"controller.find($event)\"       \n    class=\"relative w-full cursor-default min-h-8 rounded-md py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6 hover:bg-gray-50\"\n    :class=\"(params.readonly ?? readonly) ? '' : 'bg-white'\"\n    aria-haspopup=\"listbox\" \n    :aria-expanded=\"locals.popupOpen\">\n    <div class=\"flex gap-2\">\n      <div class=\"flex flex-col flex-1 overflow-hidden\">\n        <div class=\"flex flex-row items-center\">\n          <label v-if=\"controller.getCurrentOption().tags?.['colored-pill']\" aria-label=\"Pink\" class=\"relative mr-2 text-sm -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 ring-current focus:outline-none\"\n            :style=\"{ 'color': controller.getCurrentOption().tags?.['colored-pill'] }\"\n          >\n            <span aria-hidden=\"true\" class=\"h-4 w-4 rounded-full border border-black border-opacity-10 bg-current\"></span>\n          </label>\n          <span v-if=\"!controller.isEmptySelection(controller.getCurrentOption())\" class=\"block truncate font-normal flex-1 \">{{controller.getCurrentValueLabel()}}</span>\n          <span v-else class=\"truncate italic font-normal text-gray-400 flex-1\">{{locals.emptyOptionText}}</span>\n        </div>\n        <template v-if=\"controller.getShortHelpForOption(controller.getCurrentOption())\">\n          <span class=\"truncate text-ellipsis text-gray-400\" v-html=\"controller.getShortHelpForOption(controller.getCurrentOption()).html\"></span>\n        </template>\n        <span class=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2\">\n          <svg class=\"h-5 w-5 text-gray-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n            <path fill-rule=\"evenodd\" d=\"M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z\" clip-rule=\"evenodd\" />\n          </svg>\n        </span>\n      </div>\n      <button  v-if=\"controller.doesOptionHaveLongHelp(controller.getCurrentOption())\" \n        tabindex=\"-1\"\n        class=\"flex flex-col block m-auto p-2\"\n        :class=\"'text-gray-400'\"\n        @mouseenter.prevent.stop=\"controller.openDetailedHelp(controller.getCurrentOption(), $event)\"\n        @mouseleave.prevent.stop=\"$refs.detail.close()\"\n        @click.prevent.stop=\"controller.openDetailedHelp(controller.getCurrentOption(), $event)\"\n        >\n        <ae-icon class=\"far fa-info-circle\"></ae-icon>\n      </button>\n    </div>\n  </button>   \n  <ae-popup ref=\"popup\" theme=\"\"\n    :arrow=\"false\"\n    placement=\"bottom-start\"\n    :close-delay=\"150\"\n    :max-width=\"1600\"\n    @close=\"locals.popupOpen = false\"\n    @show=\"controller.onOpen()\"\n    >\n    <!--\n      Select popover, show/hide based on select state.\n\n      Entering: \"\"\n        From: \"\"\n        To: \"\"\n      Leaving: \"transition ease-in duration-100\"\n        From: \"opacity-100\"\n        To: \"opacity-0\"\n    --> \n    <ul  slot=\"popup\"\n      class=\"max-h-80 z-20 border shadow-lg overflow-y-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm\"\n      tabindex=\"-1\"\n      role=\"listbox\"\n      aria-activedescendant=\"listbox-option-3\"\n      @mouseenter=\"controller.popupEnter()\"\n      @blur=\"controller.close()\"\n      @keydown.escape.prevent.stop=\"controller.close()\"\n      @keydown.down.prevent.stop=\"controller.down($event)\"\n      @keydown.up.prevent.stop=\"controller.up($event)\"\n      @keydown.enter.prevent.stop=\"controller.select()\"\n      @keydown=\"controller.find($event)\"\n      >\n\n      <!--\n        Select option, manage highlight styles based on mouseenter/mouseleave and keyboard navigation.\n\n        Highlighted: \"bg-indigo-600 text-white\", Not Highlighted: \"text-gray-900\"\n      -->\n      <li v-for=\"(option,index) of controller.getOptions()\"\n        ref=\"option\"\n        class=\"relative min-h-8 grid grid-flow-col gap-1 items-center cursor-default select-none py-2 pl-3 pr-1\"\n        :class=\"(controller.doOptionsHaveLargeHelp() ? 'grid-cols-[32px_1fr_32px]' : 'grid-cols-[32px_1fr]') + (controller.isHighlightedOption(option) ? ' text-white bg-indigo-600' : ' text-gray-900')\"\n        :style=\"{ width: controller.getPopupWidth() }\" \n        :key=\"option.value\"\n        role=\"option\"\n        :tabindex=\"controller.isHighlightedOption(option) ? 0 : -1\"\n        :title=\"option.display\"\n        @focus=\"controller.mark(option)\"\n        @click=\"controller.select(option)\"\n        @keydown.enter.prevent.stop=\"controller.select(option)\"\n        @mouseenter=\"controller.mark(option)\"\n        @mouseleave=\"controller.unmark(option)\"\n        @keydown.f1.prevent.stop=\"controller.openDetailedHelp(option, $event)\"          \n        >\n      \n        <!-- \n          Checkmark, only display for selected option.\n        -->\n        <span v-if=\"controller.isSelectedOption(option)\"\n          class=\"inset-y-0 flex items-center\"\n          :class=\"controller.isHighlightedOption(option) ? 'text-white' : 'text-indigo-600'\">\n          <svg class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n            <path fill-rule=\"evenodd\" d=\"M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z\" clip-rule=\"evenodd\" />\n          </svg>\n        </span>\n        <span v-else>&nbsp;</span>\n\n        <div class=\"flex flex-col overflow-hidden w-full\">\n          <div class=\"flex flex-row\">\n            <label v-if=\"option.tags?.['colored-pill']\" aria-label=\"Pink\" class=\"relative mr-2 text-sm -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 ring-current focus:outline-none\"\n                :style=\"{ 'color': option.tags?.['colored-pill'] }\"\n              >\n              <span aria-hidden=\"true\" class=\"h-4 w-4 rounded-full border border-black border-opacity-10 bg-current\"></span>\n            </label>\n          \n            <span v-if=\"!controller.isEmptySelection(option)\"\n              class=\"block truncate font-normal\"\n              :class=\"controller.isSelectedOption(option) ? 'font-bold': ' font-normal'\"\n              >{{option.display}}</span>\n            <span v-else class=\"block truncate italic font-normal text-gray-400\">{{locals.emptyOptionText}}</span>\n          </div>\n\n          <template v-if=\"controller.getShortHelpForOption(option)\">\n            <span class=\"block truncate text-gray-400\" v-html=\"controller.getShortHelpForOption(option).html\"></span>\n          </template>\n        </div>\n\n          <template v-if=\"controller.doOptionsHaveLargeHelp() && $refs.option\">\n          <button  \n            v-if=\"controller.doesOptionHaveLongHelp(option)\"\n            tabindex=\"-1\"\n            class=\"flex flex-col block m-auto \"\n            :class=\"controller.isHighlightedOption(option) ? 'text-white' : 'text-gray-400'\"\n            @click.prevent.stop=\"controller.openDetailedHelp(option, { target: $refs.option[index] })\"\n            @mouseenter.prevent.stop=\"controller.openDetailedHelp(option, { target: $refs.option[index] })\"\n            >\n              <ae-icon class=\"far fa-info-circle\"></ae-icon>\n          </button>\n          <div v-else></div>\n        </template>\n\n      </li>\n    </ul>\n  </ae-popup>\n\n  <ae-popup ref=\"detail\" :arrow=\"false\" :close-delay=\"50\" :max-width=\"1200\" >\n    <div slot=\"popup\"\n      v-if=\"locals.helpDetailsOption && controller.getLongHelpForOption(locals.helpDetailsOption)\"\n      class=\"w-full overflow-auto rounded-md bg-white p-3 text-base shadow-lg \"\n      @mouseenter.prevent.stop=\"controller.openDetailedHelp(controller.getCurrentOption(), $event)\"\n      v-html=\"controller.getLongHelpForOption(locals.helpDetailsOption).html\">\n    </div>\n  </ae-popup>\n</div>","css":"","controller":"async init() {\n  locals.ready = false\n  locals.form = null\n  locals.highlightedOption = null\n  locals.emptyOptionText = params.placeholder?.value ?? 'No version'\n  locals.popupOpen = false\n  \n  try {\n    const formToUse = params.form === 'parent' ? document.p : document\n    locals.form = await Aeppic.getFormForDocument(formToUse)\n  } finally {\n    locals.ready = !!locals.form\n  } \n} \n\n\nmarkNextMatch(findTerm) {\n  console.log('searching for', findTerm)\n  let findWords = findTerm.split(' ').map(t => t.trim()).filter(t => t.length > 0).map(t => t.toLocaleLowerCase())\n\n  if (!findWords || findWords.length == 0) {\n    return\n  }\n\n  let firstWord = findWords[0]\n\n  // Find with prefix search first \n  let fullList = fieldInfo.settings.options\n  let list = fullList.filter(o => o.display.toLocaleLowerCase().startsWith(firstWord))\n\n  if (list.length === 0) { \n    list = fullList\n  }\n\n  for (const term of findWords) {\n    list = list.filter(o => o.display.toLocaleLowerCase().includes(term))\n  }\n\n  if (list.length > 0) {\n    this.focusOption(list[0])    \n  }\n}\n\nbuildFindTerm(event) {\n  const now = Date.now()\n  const timeSinceLastKeyFind = now - this.lastKeyFindPress\n  this.lastKeyFindPress = now\n\n  const MAX_DURATION_BETWEEN_KEY_PRESSES_TO_EXTEND_SEARCH = params.keyClearanceTime ?? 450 \n\n  if (timeSinceLastKeyFind > MAX_DURATION_BETWEEN_KEY_PRESSES_TO_EXTEND_SEARCH) {\n    locals.lastKeyFindTerm = ''\n  }\n\n  if (event.key?.length != 1) {\n    return null\n  }\n\n  locals.lastKeyFindTerm += event.key\n\n  return locals.lastKeyFindTerm\n}\n  \nopenDetailedHelp(option, event) {\n  locals.helpDetailsOption = option\n  $refs.detail?.show(event)\n}\n\ndoOptionsHaveLargeHelp() {\n  return fieldInfo.settings.options.some(o => this.doesOptionHaveLongHelp(o))\n}\n\ndoesOptionHaveLongHelp(option) {\n  let text = this.getLongHelpForOption(option)?.text?.trim() \n\n  if (!text) {\n    return false\n  }\n\n  return text.trim().length > 0\n}\n\ndoesOptionHaveHelp(option) {\n  return this.getShortHelpForOption(option)?.text?.trim() != ''\n}\n\ngetShortHelpForOption(option) {\n  return option.help\n}\n\ngetLongHelpForOption(option) {\n  return option.helpExtended\n}\n\nmark(option) {\n  locals.highlightedOption = option\n  locals.helpDetailsOption = option\n}\n\nunmark(option) {\n  if (option === locals.highlightedOption) {\n    locals.highlightedOption = null\n  }\n\n  if (option == locals.highlightedOption) {\n    locals.helpDetailsOption = null\n  }\n\n}\n\ndown(option) {\n  this.move(1)\n}\n\nup() {\n  this.move(-1)\n}\n\nmove(inc) {\n  let numberOfOptions = fieldInfo.settings.options.length\n  \n  if (numberOfOptions === 0) {\n    return\n  } \n\n  let index = fieldInfo.settings.options.findIndex(o => o === locals.highlightedOption)\n\n  if (index<0) {\n    index = 0\n  }\n\n  if (index === 0 && inc < 0) {\n    $refs.input.focus()\n    this.close()\n    return\n  }\n\n  let newIndex = Math.min(Math.max(0, index + inc), numberOfOptions-1)\n  let option = fieldInfo.settings.options[newIndex]\n\n  $refs.option[newIndex].focus()\n\n  this.mark(option)\n}\n\nfocusFirstOption() {\n  this.focusOption(fieldInfo.settings.options[0])\n}\n\nfocusCurrentOption() {\n  let value = this.getCurrentValue()\n  let option = this.findOption(value)\n\n  this.focusOption(option)\n}\n\nfocusOption(option) {\n  let index = fieldInfo.settings.options.findIndex(o => o === option)\n\n  if (index>=0) {\n    this.mark(fieldInfo.settings.options[index])\n    $refs.option?.[index]?.focus()\n  }\n}\n\nselect(option) {\n  if (params.readonly ?? readonly) {\n    return\n  }\n\n  if (!option) {\n    option = locals.highlightedOption\n  }\n\n  if (option) {\n    if (locals.isMultiSelect) {\n      let values = getField()\n      const alreadySelected = values.some(v => v === option.value)\n\n      if (alreadySelected) {\n        this.deselectOption(option)\n      } else {\n        let values = getField()\n        values.push(option.value)\n\n        // sort\n        let sorted = []\n        for (const o of fieldInfo.settings.options) {\n          if (values.some(v => v === o.value)) {\n            sorted.push(o.value)\n          }\n        }\n\n        setField(sorted)\n      }\n    } else {\n      setField(option.value)\n    }\n  }\n\n  this.closeAfterSelect()\n}\n\ndeselectOption(option) {\n  if (locals.isMultiSelect) {\n    let values = getField()\n    values = values.filter(v => v !== option.value)\n    setField(values)\n\n    if (values.length === 0) {\n      this.close()\n    }\n  }\n}\n\ncloseAfterSelect() {\n  if (params['keep-open-after-selection'] ) {\n    return\n  }\n\n  $refs.input?.focus()\n  this.close()\n}\n\nisEmptySelection(option) {\n  return option.display.trim() === '' && fieldInfo.settings.options[0] === option\n}\n\nisCurrentOption(option) {\n  return option.value === this.getCurrentValue()\n}\n\nisSelectedOption(option) {\n  const value = getField()\n\n  if (Array.isArray(value)) {\n    return value.some(v => v === option.value)\n  } else {\n    return value === option.value\n  }\n\n}\n\nisHighlightedOption(option) {\n  return option === locals.highlightedOption \n}\n\ngetCurrentValue() {\n  return getField()\n}\n\ngetCurrentOption() {\n  let value = this.getCurrentValue()\n  return this.findOption(value)\n}\n\nfindOption(value) {\n  const o = fieldInfo.settings.options.find(o => o.value === value)\n\n  if (!o) {\n    console.log('UNKNOWN SELECT VALUE IN FIELD',value,document)\n    return {\n      label: 'UNKNOWN',\n      value\n    }\n  }\n\n  return o\n}\n\ngetCurrentValueLabel() {\n  const option = this.getCurrentOption()\n  return option?.display ?? ''\n} \n\ngetOptions() {\n  return fieldInfo?.settings?.options ?? []\n}\n\nhasInput() {\n  return !!$refs.input\n}\n\ngetPopupWidth() {\n  const readToRefresh = locals.popupOpen\n  return $refs.input?.offsetWidth + 'px' ?? ''\n}\n\nasync openFromKeyboard(event) {\n  this.open(event)\n  await Aeppic.nextRenderPass()\n  \n  if (!locals.highlightedOption) {\n    this.focusFirstOption()\n  }\n}\n\nasync enterOnInput(event) {\n  locals.highlightedOption = null\n  if (!locals.popupOpen) {\n    if (!locals.isMultiSelect) {\n      locals.highlightedOption = this.findOption(getField())\n    }\n    return this.openFromKeyboard(event)\n  }\n\n  this.select()\n}\n\nasync downOnInput(event) {\n  if (!locals.popupOpen) {\n    return this.openFromKeyboard(event)\n  }\n\n  this.down()\n}\n\nasync open(event) {\n  $refs.popup?.show({ target: $refs.input })\n}\n\nasync close() {\n  locals.highlightedOption = null\n  locals.lastKeyFindTerm = 0\n  locals.lastKeyFindPress = 0\n  locals.helpDetailsOption = null\n\n  $refs.detail?.close()\n  $refs.popup?.close()\n\n  await Aeppic.nextRenderPass()\n\n  $refs.input?.focus()\n}\n\npopupEnter() {\n  $refs.popup?.cancelClose()\n} \n\nonOpen() {\n  locals.popupOpen = true\n\n  if (this.pendingMark) {\n    this.markNextMatch(this.pendingMark)\n    this.pendingMark = null\n  } else if (locals.highlightedOption) {\n    console.log('here')\n    this.focusOption(locals.highlightedOption)\n  } else {\n    this.focusFirstOption()\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-07-24T09:09:19+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-07-24T09:50:33+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721807433649,"ct":1721804959580,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"593896bc-4231-4022-839d-d4d9272d47be","v":"6e3fa61b551826a0","p":"91f205bc-9989-4fbe-8966-bd4ba9777c26","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"geolocation","template":"<label>\n\tLatitude\n    <input type=\"text\" v-model=\"field.lat\" :required=\"fieldInfo.required\" :readonly=\"readonly\" :aria-label=\"label\">\n</label>\n<label>\n\tLongitude\n    <input type=\"text\" v-model=\"field.lon\" :required=\"fieldInfo.required\" :readonly=\"readonly\" :aria-label=\"label\">\n</label>","css":"","controller":"","parameters":[],"standalone":"FIELD","cardinality":"SINGLE","limitTypes":true,"types":["geolocation"],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2017-08-29T12:15:56+02:00","by":"","from":"","client":"c26cd09c-c028-40b4-9f2c-b19bc743a362"},"modified":{"at":"2024-07-17T09:50:36+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936839,"ct":1721209936839,"a_depth":3,"a":["system","controls-folder","91f205bc-9989-4fbe-8966-bd4ba9777c26"],"a_forms":["system-folder-form","repository-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null,null],"stamps":[],"previous":{"v":"aa77ee4745402797","f":{"id":"control-form","v":"9bddead515b3a305","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"d100d820-1d31-43fc-8f3c-4d2a1654f85e","v":"7123f3e315985624","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"googlemaps","template":"\n<div v-if=\"params.readonly != null ? params.readonly : readonly && !field.street && !field.streetNumber && !field.postalCode && !field.city\">\nKeine Adresse eingegeben\n</div>\n<googlemaps v-else\n  @click.native.stop.prevent\n  v-model=\"field\"\n  style=\"flex:1; min-height: 200px;\"\n  :readonly=\"params.readonly != null ? params.readonly : readonly\">\n</googlemaps>","css":"._CSS {\n  display: flex;\n}","controller":"","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"118cacf8-a58c-44d4-9dc1-a3199d8c6fe7","v":"cc3b45319ef12062","text":"Google Maps"}],"device":"ALL","label":"","description":"Uses Google Maps"},"locks":[],"created":{"at":"2018-10-12T11:48:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936839,"ct":1721209936839,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"cfed772accde2a81","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"93552cf9-5d8e-4fe5-80b2-bc19882a6d70","v":"0f4028ba4e8c53c7","previous":{"v":"488ab0efdcedeb91"},"p":"75014663-b40b-4285-bdf9-ad54c6b5964a","f":{"id":"control-form","v":"d87073964fc278a5","text":"Control"},"data":{"namespace":"sct","name":"goto-anlage-scan","template":"<!-- PACKAGE: rs-barcode-scanner-for-angular@1.2.0 -->\n\n<rs-barcode-scanner as=\"scanner\" hide-close=\"true\" on-scanned=\"document.executeCommand('19bd1454-f4cf-4cff-af0f-69a29f7be03a', { barcode:code } )\" allow-native=\"true\" >\n    <md-button  class=\"md-primary md-raised\" style=\"background-color:#929eb2;margin:0;min-height:100px;min-width:100%;color:white;font-size:26px\" ng-click=\"scanner.scan()\">\n        <div layout=\"row\" layout-align=\"center\" layout-padding>\n            <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 24 24\" style=\"width: 48px; height: 48px; cursor: pointer; margin: 2px;fill:white\"><path d=\"M4 16v-8h2v8h-2zm12 0v-8h2v8h-2zm-9 0v-8h1v8h-1zm2 0v-8h2v8h-2zm3 0v-8h1v8h-1zm2 0v-8h1v8h-1zm5 0v-8h1v8h-1zm-1-10h4v4h2v-6h-6v2zm-16 4v-4h4v-2h-6v6h2zm4 8h-4v-4h-2v6h6v-2zm16-4v4h-4v2h6v-6h-2z\"></svg>\n            <span>Anlage Öffnen/Scannen</span>\n        </div>\n    </md-button>\n</rs-barcode-scanner>\n","css":"","controller":"","cardinalityLimitation":"SINGLE","standalone":"FIELD","types":[],"packages":[],"supportsMobile":false,"label":"","description":""},"locks":[],"created":{"at":"2022-02-28T15:16:16+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-02-28T15:16:36+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1646061396394,"ct":1646061376196,"a_depth":3,"a":["apps","d4581623-f542-4ade-86be-8b3ecec56f0e","75014663-b40b-4285-bdf9-ad54c6b5964a"],"a_forms":["folder","application-form","folder"],"inheritedLocks":[null,null,null,null],"stamps":[]},{"id":"a4383b33-81c9-4ceb-b55f-652ee2470d25","v":"79a71331618610ea","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"sct","name":"goto-anlage-scan","template":"<div ref=\"entry\" class=\"xae-form-cell__input w-full\">\n\n  <div v-if=\"locals.nfc\"\n    class=\"display:inline-block\">\n    <svg v-if=\"false\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"#000000\" viewBox=\"0 0 50 50\" width=\"50px\" height=\"50px\">    <path d=\"M 17.921875 2 L 16.861328 2.1503906 C 9.5223281 3.2003906 4 9.4696875 4 16.929688 C 4 20.439687 5.2400781 23.749375 7.3300781 26.359375 C 9.4200781 28.979375 12.359297 30.900391 15.779297 31.650391 C 16.829297 31.880391 17.91 32 19 32 C 19.98 32 20.939375 31.910703 21.859375 31.720703 L 21.310547 29.789062 L 20.75 27.859375 C 20.18 27.949375 19.59 28 19 28 C 17.49 28 16.020391 27.699141 14.650391 27.119141 C 12.630391 26.259141 10.970547 24.839375 9.8105469 23.109375 C 8.6505469 21.369375 8 19.300859 8 17.130859 C 8 12.700859 10.672266 8.8909375 14.822266 7.2109375 L 15.431641 6.9707031 L 15.902344 7.4296875 C 16.732344 8.2396875 17.822188 8.6796875 18.992188 8.6796875 C 20.162188 8.6796875 21.252031 8.2396875 22.082031 7.4296875 L 22.542969 6.9707031 L 23.152344 7.2109375 C 24.992344 7.9509375 26.550937 9.0004687 27.710938 10.480469 L 29.910156 9.8496094 L 31.910156 9.2792969 C 29.650156 5.4492969 25.741094 2.8103906 21.121094 2.1503906 L 20.0625 2 L 19.992188 3.0703125 C 19.952188 3.6003125 19.512187 4.0097656 18.992188 4.0097656 C 18.472188 4.0097656 18.032187 3.6003125 17.992188 3.0703125 L 17.921875 2 z M 15.029297 9.1699219 C 11.959297 10.659922 10 13.720859 10 17.130859 C 10 20.670859 12.129687 23.869297 15.429688 25.279297 C 16.559688 25.759297 17.76 26 19 26 C 19.41 26 19.809219 25.969922 20.199219 25.919922 L 18.060547 18.5 C 17.290547 15.84 18.83 13.039531 21.5 12.269531 L 25.589844 11.089844 C 24.849844 10.309844 23.970703 9.6599219 22.970703 9.1699219 C 21.850703 10.059922 20.46 10.539062 19 10.539062 C 17.54 10.539062 16.149297 10.059922 15.029297 9.1699219 z M 36.044922 10.058594 C 35.843984 10.074043 35.640703 10.110469 35.439453 10.167969 L 22.050781 14.027344 C 20.450781 14.487344 19.510469 16.167344 19.980469 17.777344 L 26.560547 40.810547 L 27.710938 44.810547 C 27.930937 45.590547 28.450156 46.230859 29.160156 46.630859 C 29.610156 46.880859 30.109141 47.009766 30.619141 47.009766 C 30.899141 47.009766 31.180938 46.970625 31.460938 46.890625 L 44.849609 43.029297 C 45.629609 42.809297 46.279922 42.290078 46.669922 41.580078 C 47.059922 40.880078 47.149687 40.059297 46.929688 39.279297 L 45.779297 35.300781 L 45.779297 35.269531 L 39.199219 12.246094 C 38.787969 10.837344 37.451484 9.9504492 36.044922 10.058594 z M 38.169922 15.886719 L 43.580078 34.869141 L 28.199219 39.289062 L 22.789062 20.316406 L 38.169922 15.886719 z M 39.064453 39.474609 C 39.450234 39.519141 39.790156 39.791953 39.910156 40.189453 C 40.060156 40.719453 39.750703 41.279687 39.220703 41.429688 L 35.380859 42.539062 C 35.290859 42.559063 35.189609 42.570312 35.099609 42.570312 C 34.669609 42.570312 34.270625 42.289609 34.140625 41.849609 C 33.990625 41.319609 34.290078 40.769375 34.830078 40.609375 L 38.669922 39.509766 C 38.802422 39.469766 38.935859 39.459766 39.064453 39.474609 z\"/></svg>\n    <pre>{{locals.nfc}}</pre>\n  </div>\n  <ae-layout  layout-id=\"a2cd7699-9b46-494f-8f89-cb1f62151d82\"\n              param-text=\"Anlagen öffnen/scannen\"\n              @click.native.stop=\"$emit('selected', document)\"\n              param-classes=\"w-full text-2xl\"\n              param-icon=\"fa fa-barcode-read\"\n  ></ae-layout>\n\n</div>\n","css":"","controller":"class Controller {\n  async init() {\n    locals.nfc = null\n    \n    if (this.isRunningOnKioskDevice()) {\n      this.global = await this.getGlobal()\n    \n      locals.nfc = 'detected kiosk'\n      this.registerKioskFeatures()\n    }\n  } \n\n  isRunningOnKioskDevice() {\n    return typeof Android != 'undefined'\n  }\n\n  destroy() {\n    if (this.isRunningOnKioskDevice()) {\n      this.unregisterKioskFeatures()\n    }\n  }\n\n  registerKioskFeatures() {\n    this.global.nfcScanResult = (data) => {\n      console.log('NFC', data)\n\n      if (data.startsWith('KT ') || data.startsWith('KT-')) {\n        const code = data.substring(3)\n        console.log('scanned code', code)\n\n        $emit('scanned:nfc', code)\n      } \n\n      locals.nfc = data\n    }\n\n    Android.useJavaScriptCallbackNFC(true)\n    locals.nfc = 'waiting'\n  }\n\n  unregisterKioskFeatures() {\n    Android.useJavaScriptCallbackNFC(false)\n    delete this.global?.nfcScanResult\n  }\n\n  async getGlobal() {\n    await Aeppic.nextRenderPass()\n\n    const window = $refs.entry?.ownerDocument.defaultView\n    return window\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2022-03-03T14:43:41+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-03-09T16:20:39+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936839,"ct":1721209936839,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"a63d675ed5d522f9","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"8b810de4-b6d4-4a4f-91c8-6bc6679b87db","v":"78efe259846ef26d","previous":{"v":"051fa0139c64f08b"},"p":"75014663-b40b-4285-bdf9-ad54c6b5964a","f":{"id":"control-form","v":"d87073964fc278a5","text":"Control"},"data":{"namespace":"sct","name":"goto-anlagenteil-barcode","template":"<!-- PACKAGE: rs-barcode-scanner-for-angular@1.2.0 -->\n\n<rs-barcode-scanner as=\"scanner\" hide-close=\"true\"  on-scanned=\"document.executeCommand('2acb618f-2989-4f47-b0ed-61f301b44366', { barcode:code } )\" allow-native=\"true\">\n    <md-button  class=\"md-primary md-raised\" style=\"margin:0;min-height:100px;min-width:100%;background-color:#bfcf3c\" ng-click=\"scanner.scan()\">\n        <div layout=\"row\" layout-align=\"center\" layout-padding >\n            <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" style=\"width: 48px; height: 48px; cursor: pointer; margin: 2px;fill:white\"><path d=\"M4 16v-8h2v8h-2zm12 0v-8h2v8h-2zm-9 0v-8h1v8h-1zm2 0v-8h2v8h-2zm3 0v-8h1v8h-1zm2 0v-8h1v8h-1zm5 0v-8h1v8h-1zm-1-10h4v4h2v-6h-6v2zm-16 4v-4h4v-2h-6v6h2zm4 8h-4v-4h-2v6h6v-2zm16-4v4h-4v2h6v-6h-2z\"></svg>\n            <div ng-if=\"!context.document.data.unterschrift.dataUrl\">\n                <span hide-xs >Nächsten Wartungspunkt Scannen</span>\n                <span hide show-xs>Nächsten Wartungspunkt</span>\n            </div>\n            <div ng-if=\"context.document.data.unterschrift.dataUrl\">\n                <span hide-xs >Wartungspunkt Scannen</span>\n                <span hide show-xs>Wartungspunkt</span>\n            </div>\n        </div>\n    </md-button>\n</rs-barcode-scanner>\n","css":"","controller":"","cardinalityLimitation":"SINGLE","standalone":"FIELD","types":[],"packages":[],"supportsMobile":false,"label":"","description":""},"locks":[],"created":{"at":"2022-02-28T15:16:43+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-03-02T14:49:55+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1646232595623,"ct":1646061403256,"a_depth":3,"a":["apps","d4581623-f542-4ade-86be-8b3ecec56f0e","75014663-b40b-4285-bdf9-ad54c6b5964a"],"a_forms":["folder","application-form","folder"],"inheritedLocks":[null,null,null,null],"stamps":[]},{"id":"4f5bd26a-c043-48ab-8ba3-e86c0eff066c","v":"fe039df721cf8055","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"security","name":"group","template":"<div class=\"_CSS-group\" xclass=\"{ '_CSS-account--disabled': field.disabled}\">\n  <div class=\"_CSS-avatar\">\n    <div v-if=\"locals.isSystemManagedGroup\" title=\"Diese Gruppe wird vom System verwaltet\">\n      <ae-icon class=\"fa fa-cogs\"></ae-icon>\n    </div>\n    <ae-design  name=\"avatar--small\" :document=\"document\"></ae-design>\n  </div>\n  <ae-design class=\"_CSS-group-title\" name=\"title\" :document=\"document\"></ae-design>\n  <ae-control name=\"security:group-users\" :document=\"document\" :params=\"params\" :readonly=\"readonly\"></ae-control>\n  \n  <div class=\"_CSS-features\">\n    <div v-if=\"document.data.group.id\">\n      <!-- <div>{{ Aeppic.translate(form.getPlaceholder('group').label) }}</div> -->\n      <div>Includes membership of</div>\n      <ae-design name=\"avatar--small\" :document=\"document.data.group\"></ae-design>\n    </div>\n  </div>\n</div>\n\n<!-- <ae-control name=\"security:accessable-documents\" :document=\"document\"></ae-control> -->\n","css":"._CSS-group {\n  display:grid;\n  grid-template-rows: 24px min-content;\n  grid-template-columns: auto 1fr auto;\n  grid-column-gap: 8px;\n  grid-template-areas: \n    \"avatar account features\"\n    \"avatar profile none\";\n  flex-direction: row;\n  margin-bottom: 8px; \n  cursor: pointer;\n}\n\n._CSS-group-overlay  {\n  grid-area: avatar;\n  z-index:1;\n  align-self:end;\n  justify-self: end;\n  margin: 0px 0px;\n  text-shadow: 1px solid black;\n  border-radius:50%;\n  width:32px; \n  padding: 2px; \n}\n\n._CSS-account--disabled {\n  opacity: 0.8;\n  background: var(--ae-background);\n}\n\n._CSS-avatar {\n  grid-area: avatar; \n}\n\n._CSS-profile-title {\n  grid-area: profile; \n}\n\n._CSS-account-title {\n  grid-area: account; \n  font-weight: 500;\n}\n\n._CSS-features {\n  grid-area: features;\n  color: #777;\n}\n\n._CSS-account--disabled ._CSS-account-title * {\n  text-decoration:line-through;\n}\n\n._CSS-users {\n  overflow: hidden;\n}","controller":"class Controller {\n  async init() {\n    locals.isSystemManagedGroup = document.data.systemManaged === true\n    // locals.isSystemManagedGroup = await this._isSystemManagedGroup()\n  }\n\n  // async _isSystemManagedGroup() {\n  //   const systemFolder = await this._getSecurityFolder()\n\n  //   if (systemFolder.data.securityGroup && systemFolder.data.securityGroup.id === document.id) {\n  //     return true\n  //   } else if (systemFolder.data.trackedTimeGroup && systemFolder.data.trackedTimeGroup.id === document.id) {\n  //     return true\n  //   } else {\n  //     return false\n  //   }\n  // }\n\n  // async _getSecurityFolder() {\n  //   if (!document.a) {\n  //     return null\n  //   }\n\n  //   const securityFolder = await Aeppic.findOne(`a:${document.a[0]} AND f.id:81939b86-a48e-480e-afd1-cb2907568550`)\n  //   return securityFolder\n  // }\n}\n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-12-04T12:54:05+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936839,"ct":1721209936839,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"f923ea1184ee9bcc","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"51c8231c-caf8-4e3a-979f-759c093beba7","v":"b28c98e8e205f2fe","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"security","name":"group-users","template":"<div v-if=\"readonly\" class=\"_CSS-list--readonly\">\n  <div class=\"_CSS-counter\">\n    <ae-layout layout-id=\"8e98c038-26ba-4225-b972-e8203d647442\" \n            :param-employees=\"locals.accounts\"\n            :param-max=\"params.edit ? 99 : 6\" \n    ></ae-layout>\n  </div>\n\n  <ae-control name=\"security:group-users-picker\" :document=\"document\" :param-realm=\"locals.realm\" :readonly=\"readonly\"></ae-control>\n</div>\n<div v-else>\n  <div class=\"_CSS-account\" v-for=\"account of locals.accounts\" @click.stop=\"$emit('selected', account)\">\n     <ae-design name=\"avatar--tiny\" :document=\"account\"></ae-design>\n     <ae-design class=\"_CSS-account__title\" name=\"title\" :document=\"account\"></ae-design>\n     <ae-layout layout-id=\"1eb9a22f-6e91-491d-bdc8-e88c6ec87f79\"\n                @remove=\"() => controller.removeAccount(account)\"\n     ></ae-layout>\n  </div>\n  <ae-control name=\"security:group-users-picker\" :document=\"document\" :param-realm=\"locals.realm\"></ae-control>\n</div>","css":"._CSS-account {\n  flex: 1;\n\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n\n  padding: 0 var(--ae-space-1);\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  border-radius: var(--ae-border-radius);\n  margin: var(--ae-space-1);\n}\n\n._CSS-account__title {\n  margin-left: var(--ae-space-2);\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}","controller":"class Controller {\n  async init() {\n    locals.accounts = []\n\n    await Aeppic.subscribe(`f.id:account data.groups:${document.id}`, { size: 500 }, accounts => locals.accounts = accounts )\n  }\n  async removeAccount(event) {\n    const account = await Aeppic.get(event)\n\n    const command = await Aeppic.Commands.execute('c958a916-5bac-4487-ab10-d473ec392765', document.id, { account })\n\n    if (!command.ok) {\n      console.error('removeFromGroup failed:', command.log)\n    }\n  }\n}\n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-12-04T13:01:50+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2022-01-26T09:29:27+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936839,"ct":1721209936839,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"f98d1b1a29ebb149","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2024-11-08T14:33:53+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"ct":1731076433060,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    locals.accounts = []\n\n    Aeppic.subscribe(`f.id:account data.groups:${document.id}`, { size: 500 }, accounts => locals.accounts = accounts )\n  }\n}\n","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"group-users","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"\n<div class=\"p-1 border rounded-md\">\n  <ae-list list-id=\"922e8251-6e60-45a4-9df6-3d434363ac01\"\n            :documents=\"locals.accounts\"\n            param-design=\"rd:employee-ref-card\"\n            :param-design-params=\"{ groupId: document.id, readonly: document.id.includes('_all-users') }\"\n            param-empty-text=\"Keine Benutzer dieser Gruppe zugeordnet\"\n  ></ae-list>\n</div>","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"7be0fe23-4384-4e5d-9a87-afdbd5b36415","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-11-27T12:25:58+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"42e0462dfd756262"},"stamps":[],"t":1764246358186,"v":"3aa0d34a2b5a6e5b"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"cloneOf":{"id":"03728759-6cfe-40ed-9d44-a9ba4dd9a733","text":"ref-add","v":"ed6c58a338c68436"},"created":{"at":"2024-11-11T10:47:20+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"ct":1731322040523,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    locals.refSearchSettings = {\n      filter: {\n        base: '',\n        targetDocument: document,\n        ancestorForm: 'c69a0c1c-6c53-40c2-87e8-5ace866bf1df', // company\n        formIds: 'd2ac3563-3c3f-4f02-b923-9b67abf98d10',\n      },\n      search: { \n        size: 20,\n        sort: [{\n          field: 'data.name',\n          descending: false\n        },\n        {\n          field: 'modified.at',\n          descending: true\n        }],\n        searchAllFields: false\n      },\n    }\n  }\n\n  showAdd(clickEvent) {\n    if (this.canAdd) {\n      $refs.selectionButton.$emit('show', clickEvent) \n    }\n  }\n\n\n  get canAdd() {\n    return !params.noAdd && !readonly && !document.id.includes('_all-users')\n  }\n\n  async addGroupToProfile(profile) {\n    const account = await Aeppic.getAccountForProfilePage(profile)\n\n    if (!account) {\n      return\n    }\n\n    const editable = await Aeppic.edit(account)\n\n    editable.addReference('groups', document)\n\n    Aeppic.save(editable)\n  }\n}","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"group-users-add","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<template v-if=\"controller.canAdd\">\n  <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n              @selected=\"controller.showAdd({ target: $refs.button.$el })\"\n              param-text=\"Nutzer hinzufügen\"\n              param-classes=\"border border-neutral-200 text-xs rounded-full\"\n              ref=\"button\"\n></ae-layout> \n  <ae-layout  layout-id=\"ef23b6bd-ec74-4701-ab8d-71a6d67fd9e4\"\n              @selected=\"controller.addGroupToProfile($event)\"\n              ref=\"selectionButton\"\n              :params=\"{ hidePath: params.hidePath,\n                        settings: locals.refSearchSettings,\n                        keepOpenAfterSelection: params.keepOpenAfterSelection,\n                        showCreate: controller.showCreate,\n                        fullPath: params.showFullPath,\n                        icon: params.icon || 'fa fa-angle-down', \n                        selectionHistory: params.selectionHistory,\n                        selectionHistorySize: params.selectionHistorySize,\n                        hideButton: true\n                      }\" \n  ></ae-layout> \n</template>     \n ","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"49cc42ef-2084-40c6-8b84-8c995d0f7c22","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2024-11-11T12:36:27+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"originalCloneOf":{"id":"03728759-6cfe-40ed-9d44-a9ba4dd9a733","text":"ref-add","v":"ed6c58a338c68436"},"p":"controls-folder","previous":{"v":"9ed9de8a18059160"},"stamps":[],"t":1731328587352,"v":"ce8339d559bc16b3"},{"id":"91e92e7b-2501-4633-8fba-e8f594541137","v":"a28be3ac522cd4b6","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"security","name":"group-users-picker","template":"<div class=\"\">\n  <ae-layout layout-id=\"64eaddd0-ac8a-46d4-a84f-0eb16e42b40d\"\n            v-if=\"controller.canAdd\"\n            class=\"_CSS-button-add\"\n            ref=\"buttonAdd\"\n            @click.native.stop.prevent=\"controller.showAdd()\"\n            v-translate:title=\"'addUserToGroup|Benutzer der Gruppe hinzufügen'\"\n  ></ae-layout>\n</div>\n\n\n<ae-layout layout-id=\"ed2cc4c2-d2a0-4969-8da7-49fb3d539cc9\"\n  ref=\"popup\"\n  :params=\"{ readonly: controller.readonly, items: locals.items, hidePath: true, searchSize: controller.searchSize }\"\n  @filter=\"controller.filterItems($event)\"\n  @selected=\"controller.addToGroup($event)\"\n  @load-more=\"controller.loadMoreItems()\" \n></ae-layout>\n\n<ae-layout layout-id=\"32dbb3b6-f883-4647-8d21-6f1e0fb83b90\"\n  ref=\"dialog\"\n  :params=\"{ readonly: controller.readonly, items: locals.items, label: label, hidePath: true, searchSize: controller.searchSize }\"\n  @filter=\"controller.filterItems($event)\"\n  @selected=\"controller.addToGroup($event)\"\n></ae-layout>\n\n","css":"","controller":"class Controller {\n  init() {\n    locals.items = []\n  }\n\n  get canAdd() {\n    return !readonly\n  }\n\n  showAdd() {\n    this.filterItems()\n\n    if (Aeppic.Device === 'Phone') {\n      $refs.dialog.$emit('show')\n      return\n    }\n\n    $refs.popup.$emit('show', {\n      target: $refs.buttonAdd.$el\n    })\n  }\n\n  get searchSize() {\n    if (this.overwrittenSearchSize) {\n      return this.overwrittenSearchSize\n    }\n\n    if (params.searchSize) {\n      const paramAsNumber = Number(params.searchSize)\n      if (!Number.isNaN(paramAsNumber)) {\n        return paramAsNumber\n      }\n    }\n\n    return 10\n  }\n\n  get readonly() {\n    if (params.readonly === true) {\n      return true\n    }\n\n    if (typeof readonly === 'boolean') {\n      return readonly\n    }\n\n    return false\n  }\n\n  loadAllItems() {\n    this.overwrittenSearchSize = 100\n    this.filterItems() \n  }\n\n  loadMoreItems() {\n    this.overwrittenSearchSize = this.overwrittenSearchSize ? this.overwrittenSearchSize + 100 : 100\n    this.filterItems()\n  }\n\n  filterItems(rawFilter = '') {\n      let searchParts = []\n\n      const queryLimiting = this.queryLimit\n\n      if (queryLimiting) {\n        searchParts.push(queryLimiting)\n      }\n\n      searchParts.push(`f.id:account`)\n\n      if (rawFilter !== '') {\n        const filter = rawFilter.trim()\n\n        if (filter.indexOf(':') === -1) {\n          filter.split(/\\s/).map(part => searchParts.push(part + '*') )\n          // searchParts.push(filter + '*')\n        } else {\n          searchParts.push(filter)\n        }\n      }\n\n      const fullSearch = searchParts.join(' AND ')\n      console.log(fullSearch, 'with size', this.searchSize)\n\n      Aeppic.find(fullSearch, {\n        size: this.searchSize\n      }).then((documents) => {\n          locals.items = documents\n      })\n    }\n\n    get queryLimit() {\n      const parts = []\n\n      if (params.realm) {\n        parts.push(`a:${params.realm}`)\n      } else {\n        // search for company form\n        for(let i = 0; i < document['a_forms'].length; i++) {\n          const aForm = document['a_forms'][i]\n\n          if (aForm === 'c69a0c1c-6c53-40c2-87e8-5ace866bf1df') {\n            parts.push(`a:${document.a[i]}`)\n          }\n\n          i = document['a_forms'].length\n        }\n      }\n\n\n      return parts.join(' AND ')\n    }\n\n    async addToGroup(event) {\n      const user = await Aeppic.get(event)\n\n      let nonEditableDocument = document\n\n      if (Aeppic.isEditableDocument(nonEditableDocument)) {\n        nonEditableDocument = nonEditableDocument.cloneAsDocument()\n      }\n      \n      const command = await Aeppic.Commands.execute('80352545-bad2-42bf-af15-66b3801b8b32', user, { group: nonEditableDocument })\n\n      if (!command.ok) {\n        console.error('addToGroup failed:', command.log)\n      }\n    }\n}\n","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-12-04T09:13:28+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936840,"ct":1721209936840,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"0e30e39c3fafa8a3","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"aac5c9d5-d022-4894-bd84-679b1952a9f4","v":"8b80241e37ca32ff","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"security","name":"groups","template":"<div class=\"_CSS-group\"  v-for=\"group of locals.sortedGroups\" :key=\"group.id\" @click=\"Aeppic.drillInto(group)\">\r\n  <ae-control name=\"security:group\" :document=\"group\" :readonly=\"true\"></ae-control>\r\n  <div class=\"_CSS-features\">\r\n    <!-- <i v-if=\"account.data.allowDirectLogin\" class=\"fa fa-sign-in\" title=\"Direct anmelden erlaubt\"></i> -->\r\n  </div>\r\n</div>","css":"._CSS {\r\n  display: grid;\r\n  grid-template-columns: repeat(auto-fill, minmax(600px, max-content));\r\n  grid-gap: 8px;\r\n}\r\n\r\n._CSS-group {\r\n  margin-bottom: 8px; \r\n  background: var(--ae-ui-card-background);\r\n  padding: 4px 8px;\r\n  border: var(--ae-border-width) solid var(--ae-border-color);\r\n  border-radius: var(--ae-border-radius);\r\n}","controller":"class Controller {\r\n  init() {\r\n    locals.realm = document.p\r\n    locals.sortedGroups = []\r\n    \r\n    locals.groups = Aeppic.subscribe(`f.id:group a:${locals.realm}`, (a) => {\r\n      const sorted = [...a]\r\n      sorted.sort((a,b) => a.data.name.localeCompare(b.data.name))\r\n\r\n      locals.sortedGroups.splice(0, locals.sortedGroups.length, ...sorted)\r\n    })\r\n  }\r\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-01-10T15:25:38+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936841,"ct":1721209936841,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"62ad7ee012e920e6","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2023-01-18T16:20:55+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936841,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    locals.show = false\n    locals.DIALOG_BREAKPOINT = 1000\n    this.helpByLayout = ''\n    locals.helpText = ''\n\n    watchParam('text', () => {\n      locals.helpText = this.helpText || ''\n    })\n\n    if (params.placeholder?.field?.tags?.help === 'layoutId') {\n      const helpDocument = await Aeppic.get(params.placeholder.field.settings.help.text)\n\n      if (helpDocument?.data.content) {\n        this.helpByLayout = helpDocument.data.content\n      } else {\n        return\n      }\n    }\n\n    locals.helpText = this.helpText || ''\n    locals.show = true \n  }\n\n  selected(event) {\n    if (locals.helpText.length <= locals.DIALOG_BREAKPOINT) {\n      this.mouseEnter(event)\n    } else {\n      $refs.popup?.closeImmediately()\n      $refs.dialog.show()\n    }\n  }\n\n  mouseEnter(event) {\n    $refs.popup && $refs.popup.show(event)\n  }\n\n  mouseLeave() {\n    $refs.popup && $refs.popup.close()\n  }\n\n  popupEnter() {\n    $refs.popup && $refs.popup.cancelClose()\n  }\n\n  // get helpHtml() {\n  //   if (params.html) {\n  //     return Aeppic.Parser.markdown(params.html)\n  //   }\n\n  //   if (params.placeholder?.field.settings.help.html) {\n  //     return Aeppic.Parser.markdown(Aeppic.translate(params.placeholder.field.settings.help.html))\n  //   }\n\n  //   return null\n  // }\n\n  get helpText() {\n    if (this.helpByLayout) {\n      return Aeppic.Parser.markdown(this.helpByLayout)\n    }\n\n    if (params.text) {\n      return Aeppic.Parser.markdown(params.text)\n    }\n\n    if (params.placeholder?.field.settings.help.text) {\n      return Aeppic.Parser.markdown(Aeppic.translate(params.placeholder.field.settings.help.text))\n    }\n\n    if (params.placeholder?.field.settings.helpExtended.text) {\n      return Aeppic.Parser.markdown(Aeppic.translate(params.placeholder.field.settings.helpExtended.text))\n    }\n\n    return ''\n  }\n}","css":"._CSS-help-container {\n  background-color: var(--ae-ui-background);\n  border-radius: var(--ae-border-radius);\n  overflow: hidden;\n  text-overflow: ellipsis;\n  padding: var(--ae-size-1) var(--ae-size-2);\n  border: var(--ae-border-width) solid var(--ae-border-color);\n}\n\n._CSS-help {\n  padding: var(--ae-size-1) var(--ae-size-2);\n  background-color: var(--ae-ui-background);\n  transition: opacity .15s, visibility 1s;\n  border-radius: var(--ae-border-radius);\n  text-overflow: ellipsis;\n}\n\n._CSS-help table {\n  display: block;\n  width: 100%;\n  overflow: auto;\n\n  border-spacing: 0;\n  border-collapse: collapse;\n}\n\n\n._CSS-help table tr {\n  background-color: #fff;\n  border-top: 1px solid #c6cbd1;\n}\n\n._CSS-help table tr:nth-child(2n) {\n  background-color: #f6f8fa;\n}\n\n._CSS-help table th, \n._CSS-help table td {\n  padding: 6px 13px;\n  border: 1px solid #dfe2e5;\n}\n\n._CSS-help h1 {\n  \tfont-size: 1.25rem; /* 20px */\n  line-height: 1.75rem; /* 28px */\n}\n\n._CSS-help h2 {\n  font-size: 1.125rem; /* 18px */\n  line-height: 1.75rem; /* 28px */\n}\n\n._CSS-help h3 {\n  font-weight: bold;\n}\n\n._CSS-help ul {\n  list-style-position: inside;\n  list-style-type: disc;\n}\n\n._CSS-help ol {\n  list-style-position: inside;\n  list-style-type: decimal;\n}\n\n._CSS-help ul > li > p,\n._CSS-help ol > li > p {\n  display: inline;\n}\n\n._CSS {\n  display: inline-block;\n}\n\n._CSS-popup {\n  padding: var(--ae-size-2) 0;\n  background-color: var(--ae-ui-background);\n  box-shadow: var(--ae-shadow-2);\n  transition: opacity .15s, visibility 1s;\n  border-radius: var(--ae-border-radius);\n}\n","description":"","device":"ALL","label":"","limitTypes":false,"name":"help","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<template v-if=\"locals.show\">\n  <template v-if=\"params.layoutId\">\n    <div class=\"_CSS-help-container\">\n      <ae-layout :layout-id=\"params.layoutId\" :param-layout-key=\"params.layoutKey\"></ae-layout>\n    </div>\n  </template>\n  <template v-else>\n    <!-- empty-title to prevent title bubble down -->\n    <ae-layout  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n                @selected=\"controller.selected($event)\"\n                @mouseenter.native=\"controller.mouseEnter($event)\"\n                @mouseleave.native=\"controller.mouseLeave()\"\n                :param-icon=\"params.icon || 'far fa-info-circle'\"\n                param-classes=\"text-neutral-500 text-xs\"\n                param-title=\"\" \n                param-border=\"border border-secondary-200\"\n    ></ae-layout> \n\n    <ae-popup ref=\"popup\" :close-delay=\"500\">\n      <div slot=\"popup\" class=\"flex flex-col max-w-[20em]\"\n        @mouseenter=\"controller.popupEnter()\"\n          @mouseleave=\"controller.mouseLeave()\"\n      >\n        <div class=\"overflow-hidden _CSS-help\" v-html=\"locals.helpText.slice(0, locals.DIALOG_BREAKPOINT)\">\n        </div>\n\n        <ae-layout  v-if=\"locals.helpText.length >= locals.DIALOG_BREAKPOINT\"\n                    layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n                    @selected=\"controller.selected($event)\"\n                    param-text=\"mehr\" \n                    :param-primary=\"true\"\n                    class=\"p-1\"\n        ></ae-layout> \n      </div>\n\n\n    </ae-popup>\n\n    <ae-dialog ref=\"dialog\">\n      <template #header=\"scope\">\n        <div class=\"flex items-center justify-end\">\n          <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n                      param-icon=\"far fa-times\"\n                      @selected=\"scope.close()\"\n          ></ae-layout>\n        </div>\n      </template>\n\n      <template #body>\n        <div class=\"overflow-hidden\">\n          <div class=\"_CSS-help max-w-[90vw] max-h-[95vh] overflow-x-hidden overflow-y-auto\" v-html=\"locals.helpText\"></div>\n        </div>\n      </template>\n    </ae-dialog>\n  </template>  \n</template>","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"25eded8e-bb41-4803-a5d3-781e3e3df4b4","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-03-27T12:09:42+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"c3bd483bd6d49f49"},"stamps":[],"t":1774613382988,"v":"a3ae8f8fe93376eb"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2019-05-03T13:05:18+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","client":"server","from":""},"ct":1721209936841,"data":{"cardinality":"BOTH","controller":"class Controller {\n  mouseEnter(event) {\n    $refs.popup && $refs.popup.show(event)\n  }\n\n  mouseLeave() {\n    $refs.popup && $refs.popup.close()\n  }\n\n  popupEnter() {\n    $refs.popup && $refs.popup.cancelClose()\n  }\n\n  get helpHtml() {\n    if (params.html) {\n      return Aeppic.Parser.markdown(params.html)\n    }\n\n    if (params.placeholder?.field.settings.help.html) {\n      return Aeppic.Parser.markdown(Aeppic.translate(params.placeholder.field.settings.help.html))\n    }\n\n    return null\n  }\n\n  get helpText() {\n    if (params.text) {\n      return Aeppic.Parser.markdown(params.text)\n    }\n\n    if (params.placeholder?.field.settings.help.text) {\n      return Aeppic.Parser.markdown(Aeppic.translate(params.placeholder.field.settings.help.text))\n    }\n\n    return null\n  }\n}","css":"._CSS-help-container {\n  background-color: var(--ae-ui-background);\n  border-radius: var(--ae-border-radius);\n  overflow: hidden;\n  text-overflow: ellipsis;\n  padding: var(--ae-size-1) var(--ae-size-2);\n  border: var(--ae-border-width) solid var(--ae-border-color);\n}\n\n._CSS-help {\n  padding: var(--ae-size-1) var(--ae-size-2);\n  background-color: var(--ae-ui-background);\n  box-shadow: var(--ae-shadow-2);\n  transition: opacity .15s, visibility 1s;\n  border-radius: var(--ae-border-radius);\n  max-width: 20em;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n._CSS-help table {\n  display: block;\n  width: 100%;\n  overflow: auto;\n\n  border-spacing: 0;\n  border-collapse: collapse;\n}\n\n\n._CSS-help table tr {\n  background-color: #fff;\n  border-top: 1px solid #c6cbd1;\n}\n\n._CSS-help table tr:nth-child(2n) {\n  background-color: #f6f8fa;\n}\n\n._CSS-help table th, \n._CSS-help table td {\n  padding: 6px 13px;\n  border: 1px solid #dfe2e5;\n}\n\n._CSS {\n  display: inline-block;\n}\n\n._CSS-popup {\n  padding: var(--ae-size-2) 0;\n  background-color: var(--ae-ui-background);\n  box-shadow: var(--ae-shadow-2);\n  transition: opacity .15s, visibility 1s;\n  border-radius: var(--ae-border-radius);\n}\n","description":"","device":"ALL","label":"","limitTypes":false,"name":"help","namespace":"","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<template v-if=\"params.layoutId\">\n  <div class=\"_CSS-help-container\">\n    <ae-layout :layout-id=\"params.layoutId\" :param-layout-key=\"params.layoutKey\"></ae-layout>\n  </div>\n</template>\n<template v-else>\n  <button \n  style=\"cursor:default;padding:0;margin:0;margin-left:5px\"\n  ref=\"button\" class=\"ae-button\"\n  @click=\"controller.mouseEnter($event)\"\n  @mouseenter=\"controller.mouseEnter($event)\"\n  @mouseleave=\"controller.mouseLeave()\"\n  :title=\"controller.helpText\"\n  >\n    <ae-icon class=\"fa fa-info-circle\"></ae-icon>\n  </button>\n\n\n  <!-- v-if=\"controller.helpHtml\" -->\n  <ae-popup ref=\"popup\" v-if=\"controller.helpText\" :close-delay=\"500\">\n    <div slot=\"popup\" class=\"_CSS-help\" v-html=\"controller.helpText\"\n      @mouseenter=\"controller.popupEnter()\"\n      @mouseleave=\"controller.mouseLeave()\">\n    </div>\n  </ae-popup>\n</template>\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"2f2916de-eb74-44ed-87f9-30a19a586183","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-08-18T10:09:57+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"02fc318772da8dd3"},"stamps":[],"t":1755511797476,"v":"2a448aa57cf7efa9"},{"id":"9a97a3af-50ee-421f-8ed9-acc14333c68a","v":"eaa9e8b53ed6225e","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"html","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor  style=\"min-height:70vh\" \n                  xstyle=\"min-height:-webkit-fill-available\"\n                  v-model=\"field\" \n                  :readonly=\"readonly\" \n                  theme=\"vs-dark\"\n                  language=\"html\"\n                  :get-hover-content=\"(word) => controller.getHoverContent(word)\"\n  ></monaco-editor>\n</ae-expandable>\n  <!-- <monaco-editor style=\"min-height:40em\" v-model=\"locals.debouncedFieldValue\" @input=\"controller.debounceUpdate()\" :readonly=\"readonly\" theme=\"vs-dark\" language=\"html\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor> -->","css":"","controller":"class Controller {\n  init() {\n    locals.debounce = null\n    locals.debouncedFieldValue = null\n    setTimeout(() => {\n      locals.debouncedFieldValue = getField()\n    })\n  }\n\n  debounceUpdate() {\n    if (locals.debounce) {\n      clearTimeout(locals.debounce)\n    } else {    \n      setField(locals.debouncedFieldValue)\n    }\n\n    locals.debounce = setTimeout(() => {\n      setField(locals.debouncedFieldValue)\n    }, 2500)\n  }\n\n  async getHoverContent(word) {\n    const doc = await Aeppic.get(word)\n\n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n  log(e){console.log(e)}\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0a99614c3b79c920","text":"Monaco Editor"}],"device":"ALL","label":"","description":"A control to display HTML"},"locks":[],"created":{"at":"2018-10-12T11:48:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2023-01-20T15:05:18+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936841,"ct":1721209936841,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"406b239b52ee6f4b","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"05ccda82-868f-47ad-b69f-b5fcfa925601","v":"17f46a7a168a0fa4","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"html-preview","template":"<iframe v-if=\"field\"\r\n        allowtransparency=\"true\"\r\n        referrerpolicy=\"no-referrer\"\r\n        sandbox\r\n        :srcdoc=\"locals.fieldWithoutHrefs\"\r\n        class=\"w-full h-[70vh] min-h-[800px] border rounded-md \"\r\n></iframe>\r\n<p v-else>Kein HTML Inhalt</p>\r\n","css":"._CSS-iframe {\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  border-radius: var(--ae-border-radius);\n  background: var(--ae-ui-background);\n}","controller":"class Controller {\n  init() {\n    locals.fieldWithoutHrefs = this.sanitzedField()\n\n    Aeppic.watch(document.id, () => this.sanitzedField())\n  }\n\n  sanitzedField() {\n    return getField().replace(/a href=\"[^\"]+\"/gi, 'a')\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2023-02-27T16:00:34+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2024-02-29T13:04:03+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936842,"ct":1721209936842,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"c06e70bd9b371768","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"6bce3565-86c1-4f15-9859-f7e1b140bd11","v":"7b6da1ea1fa7d7be","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"html-preview","template":"<iframe v-if=\"field\"\r\n        allowtransparency=\"true\"\r\n        class=\"_CSS-iframe\"\r\n         width=\"100%\"\r\n        height=\"800\"\r\n        referrerpolicy=\"no-referrer\"\r\n        sandbox :srcdoc=\"field\"\r\n></iframe>\r\n<p v-else>No HTML</p>\r\n","css":"._CSS-iframe {\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  border-radius: var(--ae-border-radius);\n  background: var(--ae-ui-background);\n}","controller":"","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-08-17T12:44:48+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2021-11-23T14:43:55+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936841,"ct":1721209936841,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"acec8f5cc695a73f","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"5f50aec5-2986-44c4-a0a2-7e097cd5431d","v":"d59cc7041d330066","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"icon","template":"\n<div class=\"_CSS_field ae-form-cell__input\">\n  <ae-layout  v-show=\"!field.dataUrl\"\n              layout-id=\"f5a5c2a0-bf9c-4b44-b05b-8e545156df10\"\n              :param-show-ui=\"true\"\n              :param-readonly=\"controller.readonly\"\n              :param-document=\"document\"\n              :param-field-to-add-to=\"fieldName\"\n              :param-flatten=\"true\"\n              :param-file-form-id=\"params.fileFormId\"\n              :param-file-form-field=\"params.fileFormField\"\n              :param-file-name-to-field=\"params.fileNameToField\"\n              ref=\"container\"\n              class=\"_CSS-input\"\n  ></ae-layout>\n\n  <div v-if=\"field.dataUrl\" class=\"_CSS-file\" @click=\"$refs.preview.$emit('show')\">\n    <ae-design name=\"thumbnail\" :document=\"document\" :watch=\"false\"></ae-design>\n    <div class=\"_CSS-file__info\">\n      <span class=\"_CSS-file__description\">\n        <span v-if=\"field.name\" class=\"_CSS-file__name\">{{field.name}}</span>\n        <span v-if=\"field.size\">{{controller.size}}</span>\n      </span>\n    </div>\n    <div style=\"flex: 1;\"></div>\n    <ae-design name=\"ref-remove\" :document=\"document\" v-if=\"!controller.readonly\" @remove=\"controller.resetField()\"></ae-design>\n  </div>\n\n  <div class=\"ae-progress--indeterminate\" title=\"uploading\" v-if=\"controller.isUploading && locals.upload.progressPercent == 0\">\n  </div>\n  <div v-else-if=\"locals.upload\" style=\"display:flex;transform:scaleX(-1)\">\n    <!-- status: {{locals.upload.status}} -->\n    <!-- {{locals.upload.progressPercent}} -->\n    <div class=\"ae-progress\" :style=\"{ 'padding-left': (100 - (locals.upload.progressPercent * 100)).toString() + '%' }\" ></div>\n  </div>\n</div>\n\n<ae-layout\n  ref=\"preview\"\n  layout-id=\"bbd6d99f-571d-422f-a2bc-15d3f8ecfae8\"\n  :param-file-name=\"field.name\"\n  :param-field-name=\"fieldName\"\n  :param-document=\"document\"></ae-layout>\n","css":"._CSS {\r\n  display: flex;\r\n}\r\n\r\n._CSS_field {\r\n  flex: 1;\r\n  display:flex;\r\n  flex-direction: column;\r\n}\r\n\r\n._CSS-file {\r\n  display: flex;\r\n  align-items: center;\r\n}\r\n\r\n._CSS-file__info {\r\n  margin-left: var(--ae-size-4);\r\n  display: flex;\r\n  flex-flow: column;\r\n}\r\n\r\n._CSS-file__description {\r\n  overflow: hidden;\r\n  text-overflow: ellipsis;\r\n  flex: 1;\r\n  display: flex;\r\n  flex-flow: column;\r\n}\r\n","controller":"class FileControl {\r\n  init() {\r\n    locals.upload = null //{progressPercent:0.3,status:'started'}\r\n  }\r\n\r\n  get isUploading() {\r\n    const status = Aeppic.Uploads.getUploadStatusForDocument(document)\r\n\r\n    if (status) {\r\n      locals.upload = status[0]\r\n    } else {\r\n      locals.upload = null\r\n    }\r\n\r\n    return !!locals.upload\r\n  }\r\n\r\n  resetField() {\r\n    setField(fieldInfo.defaultValue)\r\n  }\r\n\r\n  get size() {\r\n    const GIGABYTE_AS_BYTE = 1000000000\r\n    const MEGABYTE_AS_BYTE = 1000000\r\n    const KILOBYTE_AS_BYTE = 1000\r\n\r\n    const field = getField()\r\n    const size = field && field.size\r\n\r\n    if (!size) {\r\n      return {\r\n        size: 0,\r\n        suffix: 'B'\r\n      }\r\n    }\r\n\r\n    let divisor = 1\r\n    let suffix = 'B'\r\n\r\n    if (size > GIGABYTE_AS_BYTE) {\r\n      divisor = GIGABYTE_AS_BYTE\r\n      suffix = 'GB'\r\n    } else if (size > MEGABYTE_AS_BYTE) {\r\n      divisor = MEGABYTE_AS_BYTE\r\n      suffix = 'MB'\r\n    } else if (size > KILOBYTE_AS_BYTE) {\r\n      divisor = KILOBYTE_AS_BYTE\r\n      suffix = 'KB'\r\n    }\r\n\r\n    return `${(size / divisor).toFixed(0)} ${suffix}`\r\n  }\r\n\r\n  get readonly() {\r\n    if (params.readonly === true) {\r\n      return true\r\n    }\r\n\r\n    if (typeof readonly === 'boolean') {\r\n      return readonly\r\n    }\r\n\r\n    return false\r\n  }\r\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-05-08T09:00:42+00:00","by":"47325375-27fb-4303-8bf5-9438a0524219","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936842,"ct":1721209936842,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"2224618951d5d0f4","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-11-02T15:54:49+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936842,"data":{"cardinality":"BOTH","controller":"class Controller {\n  init() {\n    locals.editingIndex = -1\n\n    locals.collections = {\n      user: {\n        selectorIcon: 'far fa-user',\n        icons: [\n          'user-alien',\n          'user-alt-slash',\n          'user-alt',\n          'user-astronaut',\n          'user-chart',\n          'user-check',\n          'user-circle',\n          'user-clock',\n          'user-cog',\n          'user-cowboy',\n          'user-crown',\n          'user-edit',\n          'user-friends',\n          'user-graduate',\n          'user-hard-hat',\n          'user-headset',\n          'user-injured',\n          'user-lock',\n          'user-md-chat',\n          'user-md',\n          'user-minus',\n          'user-music',\n          'user-ninja',\n          'user-nurse',\n          'user-plus',\n          'user-robot',\n          'users-class',\n          'users-cog',\n          'users-crown',\n          'user-secret',\n          'user-shield',\n          'user-slash',\n          'users-medical',\n          'users-slash',\n          'users',\n          'user',\n          'user-tag',\n          'user-tie',\n          'user-times',\n          'user-unlock',\n          'user-visor',\n        ],\n      },\n      file: {\n        selectorIcon: 'far fa-file',\n        icons: [\n          'file-alt',\n          'file-archive',\n          'file-audio',\n          'file-certificate',\n          'file-chart-line',\n          'file-chart-pie',\n          'file-check',\n          'file-code',\n          'file-contract',\n          'file-csv',\n          'file-download',\n          'file-edit',\n          'file-excel',\n          'file-exclamation',\n          'file-export',\n          'file-image',\n          'file-import',\n          'file-invoice-dollar',\n          'file-invoice',\n          'file-medical-alt',\n          'file-medical',\n          'file-minus',\n          'file-music',\n          'file-pdf',\n          'file-plus',\n          'file-powerpoint',\n          'file-prescription',\n          'file-search',\n          'file-signature',\n          'files-medical',\n          'file-spreadsheet',\n          'file',\n          'file-times',\n          'file-upload',\n          'file-user',\n          'file-video',\n          'file-word',\n        ]\n      },\n      folder: {\n        selectorIcon: 'far fa-folder',\n        icons: [\n          'folder-download',\n          'folder-minus',\n          'folder-open',\n          'folder-plus',\n          'folders',\n          'folder',\n          'folder-times',\n          'folder-tree',\n          'folder-upload',\n        ]\n      }\n    }\n    locals.availableCollectionNames = params.collections?.split(',') || Object.keys(locals.collections)\n\n    locals.activeCollection = locals.availableCollectionNames[0] || ''\n\n    if (!locals.collections?.[params.collection] && !params.collections) {\n      console.error(`No or missing collectionName \"${params.collection}\". Disabling selection`)\n    } \n  } \n \n  get field() {\n    const field = getField() \n\n    if (Array.isArray(field)) {\n      return field\n    }\n\n    if (!field) {\n      return []\n    }\n\n    return [field]\n  }\n\n  remove(args) {\n    const { index } = args\n\n    let field = getField()\n\n    if (Array.isArray(field)) { \n      field.splice(index, 1) \n    } else {\n      field = ''\n    }\n\n    setField(field)\n  }\n\n  show(args) {\n    const { event, index } = args\n\n    locals.editingIndex = index\n\n    $refs.list.show(event)\n  }\n\n  fullIconName(name) {\n    return `far fa-${name}`\n  }\n\n  setIcon(name) {\n    const fullName = this.fullIconName(name)\n\n    let field = getField()\n\n    if (Array.isArray(field)) {\n      if (locals.editingIndex === -1) {\n        field.push(fullName)        \n      } else {\n        field.splice(locals.editingIndex, 1, fullName)\n      }\n    } else {\n      field = fullName\n    }\n\n    setField(field)\n  }\n} ","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"icon","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<div ref=\"container\"\n     class=\"border rounded-md py-1 px-2 flex items-center gap-1 bg-white group min-h-[2em] min-w-[100px]\"\n     @click.stop.prevent=\"controller.show({ event: $event, index: -1 })\"\n>\n\n\n  <!-- <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n            :param-icon-design-document-id=\"document.id\"\n            @selected=\"controller.show($event)\"\n  ></ae-layout> -->\n\n  <div  v-for=\"(entry, index) of controller.field\"\n        class=\"border rounded-full p-1 pl-2 flex items-center gap-2 bg-white\" \n        @click.stop=\"controller.show({ event: $event, index })\"\n        :title=\"entry\"\n  >\n    <ae-icon :class=\"entry\" class=\"text-lg\" >\n    </ae-icon> \n\n    <!-- <div class=\"text-neutral-400\">\n    {{ entry }} \n    </div> -->\n\n    <div @click.stop=\"controller.remove({ index })\"\n        class=\"text-neutral-400 px-1  bg-neutral-100 hover:bg-neutral-200 rounded-full\"\n    >\n      <ae-icon class=\"far fa-times\"></ae-icon>\n    </div>\n  </div>\n\n  <div class=\"flex-1\"></div>  \n\n    \n  <div v-if=\"!params.noAdd && !params.readonly\"\n    \n    class=\"text-neutral-400 absolute right-1 bottom-0 group-hover:text-sky-300\"\n  >\n    <ae-icon class=\"fa fa-caret-down\"></ae-icon>\n  </div>\n</div>\n\n<ae-popup ref=\"list\" placement=\"bottom\"> \n  <div class=\"flex flex-col p-1\">\n    <div class=\"flex gap-1\">\n      <ae-layout v-for=\"collectionName of locals.availableCollectionNames\"\n                 layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n                 :param-text=\"collectionName\"\n                 @selected=\"locals.activeCollection = collectionName\"\n                 :param-icon=\"locals.collections[collectionName]?.selectorIcon\"\n                 :param-primary=\"collectionName === locals.activeCollection\"\n      >\n      </ae-layout>\n    </div>\n    <div v-if=\"locals.collections[locals.activeCollection]\" class=\"flex flex-wrap gap-2 text-2xl justify-evenly\tmax-h-[50vh] \">\n      <div  v-for=\"name of locals.collections[locals.activeCollection].icons\" \n            class=\"hover:text-orange-500 p-1\"\n            :title=\"name\"\n      >\n        <ae-icon :class=\"controller.fullIconName(name)\" \n                @click.stop.native=\"controller.setIcon(name)\"\n        >\n        </ae-icon>\n      </div> \n    </div>\n  </div>\n</ae-popup>\n\n \n\n ","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"f046d635-44ed-4f23-acbd-9912d03942b5","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-09-23T13:08:12+00:00","backedBy":null,"by":"729fa027-7e7e-4b06-9602-f535c43824f0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"3e65c9723d6ae17a"},"stamps":[],"t":1758632892129,"v":"f34dc06370afabb7"},{"id":"fe723026-1722-493e-93ad-0492a8a9984b","v":"410e1fba26754988","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"icon-rating","template":"<div class=\"_CSS-icon-container ae-form-cell__input\" :key=\"field\" :class=\"{ '_CSS--interactive': !readonly, 'ae-form-cell__input--disabled': readonly }\">\n  <template v-for=\"n in controller.maxRating\">\n    <ae-icon v-if=\"n <= field\" :key=\"n\" class=\"fa fa-star _CSS-icon _CSS-icon-star\" @click.native=\"controller.setRating(n)\"></ae-icon>\n    <ae-icon v-else :key=\"n\" class=\"far fa-star _CSS-icon _CSS-icon-star\" @click.native=\"controller.setRating(n)\"></ae-icon>\n  </template>\n</div>\n","css":"._CSS-icon-container {\n  display: flex;\n  flex: 1;\n  align-items: center;\n}\n\n._CSS--interactive ._CSS-icon-star:hover {\n  color: gold;\n}\n\n\n/* ._CSS-container-star--inactive {\n  display: flex;\n  width: 1em;\n  height: 1em;\n} */\n/* ._CSS-icon-star--inactive-hovered {\n  display: none;\n}\n\n._CSS-container-star--inactive:hover ~ ._CSS-icon-star,\n._CSS-icon-star:hover ~ ._CSS-icon-star,\n._CSS-icon-star:hover ~ ._CSS-container-star--inactive {\n  color:gold;\n}\n\n._CSS-container-star--inactive:hover > ._CSS-icon-star--inactive {\n  display: none;\n}\n\n._CSS-container-star--inactive:hover > ._CSS-icon-star--inactive-hovered {\n  display: inline-block;\n} */\n","controller":"class Controller {\n  get maxRating() {\n    const options = fieldInfo.settings.options\n    const hasZero = !!options.find(option => option.value === 0)\n    \n    if (hasZero) {\n      return options.length - 1\n    }\n\n    return options.length\n  }\n\n  setRating(rating) {\n    if (readonly) {\n      return\n    }\n\n    if (rating === getField()) {\n      setField(0)\n    } else {\n      setField(rating)\n    }\n\n    if (params.autoSave) {\n      Aeppic.save(document)\n    }\n  }\n}\n","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-06-13T12:58:11+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936842,"ct":1721209936842,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"169271c3418c77ce","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"4116a0c4-1c43-4732-9938-1a12f6d4c201","v":"cd684b7dd2f1f714","previous":{"v":"76adaa3efd4ddde2"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"image","template":"<ae-control name=\"rd:file\" :document=\"document\"\n:params=\"params\" v-on=\"$listeners\" :field-name=\"fieldName\"\n></ae-control>","css":"","controller":"","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2026-05-19T09:39:14+00:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":"","impersonatedBy":"","from":"","client":"server"},"modified":{"at":"2026-05-19T09:40:35+00:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":"","impersonatedBy":"","from":"","client":"server"},"t":1779183635986,"ct":1779183554177,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"8a127302-831f-44d9-9a75-cdb7eb41157f","v":"c59f531b7c4bfe22","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"image-rotate","template":"<button class=\"ae-button\" @click.stop=\"controller.rotate()\" v-if=\"!params.noUi\" :disabled=\"readonly\" title=\"Bild drehen\">\n  <ae-icon class=\"fa fa-redo-alt\"></ae-icon>\n</button>\n\n<canvas ref=\"canvas\" v-show=\"false\"></canvas>","css":"","controller":"class Controller {\n  init() {\n    $on('rotate', () => this.rotate())\n  }\n\n  rotate() {\n    const resizeHandler = () => {\n      const { height, width } = image\n      const canvas = $refs.canvas\n\n      canvas.height = width\n      canvas.width = height\n      canvas.style.height = width\n      canvas.style.width = height\n\n      const ctx = canvas.getContext('2d')\n\n      // 90° rotate right\n      ctx.rotate(0.5 * Math.PI)\n      ctx.translate(0, -height)\n\n      ctx.drawImage(image, 0, 0)\n\n      const field = getField()\n\n      // keep same dataType or filesize will increase dramatically\n      canvas.toBlob((blob) => {\n        this._setDataUrl(blob)\n      }, field.type || 'image/jpeg')\n    }\n\n    const image = new Image()\n\n    image.addEventListener('load', resizeHandler, { once: true })\n    image.src = Aeppic.Content.getSrc(document, 'datei')\n  }\n\n  async _setDataUrl(dataUrl) {\n    document.setFile('datei', dataUrl)\n    Aeppic.commit(document)\n  }\n}\n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-06-19T11:37:09+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-09-22T13:17:01+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936842,"ct":1721209936842,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"1766090f2c32fdac","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"d0694e0c-0af0-4533-951d-34b470986fec","v":"775baecc3f570ea5","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"images","template":"<div class=\"ae-form-cell__input\">\n  <ae-list v-if=\"params.children\" \n          :root-document-id=\"params.children\"\n          query-id=\"f812861f-68c8-4818-9972-be7b5474c9b1\"\n          list-id=\"208b6d2d-4198-45a7-b716-b697e8e39d39\"\n          param-design=\"attachment-card\"\n          :param-design-params=\"{ ...params, readonly: true }\"\n  ></ae-list>\n\n  <ae-list v-else-if=\"params.id\" \n          :documents=\"[params.id]\"\n          list-id=\"208b6d2d-4198-45a7-b716-b697e8e39d39\"\n          param-design=\"attachment-card\"\n          :param-design-params=\"{ ...params, readonly: true }\"\n  ></ae-list>\n</div>","css":"","controller":"","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2022-02-09T14:36:18+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-02-09T15:18:49+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936842,"ct":1721209936842,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"9d55b69c176782b5","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2023-06-01T09:42:18+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936842,"data":{"cardinality":"BOTH","controller":"class Controller {\n  init() {\n    this.FORMS = {\n      WorkflowActAsActorRights: 'd4d31804-1519-4866-8808-52a4f656a041',\n      Group: 'group',\n      Key: 'key-form',\n    }\n\n    locals.progress = []\n  }\n  \n  async install() {\n    if ((await packages.window.confirm('Proceed with cloning stuff from workflowDefinition? NO update/replace, just new!')) === false) {\n      return\n    }\n\n    const definition = await Aeppic.get(document.data.workflowDefinition)\n\n    // locals.progress.push('Creating \"Workflow Act as Actor Right\"')\n\n    // const actors = await Aeppic.getAll(definition.documentactors)\n\n    // for (const actor of actors) {\n    //   const doc = await Aeppic.new('d4d31804-1519-4866-8808-52a4f656a041', document)\n\n    //   doc.addReference('workflow', document)\n    //   doc.addReference('actors', actor)\n\n    //   Aeppic.save(doc)\n    // }\n\n    locals.progress.push('Cloning locks')\n\n    const locks = await Aeppic.getAll(definition.data.locks)\n\n    const workflowActAsActorRights = []\n\n    for (const lock of locks) {\n      const cloned = await Aeppic.cloneDeep(lock, document)\n\n      const clonedLock = cloned.root\n      const groupNamedAsLock = await Aeppic.findOne(`a:${document.a[0]} f.id:${this.FORMS.Group} data.name:\"${clonedLock.data.name}\"`)\n\n      let addedLockPermissions = false\n\n      // adapt rights to\n      cloned.documents.forEach(doc => {\n        if (doc.f.id === this.FORMS.WorkflowActAsActorRights) {\n          doc.addReference('workflow', document)\n          workflowActAsActorRights.push(doc)\n          return\n        }\n\n        console.log({ groupNamedAsLock, clonedLock})\n\n        if (clonedLock.data.shared && doc.f.id === this.FORMS.Key && groupNamedAsLock) {\n          const key = doc\n          key.addReference('groups', groupNamedAsLock)\n\n          locals.progress.push(`   => Adding group ${groupNamedAsLock.data.name} to lock ${clonedLock.data.name}`)\n          addedLockPermissions = true\n        }\n      })\n\n      if (!addedLockPermissions) {\n        locals.progress.push(`   => No rights added to lock ${clonedLock.data.name}`)\n      }\n\n      Aeppic.saveAll(cloned.documents)\n    }\n\n    // Prevent workflow actor rights from being cloned afterwards in Workflow (relevant for individual rights only)\n    locals.progress.push('Moving all cloned \"Workflow Act as Actor Right\" to workflow configuration')\n\n    workflowActAsActorRights.forEach(actorRight => Aeppic.move(actorRight, document))\n\n    locals.progress.push('Done')\n  }\n} ","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"install-locks","namespace":"workflow","packages":[{"id":"10843fbf-4f0c-416b-be7d-42d6bbd0ae68","text":"Window","v":"aaf6a9dbaa4582ca"}],"parameters":[],"standalone":"REQUIRED","template":"<button @click.stop=\"controller.install()\" style=\"color:blue\">\n  Install locks from workflow definition\n\n  <div class=\"flex flex-col\">\n    <div v-for=\"progress of locals.progress\">\n      {{ progress }}\n    </div>\n  </div>\n</button>","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"bd1f6fd3-9fd8-4936-86a1-d5ee81991e8e","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-06-25T12:43:28+00:00","backedBy":"","by":"b7485498-f1f3-4ab1-87d1-9cb63ea890c8","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"9abaf3934e79826d"},"stamps":[],"t":1782391408684,"v":"5d0c0949dad21e20"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2025-12-05T16:58:52+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"ct":1764953932013,"data":{"cardinality":"BOTH","controller":"","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"intelligence","namespace":"aeppic","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"02a49a30-74dc-4f97-976b-287b6a8b1386","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-12-05T17:00:04+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"initial"},"stamps":[],"t":1764954004503,"v":"96731770d982576d"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"cloneOf":{"id":"297b7216-a620-43ee-a761-11698dd3d65b","text":"json","v":"72431ecae190dc49"},"created":{"at":"2026-02-20T09:53:51+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"ct":1771581231947,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    locals.theme = (await Aeppic.Preferences.getSetting('aeppic:developer:codeTheme')) || 'vs-dark'\n  }\n\n  async getHoverContent(word) {\n    const infos = await Aeppic.find(`id:${word}`)\n    if (!infos || !infos.length) {\n      return []\n    }\n\n    const doc = infos[0]\n\n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","css":"","description":"A control to display JSON source","device":"ALL","label":"","limitTypes":false,"name":"json","namespace":"rd","packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","text":"Monaco Editor","v":"0ea897d653ca79d6"}],"parameters":[],"standalone":"SUPPORTED","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:40em\" v-model=\"field\" :theme=\"locals.theme\" :readonly=\"readonly\" language=\"json\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"11f3bfd5-183e-424b-8923-f09d81e3d790","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-02-20T09:54:00+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"originalCloneOf":{"id":"297b7216-a620-43ee-a761-11698dd3d65b","text":"json","v":"72431ecae190dc49"},"p":"controls-folder","previous":{"v":"initial"},"stamps":[],"t":1771581240292,"v":"b8a398ab9e575be7"},{"id":"297b7216-a620-43ee-a761-11698dd3d65b","v":"72431ecae190dc49","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"json","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:40em\" v-model=\"field\" :theme=\"locals.theme\" :readonly=\"readonly\" language=\"json\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>","css":"","controller":"class Controller {\n  async init() {\n    locals.theme = (await Aeppic.Preferences.getSetting('aeppic:developer:codeTheme')) || 'vs-dark'\n  }\n\n  async getHoverContent(word) {\n    const infos = await Aeppic.find(`id:${word}`)\n    if (!infos || !infos.length) {\n      return []\n    }\n\n    const doc = infos[0]\n\n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0ea897d653ca79d6","text":"Monaco Editor"}],"device":"ALL","label":"","description":"A control to display JSON source"},"locks":[],"created":{"at":"2019-03-20T10:04:37+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2022-01-13T10:34:33+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936843,"ct":1721209936843,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"2892cb6bff17d580","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"0dac340f-de00-48a2-96de-862088ba2973","v":"ee9c345b94489883","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"security","name":"key","template":"<div class=\"_CSS-group\">\n  <ae-design class=\"_CSS-group-title\" name=\"title\" :document=\"document\"></ae-design>\n  \n  <div class=\"_CSS-features\">\n    <!-- TODO: show if this is a subgroup -->\n    <!-- <i v-if=\"account.data.allowDirectLogin\" class=\"fa fa-sign-in\" title=\"Direct anmelden erlaubt\"></i> -->\n  </div>\n</div>\n\n<!-- <ae-control name=\"security:accessable-documents\" :document=\"document\"></ae-control>  -->","css":"._CSS-group {\n  display:grid;\n  grid-template-rows: 24px min-content;\n  grid-template-columns: auto 1fr auto;\n  grid-column-gap: 8px;\n  grid-template-areas: \n    \"avatar account features\"\n    \"avatar profile none\";\n  flex-direction: row;\n  margin-bottom: 8px; \n  cursor: pointer;\n}\n\n._CSS-group-overlay  {\n  grid-area: avatar;\n  z-index:1;\n  align-self:end;\n  justify-self: end;\n  margin: 0px 0px;\n  text-shadow: 1px solid black;\n  border-radius:50%;\n  width:32px; \n  padding: 2px; \n}\n\n._CSS-account--disabled {\n  opacity: 0.8;\n  background: var(--ae-background);\n}\n\n._CSS-avatar {\n  grid-area: avatar; \n}\n\n._CSS-profile-title {\n  grid-area: profile; \n}\n\n._CSS-account-title {\n  grid-area: account; \n  font-weight: 500;\n}\n\n._CSS-features {\n  grid-area: features;\n  color: #777;\n}\n\n._CSS-account--disabled ._CSS-account-title * {\n  text-decoration:line-through;\n}\n\n._CSS-users {\n  overflow: hidden;\n}","controller":"","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-01-10T15:39:56+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936843,"ct":1721209936843,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"5ba90804c71f01fd","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"d3594ea4-74a9-4abf-b0b2-3809faa3330d","v":"3d3e751bcf024756","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"security","name":"keys","template":"<!-- <div v-show=\"false\">  -->\r\n  <!--<svg :id=\"id('toggle-off')\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 576 512\"><path d=\"M384 64H192C85.961 64 0 149.961 0 256s85.961 192 192 192h192c106.039 0 192-85.961 192-192S490.039 64 384 64zM48 256c0-79.583 64.404-144 144-144 79.582 0 144 64.404 144 144 0 79.582-64.404 144-144 144-79.582 0-144-64.404-144-144zm336 144h-65.02c86.704-76.515 86.683-211.504 0-288H384c79.582 0 144 64.404 144 144 0 79.582-64.404 144-144 144z\"/></svg>-->\r\n  <!-- <ae-icon :id=\"id('toggle-off')\" class=\"fa fa-toggle-off\"></ae-icon> -->\r\n<!-- </div>  -->\r\n<table class=\"_CSS-overview\">\r\n  <tr>\r\n    <th></th>\r\n    <th class=\"_CSS-lock\" v-for=\"lock of locals.sortedLocks\" :title=\"lock.data.name\">\r\n      <ae-design name=\"avatar--small\" :document=\"lock\"></ae-design>\r\n    </th>\r\n  </tr>\r\n  <tr >\r\n    <td class=\"_CSS-row-header\">Groups</td>\r\n  </tr>\r\n  <tr v-for=\"group of locals.sortedGroups\" class=\"_CSS-group-row\" >\r\n    <td class=\"_CSS-group\">\r\n      <ae-design class=\"_CSS-group_avatar\" name=\"avatar--small\" :document=\"group\"></ae-design>\r\n      <ae-design class=\"_CSS-group_info\" name=\"title\" :document=\"group\"></ae-design>\r\n    </td>\r\n    <td class=\"_CSS-key\" v-for=\"lock of locals.sortedLocks\" :title=\"lock.data.name\">\r\n      <!--<svg class=\"_CSS-key--off svg-inline--fa fa-w-20\"><use :href=\"'#'+id('toggle-off')\"></use></svg>-->\r\n      <i class=\"fa fa-toggle-off\"></i>\r\n    </td>\r\n  </tr>\r\n  <tr>\r\n    <td class=\"_CSS-row-header\">Accounts</td>\r\n  </tr>\r\n  <tr v-for=\"account of locals.sortedAccounts\" class=\"_CSS-account-row\" >\r\n    <td class=\"_CSS-account\">\r\n      <ae-design class=\"_CSS-account_avatar\" name=\"avatar--small\" :document-id=\"account.p\"></ae-design>\r\n      <ae-design class=\"_CSS-account_info\" name=\"title\" :document=\"account\"></ae-design>\r\n    </td>\r\n    <td class=\"_CSS-key\" v-for=\"lock of locals.sortedLocks\" :title=\"lock.data.name\" xmouseover=\"controller.showKeyPopup(account, lock, $event)\" xmouseleave=\"controller.hideKeyPopup(account, lock, $event)\">\r\n      <!-- <svg class=\"_CSS-key--off svg-inline--fa fa-w-20\"><use :href=\"'#'+id('toggle-off')\"></use></svg> -->\r\n      <i class=\"fa fa-toggle-off\"></i>\r\n    </td>\r\n  </tr>\r\n</table>\r\n\r\n<ae-popup ref=\"keyPopup\" :close-delay=\"2000\">\r\n  <div slot=\"popup\" slot-scope=\"context\" class=\"_CSS-popup\" xmouseenter=\"context.cancelClose()\" xmouseleave=\"context.close()\">\r\n    <div class=\"_CSS-popup_lock-avatar\">\r\n      <ae-design  name=\"avatar--small\" :document=\"context.data.lock\"></ae-design>\r\n      <!-- <ae-icon class=\"_CSS-lock-overlay fa fa-lock-alt fa-1x\"></ae-icon> -->\r\n    </div>\r\n    <ae-design class=\"_CSS-popup_lock-title\" name=\"title\" :document=\"context.data.lock\"></ae-design>\r\n    <ae-design class=\"_CSS-popup_account-avatar\" name=\"avatar--small\" :document-id=\"context.data.account.p\"></ae-design>\r\n    <div  class=\"_CSS-popup_account-title\">\r\n      <ae-design name=\"title\" :document-id=\"context.data.account.id\"></ae-design>\r\n    </div>\r\n  </div>\r\n</ae-popup>","css":"._CSS {\r\n  display: grid;\r\n  grid-template-columns: repeat(auto-fill, minmax(min-content, 200px));\r\n  grid-gap: 8px;\r\n}\r\n\r\n._CSS .fa-toggle-off {\r\n  opacity: 0.2;\r\n}\r\n\r\n._CSS .fa-toggle-off:before {\r\n  content: \"\\f204\";\r\n}\r\n\r\n._CSS  .fa-toggle-on:before {\r\n  content: \"\\f205\";\r\n}\r\n\r\n._CSS-overview-grid {\r\n  grid-template:\r\n    \".        lock\"\r\n    \"account  content\";\r\n  grid-template-columns: auto 1fr;\r\n  grid-template-rows: auto 1fr;\r\n}\r\n\r\n\r\n._CSS-overview td {\r\n  text-align: center;\r\n}\r\n\r\n._CSS-overview td:nth-child(2n+2) {\r\n  background: #DDD;\r\n}\r\n\r\n._CSS-lock * {\r\n  max-height: 24px;\r\n  max-width: 24px;\r\n}\r\n\r\n._CSS-key--off {\r\n  opacity:0.4;\r\n}\r\n\r\n._CSS-row-header {\r\n  font-size: 12px;\r\n  color: #777;\r\n  padding-top:8px;\r\n  height: 32px;\r\n}\r\n\r\n._CSS-group_avatar * {\r\n  max-height: 24px;\r\n  max-width: 24px;\r\n}\r\n\r\n._CSS-group_info {\r\n  position:absolute;\r\n  z-index:1;\r\n  background:white;\r\n  width: auto;\r\n  padding: 8px;\r\n  border-radius:2px;\r\n  border: 1px solid lightgray;\r\n  opacity:0;\r\n  transition: ease-in-out 0.2s;\r\n  pointer-events:none;\r\n}\r\n\r\n._CSS-group-row:hover ._CSS-group_info { \r\n  opacity:1;\r\n}\r\n\r\n\r\n._CSS-overview ._CSS-group-row:hover td {\r\n  background:lightgray;\r\n}\r\n\r\n._CSS-account_avatar *{\r\n  max-height: 24px;\r\n  max-width: 24px;\r\n} \r\n\r\n._CSS-account_info {\r\n  position:absolute; \r\n  z-index:1;\r\n  background:white;\r\n  width: auto;\r\n  padding: 8px;\r\n  border-radius:2px;\r\n  border: 1px solid lightgray;\r\n  opacity:0;\r\n  transition: ease-in-out 0.2s;\r\n  pointer-events:none;\r\n  \r\n}\r\n\r\n._CSS-account-row:hover ._CSS-account_info { \r\n  opacity:1;\r\n}\r\n\r\n._CSS-account-row:hover td {\r\n  background:lightgray;\r\n}\r\n\r\n._CSS-lock-overlay  {\r\n  grid-area: avatar;\r\n  z-index:1;\r\n  align-self:end;\r\n  justify-self: end;\r\n  color: #000;\r\n  margin: 2px 0px;\r\n  text-shadow: 1px solid black;\r\n  \r\n}\r\n\r\n\r\n\r\n._CSS-description {\r\n  grid-area: description;\r\n}\r\n\r\n._CSS-lock-icon  {\r\n  grid-area: avatar;\r\n  color: darkgray;\r\n  align-self: center;\r\n}\r\n\r\n._CSS-lock--disabled {\r\n  opacity: 0.8;\r\n  background: #ddd;\r\n}\r\n\r\n._CSS-avatar {\r\n  grid-area: avatar; \r\n}\r\n\r\n._CSS-profile-title {\r\n  grid-area: profile; \r\n}\r\n\r\n._CSS-lock-title {\r\n  grid-area: lock; \r\n}\r\n\r\n._CSS-features {\r\n  grid-area: features;\r\n  color: #777;\r\n}\r\n\r\n._CSS-lock--disabled ._CSS-lock-title * {\r\n  text-decoration:line-through;\r\n}\r\n\r\n._CSS-popup {\r\n  background:white;\r\n  border:lightgray 1px solid;\r\n  display: grid;\r\n  grid-template:\r\n    \"lock-avatar    lock-title\" \r\n    \"account-avatar account-title\";\r\n  background: #eee;\r\n}\r\n\r\n._CSS-popup * {\r\n  padding: 4px;\r\n}\r\n\r\n._CSS-popup_lock-avatar {\r\n  grid-area: lock-avatar;\r\n  align-self:center;\r\n  position:relative;\r\n}\r\n\r\n._CSS-popup_lock-title {\r\n  grid-area: lock-title;\r\n  align-self:center;\r\n}\r\n\r\n._CSS-popup_account-avatar {\r\n  grid-area: account-avatar;\r\n  align-self:center;\r\n  background:white;\r\n}\r\n\r\n._CSS-popup_account-title {\r\n  grid-area: account-title;\r\n  align-self:stretch;\r\n  justify-content: flex-start;\r\n  background:white;\r\n  display:flex;\r\n  align-items: center;\r\n}\r\n\r\n._CSS-popup_title {\r\n  font-size: 10px;\r\n  \r\n}\r\n\r\n._CSS-lock-overlay  {\r\n  color: #000;\r\n  right: 2px;\r\n  bottom: 0px;\r\n  position:absolute;\r\n}\r\n","controller":"init() {\r\n  locals.realm = document.p\r\n\r\n  locals.sortedLocks = []\r\n  locals.sortedAccounts = []\r\n  locals.popupDelayDefault = 1000\r\n  locals.popupDelay = locals.popupDelayDefault\r\n  locals.popupItem = null\r\n  locals.popupShowTimer = null\r\n\r\n  locals.profiles = {\r\n    NONE: [],\r\n\r\n  }\r\n\r\n  locals.sortedGroups = []\r\n\r\n  locals.locks = Aeppic.subscribe(`f.id:lock-form a:${locals.realm}`, (a) => {\r\n    const sorted = [...a]\r\n    sorted.sort((a,b) => a.data.name.localeCompare(b.data.name))\r\n \r\n    locals.sortedLocks.splice(0, locals.sortedLocks.length, ...sorted)\r\n    console.log('locks', locals.sortedLocks)\r\n  }) \r\n\r\n  locals.groups = Aeppic.subscribe(`f.id:e5c233c9-5425-48d7-a785-d962897bc6f8 a:${locals.realm}`, async (a) => {\r\n    const sorted = [...a]\r\n    sorted.sort((a,b) => a.data.name.localeCompare(b.data.name))\r\n\r\n    // const profiles = await Promise.all(sorted.map(a => this.getProfile(a)))\r\n    // profiles.sort((a,b) => {\r\n    //   return a && b ? a.data.name.localeCompare(b.data.name)\r\n    //           : a ? -1\r\n    //           : b ? 1\r\n    //           : 0 \r\n    // })\r\n    locals.sortedGroups.splice(0, locals.sortedGroups.length, ...sorted)\r\n  })\r\n\r\n    \r\n  locals.accounts = Aeppic.subscribe(`f.id:account a:${locals.realm} data.isGroupAccount:false`, async (a) => {\r\n    const sorted = [...a]\r\n    sorted.sort((a,b) => a.data.name.localeCompare(b.data.name))\r\n\r\n    // const profiles = await Promise.all(sorted.map(a => this.getProfile(a)))\r\n    // profiles.sort((a,b) => {\r\n    //   return a && b ? a.data.name.localeCompare(b.data.name)\r\n    //           : a ? -1\r\n    //           : b ? 1\r\n    //           : 0 \r\n    // })\r\n\r\n    console.log('accounts[0]',sorted[0].data.name)\r\n    // sorted.length=8\r\n    locals.sortedAccounts.splice(0, locals.sortedAccounts.length, ...sorted)\r\n  })\r\n}\r\n\r\nshowKeyPopup(account, lock, $event) {\r\n  locals.popupItem = { account, lock }\r\n\r\n  locals.popupShowTimer = Aeppic.setTimeout(() => {\r\n    locals.popupShowTimer = null\r\n\r\n    if (!locals.popupItem) {\r\n      return\r\n    }\r\n\r\n    if (locals.popupItem.lock !== lock ||\r\n        locals.popupItem.account !== account ) {\r\n      return\r\n    }\r\n    \r\n    $refs.keyPopup.show($event, locals.popupItem)\r\n\r\n  }, locals.popupDelay)\r\n}\r\n\r\nhideKeyPopup(account, lock, $event) {\r\n\r\n  if (!locals.popupItem) {\r\n    return\r\n  }\r\n\r\n  if (locals.popupItem.lock !== lock ||\r\n      locals.popupItem.account !== account ) {\r\n    return\r\n  }\r\n\r\n  if (locals.popupShowTimer) {\r\n    clearTimeout(locals.popupShowTimer)\r\n  } else {\r\n    $refs.keyPopup.close()\r\n  }\r\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-10-12T11:48:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936843,"ct":1721209936843,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"cfb229bbf3ddf8fa","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"1e6cb27b-696e-439c-88fa-8fe77e2a153c","v":"029a064f2de36eda","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"manufacturing","name":"label","template":"<button @click=\"controller.downloadLabel()\">\r\n  <ae-icon class=\"fa fa-download\"></ae-icon>\r\n</button>  ","css":"","controller":"init() {\r\n  const variablesToReplace = Object.keys(params).filter(n => n.startsWith('replace'))\r\n\r\n  locals.replacements = {}\r\n \r\n  Aeppic.watch(document, updateReplacements)\r\n  Aeppic.watchRevisions(document, updateReplacements)\r\n  updateReplacements()\r\n\r\n  function updateReplacements(d) {\r\n    variablesToReplace.forEach(name => {\r\n      const dataFieldName = params[name]\r\n      locals.replacements[name] = document.data[dataFieldName]\r\n    })\r\n  }\r\n}\r\n\r\nasync downloadLabel() {\r\n  console.log('download label')\r\n  // const hasFile = document.data[fieldName].dataUrl\r\n\r\n  // if (hasFile) {\r\n  //   const src = Aeppic.getSrc(document)\r\n  // }    \r\n\r\n  const templateFile = await Aeppic.get(params.template)\r\n\r\n  if (!templateFile) {\r\n    return\r\n  }\r\n\r\n  let labelName = params.name || 'Label.txt'\r\n\r\n  const keyPrefix = 'replace'\r\n\r\n  const contentUrl = await Aeppic.Content.getSrc(templateFile, 'datei')\r\n  const contentResponse = await Aeppic.fetch(contentUrl)\r\n\r\n  let templateContent = await contentResponse.text() \r\n  \r\n  for (const [key, value] of Object.entries(locals.replacements)) {\r\n    if (!key.startsWith(keyPrefix)) {\r\n      continue\r\n    }\r\n \r\n    const keyNameInTemplate = `\\\\$${key.substr(keyPrefix.length)}\\\\$`\r\n\r\n    templateContent = templateContent.replace(new RegExp(keyNameInTemplate, 'g'), value)\r\n    labelName = labelName.replace(new RegExp(keyNameInTemplate, 'g'), value)\r\n  }\r\n\r\n  if (locals.replacements.replaceDate) {\r\n    const day = locals.replacements.replaceDate.substr(8,2)\r\n    const month = locals.replacements.replaceDate.substr(5,2)\r\n    const year = locals.replacements.replaceDate.substr(0,4)\r\n    const yearShort = year.substr(2)\r\n    \r\n    templateContent = templateContent.replace(/\\$YYMMDD\\$/g, `${yearShort}${month}${day}`)\r\n    templateContent = templateContent.replace(/\\$YYYY-MM\\$/g, `${year}-${month}`)\r\n\r\n    debugger\r\n  }\r\n\r\n  const blob = new Blob([templateContent], { type: 'application/xml' })\r\n\r\n  Aeppic.Modules.FileSaver.saveAs(blob, labelName)\r\n}\r\n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-11-02T09:44:03+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2021-11-02T09:44:03+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936843,"ct":1721209936843,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"251f0de3c2594a3b","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"2598bdf2-107b-4229-b177-688e966ee77c","v":"0bc3316394a87f1c","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"layout-renderer","template":"<ae-expandable>\r\n  <ae-layout :layout-id=\"field.id\" :params=\"locals.params\" style=\"min-height:5em;border:1px solid rgba(0,0,0,0.4)\" :param-debug=\"true\"></ae-layout>\r\n</ae-expandable> ","css":"","controller":"init() {\r\n  locals.params = {}\r\n\r\n  // watchParam('params', () => {\r\n  const paramsAsString = document.data[params.params]\r\n  console.log(params, document) \r\n  locals.params = paramsAsString ? this._tryParse(paramsAsString) : {}\r\n  // }) \r\n}\r\n\r\n_tryParse(str) {\r\n  if (!str) return\r\n  if (typeof str !== 'string') return\r\n  if (!str.trim()) return\r\n  if (str.trim().indexOf('{') !== 0) return\r\n\r\n  try { \r\n    return JSON.parse(str)\r\n  } catch (error) {\r\n    console.error('Cannot parse', str)\r\n  }\r\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-06-27T08:59:37+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2021-03-23T13:10:06+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936843,"ct":1721209936843,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"25604104a171ab6c","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"cloneOf":{"id":"2598bdf2-107b-4229-b177-688e966ee77c","text":"layout-renderer","v":"0bc3316394a87f1c"},"created":{"at":"2025-07-28T10:07:55+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"ct":1753697275392,"data":{"cardinality":"BOTH","controller":"init() {\r\n  locals.params = {}\r\n\r\n  // watchParam('params', () => {\r\n  const paramsAsString = document.data[params.params]\r\n  console.log(params, document) \r\n  locals.params = paramsAsString ? this._tryParse(paramsAsString) : {}\r\n  // }) \r\n}\r\n\r\n_tryParse(str) {\r\n  if (!str) return\r\n  if (typeof str !== 'string') return\r\n  if (!str.trim()) return\r\n  if (str.trim().indexOf('{') !== 0) return\r\n\r\n  try { \r\n    return JSON.parse(str)\r\n  } catch (error) {\r\n    console.error('Cannot parse', str)\r\n  }\r\n}","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"layout-renderer","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<ae-expandable>\r\n  <ae-layout\r\n    :layout-id=\"field.id\"\r\n    :params=\"locals.params\"\r\n    style=\"min-height: 5em; border: 1px solid rgba(0, 0, 0, 0.4);height:80vh;\"\r\n    :param-debug=\"true\"\r\n  ></ae-layout>\r\n</ae-expandable>\r\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"d602afb9-5153-4a57-aed7-48d1c1467987","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-07-28T10:14:17+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"originalCloneOf":{"id":"2598bdf2-107b-4229-b177-688e966ee77c","text":"layout-renderer","v":"0bc3316394a87f1c"},"p":"controls-folder","previous":{"v":"79a47007b469fa32"},"stamps":[],"t":1753697657891,"v":"11eeb749b21ba402"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2023-09-18T13:35:32+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936843,"data":{"cardinality":"BOTH","controller":"","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"line","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<ae-control name=\"rd:text\" :document=\"document\" :params=\"params\" :field-name=\"fieldName\" :readonly=\"readonly\"></ae-control>\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"da7c2953-07c7-4fb3-a924-decc3f6634b5","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-03-06T10:21:04+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"42847edc7f79d320"},"stamps":[],"t":1741256464043,"v":"5eda595f665a8d66"},{"id":"55779b5b-d959-4e79-9019-cbce0e29c4d7","v":"e024927a9e3c91b0","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"model","name":"link-share","template":"<!-- <div class=\"ae-form-cell__input\" :class=\"{ 'ae-form-cell__input--no-border': params.withoutBorder }\"> -->\n<div>\n  <div class=\"_CSS-links\">\n    <div v-for=\"link of locals.links\">\n      <input type=\"text\" :value=\"locals.baseUrl + link.id\" style=\"width:100%;background:var(--ae-ui-background);\" readonly>\n    </div>\n  </div>\n</div>","css":"","controller":"class Controller {\n  init() {\n    this.FORMS = {\n      SharedDocument: '804bed89-803a-4090-8b1b-857cab8ec6dd'\n    }\n    locals.baseUrl = `${packages.window.location.origin}/invite/`\n\n    locals.links = []\n\n    Aeppic.subscribe(`p:${document.id} f.id:${this.FORMS.SharedDocument}`, (docs) => {\n      locals.links = docs\n    })\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"10843fbf-4f0c-416b-be7d-42d6bbd0ae68","v":"1e6bd8085315e112","text":"Window"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-11-25T13:55:33+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2021-07-27T09:51:39+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936844,"ct":1721209936844,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"8a3ff7bef1d13099","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"391cd336-efaa-4c9b-affe-f51a67a191c3","v":"910f78a8b42a69bc","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"list","template":"<ae-list :list-id=\"params.listId || '2e798091-303f-406b-a42a-93aa1cd8425c'\"\n          :search=\"locals.query\"\n          v-if=\"locals.query\"\n          @selected=\"controller.onSelected($event, arguments[1])\"\n          :sort=\"params.sort\"\n></ae-list>\n","css":"","controller":"class Controller {\n  init() {\n    locals.query = ''\n\n    const parts = []\n\n    if (params.ancestorFormAsParent) {\n      const ancestor = document.a[document.a_forms.findIndex(aForm => aForm === params.ancestorFormAsParent)]\n      parts.push(`p:${ancestor}`)\n    }\n\n    if (params.ancestorFormAsAncestor) {\n      const ancestor = document.a[document.a_forms.findIndex(aForm => aForm === params.ancestorFormAsAncestor)]\n      parts.push(`a:${ancestor}`)  \n    }\n\n    if (params.documentAsParent) {\n      parts.push(`p:${document.id}`)\n    }\n\n    if (params.documentAsAncestor) {\n      parts.push(`a:${document.id}`)\n    }\n\n    if (params.ancestorField) {\n      parts.push(`a:${document.data[params.ancestorField].id}`)\n    }\n\n    if (params.formId) {\n      parts.push(`f.id:${params.formId}`)\n    } else if (fieldInfo.formRef) {\n      parts.push(`f.id:${fieldInfo.formRef}`)\n    }\n\n    if (params.filterDocumentInField) {\n      parts.push(`data.${params.filterDocumentInField}:${document.id}`)\n    }\n\n    locals.query = parts.join(' ')\n  }\n\n  onSelected(event, args) {\n    if (readonly) {\n      return\n    }\n\n    Aeppic.drillInto(event, args)\n  }\n}\n\n\n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-02-25T14:25:40+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-07-25T14:11:21+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936844,"ct":1721209936844,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"b6d4e4765a8bf7e5","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"2c6f12c9-c3ad-4311-8989-cc582d7780c4","v":"011a75a276800ffc","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"security","name":"locks","template":"<div class=\"_CSS-lock\" v-for=\"lock of locals.sortedLocks\" :key=\"lock.id\" @click=\"Aeppic.drillInto(lock)\">\r\n  <ae-design class=\"_CSS-lock-icon\" name=\"avatar--small\" :document=\"lock\"></ae-design>\r\n  <ae-icon class=\"_CSS-lock-overlay fa fa-lock-alt\"></ae-icon>\r\n  <ae-design class=\"_CSS-lock-title\" name=\"title\" :document=\"lock\"></ae-design>\r\n  <div class=\"_CSS-description\">{{lock.data.description}}</div>\r\n  <div class=\"_CSS-features\"> \r\n    <!-- <i v-if=\"lock.data.allowDirectLogin\" class=\"fa fa-sign-in\" title=\"Direct anmelden erlaubt\"></i> -->\r\n  </div>\r\n</div>","css":"._CSS {\r\n  display: grid;\r\n  grid-template-columns: repeat(auto-fill, minmax(200px, max-content));\r\n  grid-gap: 8px;\r\n}\r\n\r\n._CSS-lock {\r\n  display:grid;\r\n  grid-template-rows: 24px 24px;\r\n  grid-template-columns: auto 1fr auto;\r\n  grid-column-gap: 8px;\r\n  grid-template-areas: \r\n    \"avatar lock features\"\r\n    \"avatar description none\";\r\n  flex-direction: row;\r\n  margin-bottom: 8px; \r\n  background: #fff;\r\n  border: 1px solid lightgray;\r\n  padding: 4px 8px;\r\n  cursor: pointer;\r\n}\r\n\r\n._CSS-lock-overlay  {\r\n  grid-area: avatar;\r\n  z-index:1;\r\n  align-self:end;\r\n  justify-self: end;\r\n  color: #000;\r\n  margin: 2px 0px;\r\n  text-shadow: 1px solid black;\r\n  \r\n}\r\n\r\n\r\n\r\n._CSS-description {\r\n  grid-area: description;\r\n}\r\n\r\n._CSS-lock-icon  {\r\n  grid-area: avatar;\r\n  color: darkgray;\r\n  align-self: center;\r\n}\r\n\r\n._CSS-lock--disabled {\r\n  opacity: 0.8;\r\n  background: #ddd;\r\n}\r\n\r\n._CSS-avatar {\r\n  grid-area: avatar; \r\n}\r\n\r\n._CSS-profile-title {\r\n  grid-area: profile; \r\n}\r\n\r\n._CSS-lock-title {\r\n  grid-area: lock; \r\n}\r\n\r\n._CSS-features {\r\n  grid-area: features;\r\n  color: #777;\r\n}\r\n\r\n._CSS-lock--disabled ._CSS-lock-title * {\r\n  text-decoration:line-through;\r\n}\r\n\r\n","controller":"init() {\r\n  locals.realm = document.p\r\n  locals.sortedLocks = []\r\n  \r\n  locals.locks = Aeppic.subscribe(`f.id:lock-form (a:${locals.realm} OR a:apps)`, (a) => {\r\n    const sorted = [...a]\r\n    sorted.sort((a,b) => a.data.name.localeCompare(b.data.name))\r\n \r\n    locals.sortedLocks.splice(0, locals.sortedLocks.length, ...sorted)\r\n    console.log('locks', locals.sortedLocks)\r\n  }) \r\n}\r\n","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-10-12T11:48:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936844,"ct":1721209936844,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"31091a4c4a01123a","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"74bf5a4c-42bc-4a6e-8a52-a1e4a7fb2be3","v":"2383f713e19cb9b4","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"mails","template":"\r\n<template v-if=\"!controller.readonly\">\r\n  <div v-if=\"params.readonly\" class=\"_CSS-file\" ref=\"container\">\r\n    <div class=\"_CSS-file__drop-area _CSS-file__drop-area--readonly _CSS-file__input-container\" @drop.prevent.stop @dragenter.prevent.stop @dragleave.prevent.stop >\r\n      <svg class=\"_CSS-file--no-thumbnail\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 384 512\"><path d=\"M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm160-14.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z\"/></svg>\r\n      Keine Datei vorhanden\r\n    </div> \r\n  </div>\r\n  <div v-else class=\"_CSS-file\" ref=\"container\">\r\n    <div\r\n        class=\"_CSS-file__drop-area _CSS-file__input-container\"\r\n        @dragenter.prevent.stop=\"locals.dragHover = true\"\r\n        @dragleave.prevent.stop=\"locals.dragHover = false\"\r\n        @dragover.prevent.stop=\"controller.onDragOver($event)\"\r\n        @drop.prevent.stop=\"controller.drop($event)\"\r\n        :class=\"{ '_CSS-file__drop-area--hover': locals.dragHover }\"\r\n    >\r\n      <template v-if=\"locals.error\">\r\n        <ae-icon class=\"fa fa-exclamation-triangle\"></ae-icon> \r\n        <span style=\"margin-left:var(--ae-space-1)\"> Fehler bei der Verarbeitung </span>\r\n      </template>\r\n      <template v-if=\"locals.working\">\r\n        <ae-icon class=\"fa fa-circle-notch fa-spin\"></ae-icon> \r\n        <span style=\"margin-left:var(--ae-space-1)\"> Mail wird verarbeitet ... </span>\r\n      </template>\r\n      <template v-else>\r\n        <svg class=\"_CSS-file--no-thumbnail\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 384 512\"><path d=\"M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm160-14.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z\"/></svg>\r\n        <span>Mails aus Outlook Postfach, <i>*.msg</i> oder <i>*.eml</i> Dateien hier ablegen oder</span>&nbsp;\r\n\r\n        <a v-if=\"!params.autoOpen\" class=\"_CSS-file__input-link\" @click=\"$refs.input.click()\">\r\n          <span>auswählen</span>\r\n        </a>\r\n        <a v-else class=\"_CSS-file__input-link\" @click=\"$refs.input.click()\" @focus=\"$refs.input.click()\" v-focus tabindex=\"-1\">\r\n          <span>auswählen</span>\r\n        </a>\r\n      </template>\r\n    </div> \r\n  </div>\r\n  <input @change=\"controller.onFilesSelected($event)\" multiple ref=\"input\" type=\"file\" style=\"display: none;\">\r\n  <!-- <img ref=\"hiddenImage\" :src=\"field.dataUrl\" @load=\"controller.imageLoaded()\" style=\"display: none;\"> -->\r\n</template>\r\n\r\n<ae-control name=\"ref\"\r\n            :document=\"document\"\r\n            :field-name=\"fieldName\"\r\n            :param-no-add=\"true\"\r\n            :param-handle-selected=\"false\"\r\n            @selected=\"controller.mailSelected($event, arguments[1])\" \r\n            :param-readonly=\"controller.readonly\"\r\n            :param-handle-remove=\"true\"\r\n            :param-confirm-remove=\"true\"\r\n            :param-handle-selected-readonly=\"true\"\r\n            :param-list=\"true\"\r\n            :param-disable-sort=\"true\"\r\n            :param-sort=\"locals.sort\"\r\n            param-design=\"ref-card\"\r\n            :param-collapse-after=\"20\"\r\n            ref=\"control\"\r\n></ae-control>\r\n\r\n<ae-dialog ref=\"preview\">\r\n  <template v-slot:header=\"scope\">\r\n    <div class=\"_CSS-dialog__header\">\r\n      <ae-design name=\"title\" :document=\"locals.previewDocument\"></ae-design>\r\n      <button class=\"ae-button\" @click.stop=\"scope.close()\">\r\n        <ae-icon class=\"fa fa-times\"></ae-icon>\r\n      </button>\r\n    </div>\r\n  </template>\r\n  <ae-design slot=\"body\" name=\"page\" :document=\"locals.previewDocument\" :param-readonly=\"true\"></ae-design>\r\n</ae-dialog>\r\n    \r\n<ae-popup ref=\"convertingInfo\">\r\n  <template #default=\"scope\">\r\n    <div class=\"_CSS-popup__body\">\r\n      <ae-icon class=\"fa fa-info\"></ae-icon>\r\n      Roh-Mail einlesen und konvertieren:\r\n\r\n      <div>\r\n        <template v-if=\"locals.error\">\r\n        <ae-icon class=\"fa fa-exclamation-triangle\"></ae-icon> \r\n        <span style=\"margin-left:var(--ae-space-1)\"> Fehler bei der Verarbeitung </span>\r\n      </template>\r\n      <template v-else-if=\"locals.working\">\r\n        <ae-icon class=\"fa fa-circle-notch fa-spin\"></ae-icon> \r\n        <span style=\"margin-left:var(--ae-space-1)\"> Mail wird verarbeitet ... </span>\r\n      </template>\r\n      <template v-else>\r\n        <ae-icon class=\"fa fa-check\"></ae-icon> \r\n        <span style=\"margin-left:var(--ae-space-1)\"> Mail erfolgreich eingelesen </span>\r\n      </template>\r\n      </div>\r\n    </div>\r\n  </template>\r\n</ae-popup>    \r\n\r\n<ae-layout layout-id=\"6ee77ae8-b2f6-4fce-8fbc-788a55d5f7da\"\r\nref=\"removeConfirmDialog\"\r\n@delete=\"controller.removeReference($event)\"\r\n></ae-layout>\r\n","css":"._CSS-file__input-container {\r\n  padding: 8px;\r\n  display: flex;\r\n  align-items: center;\r\n}\r\n\r\n._CSS-file__input-link {\r\n  cursor: pointer;\r\n  color: var(--ae-foreground-l1);\r\n  font-weight: bold;\r\n}\r\n\r\n._CSS-file__input-link:hover,_CSS-file__input-link:focus {\r\n  text-decoration: underline;\r\n}\r\n\r\n._CSS-file__description {\r\n  overflow: hidden;\r\n  text-overflow: ellipsis;\r\n  flex: 1;\r\n}\r\n\r\n._CSS-file__drop-area {\r\n  border: var(--ae-border-width-2) dashed var(--ae-border-color);\r\n  border-radius: var(--ae-border-radius);\r\n  padding: 2em 1em;\r\n  color: var(--ae-foreground-l2);\r\n  background: var(--ae-ui-background);\r\n}\r\n._CSS-file__drop-area._CSS-file__drop-area--readonly {\r\n  border: var(--ae-border-width-1) solid var(--ae-border-color);\r\n}\r\n\r\n._CSS-file__drop-area--hover {\r\n  animation: _CSS-pulse 2s infinite;\r\n  background-color: var(--ae-primary-100);\r\n}\r\n\r\n@keyframes _CSS-pulse {\r\n  0% {\r\n    background-color: var(--ae-primary-100);\r\n  }\r\n  50% {\r\n    background-color: var(--ae-primary-200);\r\n  }\r\n  100% {\r\n    background-color: var(--ae-primary-100);\r\n  }\r\n}\r\n\r\n._CSS-file--no-thumbnail {\r\n  align-self:center;\r\n  height: 1.5rem;\r\n  opacity: 0.5;\r\n  padding-right: 8px;\r\n  fill: var(--ae-foreground-l2);\r\n}\r\n\r\n._CSS-preview-header {\r\n  display: flex;\r\n  justify-content: space-between;\r\n}\r\n\r\n._CSS-preview-header__entry {\r\n  display: flex;\r\n  align-items: center;\r\n  flex: 1;\r\n}\r\n\r\n._CSS-file-thumbnail {\r\n  height: 4rem;\r\n  width: 4rem;\r\n  background-size: cover;\r\n  background-repeat: no-repeat;\r\n  background-position-x: center;\r\n  background-color: #e2e2e2;\r\n  cursor: pointer;\r\n}\r\n\r\n._CSS-dialog__header {\r\n  display: flex;\r\n  justify-content: space-between;\r\n  align-items: center;\r\n}\r\n\r\n._CSS-popup__body {\r\n  padding: var(--ae-size-4);\r\n}","controller":"class Controller {\r\n  async init() {\r\n    this.FORMS = {\r\n      Mail: '59b7188e-1296-4466-a9c2-d6255496e7d6',\r\n      Datei: params.fileFormId || '6d3f6460-c6d2-4df1-8c05-e8bb66c5b8d2',\r\n      Kontakt: params.contactFormId || 'f8fd3176-4995-455f-a8a8-d2742a836816',\r\n      Mitarbeiter: 'd2ac3563-3c3f-4f02-b923-9b67abf98d10',\r\n    }\r\n\r\n    this.contactEmailFields = params.contactEmailFields ? params.contactEmailFields.split(',') : ['email', 'email2', 'email3']\r\n\r\n    locals.working = false\r\n    locals.previewDocument = null\r\n    locals.dragHover = false\r\n    locals.error = false\r\n    locals.sort = [\r\n      {\r\n        fields: ['data.empfangen','created.at'],\r\n        descending: true\r\n      },\r\n    ]\r\n\r\n    this.dropHandler = event => this.drop(event)\r\n    this.selectedHandler = event => this.selected(event)\r\n    this.pasteHandler = event => this.paste(event)\r\n\r\n    $on('drop', this.dropHandler)\r\n    // $on('input', this.selectedHandler)\r\n    // $on('paste', this.pasteHandler)\r\n  }\r\n\r\n  get readonly() {\r\n    if (params.readonly === true) {\r\n      return true\r\n    }\r\n\r\n    if (typeof readonly === 'boolean') {\r\n      return readonly\r\n    }\r\n\r\n    return false\r\n  }\r\n\r\n  async onFilesSelected(event) { \r\n    const files = event.target.files\r\n    await this.handleFiles(files)\r\n  }\r\n\r\n  async handleFiles(files) {\r\n    if (locals.working) {\r\n      return\r\n    }\r\n\r\n    locals.error = false\r\n    locals.working = true\r\n\r\n    try {\r\n      for (const f of files) {\r\n        if ('file' in f) {\r\n          const asFile = await this.convertToFile(f)\r\n          await this.handleFile(asFile)\r\n        } else {\r\n          await this.handleFile(f)\r\n        }\r\n      }\r\n    } catch (e) {\r\n      console.error('Error handling files', error)\r\n\r\n      locals.error = true\r\n    }\r\n\r\n    locals.working = false\r\n  }\r\n\r\n  async convertToFile(fileEntry) {\r\n    return new Promise((resolve, reject) => {\r\n      fileEntry.file(resolve,reject)\r\n    })\r\n  }\r\n\r\n  \r\n  onDragOver(e) {\r\n    var dt =  e.dataTransfer\r\n    dt.dropEffect = \"copy\"\r\n  }\r\n\r\n  async drop(event) {\r\n    locals.dragHover = false\r\n\r\n    const fileList = event.dataTransfer.files || event.dataTransfer.items\r\n\r\n    if (!fileList.length) {\r\n      return\r\n    }\r\n\r\n    const files = []\r\n    const directories = []\r\n\r\n    for (const fileListItem of fileList) {\r\n      if (fileListItem.webkitGetAsEntry) {\r\n        const fileSystemEntry = fileListItem.webkitGetAsEntry()\r\n        fileSystemEntry.type = fileListItem.type\r\n\r\n        if (fileSystemEntry.isFile) {\r\n          files.push(fileSystemEntry)\r\n        } else {\r\n          directories.push(fileSystemEntry)\r\n        }\r\n      } else {\r\n        files.push(fileListItem)\r\n      }\r\n    }\r\n\r\n    params.debug && console.log('drop', files, directories)\r\n\r\n    await this.handleFiles(files)\r\n  }\r\n\r\n  async handleFile(file) {\r\n    return new Promise((resolve, reject) => {\r\n      const fileReader = new FileReader()\r\n\r\n      fileReader.onload = (loadEvent) => {\r\n        const buffer = loadEvent.target.result\r\n\r\n\r\n        if (file.type === 'message/rfc822') {\r\n          const asString = new TextDecoder('utf-8').decode(new Uint8Array(buffer))\r\n          const parsed = packages.EmlParseJs.readEml(asString) \r\n          // console.log(parsed, asString) \r\n          // return\r\n\r\n          resolve(this.handleEml(file, parsed))\r\n        } else {\r\n          console.log(packages)\r\n          const msgReader = new packages.MSGReader(buffer)\r\n          const fileData = msgReader.getFileData()\r\n\r\n          if (fileData.error) {\r\n            return reject(fileData.error)\r\n          }\r\n\r\n          resolve(this.handleMsg(file, fileData, msgReader))\r\n        }\r\n      }\r\n\r\n      fileReader.readAsArrayBuffer(file)\r\n    })\r\n  }\r\n\r\n  async handleEml(file, parsedEml) {\r\n    const mail = await Aeppic.new(this.FORMS.Mail, document.id)\r\n    mail.data.name = parsedEml.subject || file.name || ''\r\n\r\n    Aeppic.save(mail)\r\n\r\n    // wait some time - but why?\r\n    await this.sleep(345)\r\n\r\n    mail.setFile('emlFile', file)\r\n\r\n    if (parsedEml.from) {\r\n      mail.data.von = this._emlReceiverAsMailto(parsedEml.from)\r\n    }\r\n\r\n    mail.data.text = parsedEml.text\r\n\r\n    if (parsedEml.htmlheaders?.['Content-Transfer-Encoding'] === 'base64') {\r\n      mail.data.html = decodeURIComponent(\r\n        atob(parsedEml.html).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')\r\n      )\r\n    } else {\r\n      mail.data.html = parsedEml.html\r\n    }\r\n\r\n    console.log(parsedEml)\r\n\r\n    if (parsedEml.from) {\r\n      const senderContact = await this.lookupContact(parsedEml.from.email, parsedEml.from.name)\r\n\r\n      if (senderContact) {\r\n        mail.addReference('absender', senderContact)\r\n      }\r\n    }\r\n\r\n    if (parsedEml.to) {\r\n      const recipients = Array.isArray(parsedEml.to) ? parsedEml.to : [parsedEml.to]\r\n\r\n      for (const recipient of recipients) {\r\n        const contact = await this.lookupContact(recipient.email, recipient.name)\r\n\r\n        if (contact) {\r\n          mail.addReference('empfaenger', contact)\r\n        }\r\n\r\n        const asMailto = this._emlReceiverAsMailto(recipient)\r\n\r\n        if (asMailto) {\r\n          mail.data.an.push(asMailto)\r\n        }\r\n      }\r\n    }\r\n\r\n    mail.data.headers = [JSON.stringify(parsedEml.headers)]\r\n\r\n    if (parsedEml.headers['Delivery-date']) {\r\n      try {\r\n        mail.data.empfangen = parsedEml.headers['Delivery-date'] ? Aeppic.DateTime.fromRFC2822(parsedEml.headers['Delivery-date'].split(/\\n|\\r\\n/)[0]).toISO() : ''\r\n      } catch (e) {\r\n        console.error('Could not parse headers.Develivery-Date: ', parsedEml.headers['Delivery-date'], e.toString(), e.stack, e)\r\n      }\r\n    } else if (parsedEml.headers['X-Received']) {\r\n      try {\r\n        // example: 0000:a11:1111:11a1:: with SMTP id somename.000.1638449117913 \\n Thu, 02 Dec 2021 04:45:17 -0800 (PST)\r\n        mail.data.empfangen = parsedEml.headers['X-Received'] ? Aeppic.DateTime.fromRFC2822(parsedEml.headers['X-Received'].split(/\\n|\\r\\n/)[1]).toISO() : ''\r\n      } catch (e) {\r\n        console.error('Could not parse headers.Develivery-Date: ', parsedEml.headers['X-Received'], e.toString(), e.stack, e)\r\n      }\r\n    }\r\n\r\n    try {\r\n      // example: Thu, 2 Dec 2021 12:45:15 +0000 \\r\\n <FOOBAR@FOOBAR.foobar.foobar.example.com>\r\n      mail.data.gesendet = parsedEml.headers.Date ? Aeppic.DateTime.fromRFC2822(parsedEml.headers.Date.split(/\\n|\\r\\n/)[0]).toISO() : ''\r\n    } catch (e) {\r\n      console.error('Could not parse headers.Date: ', parsedEml.headers.Date, e.toString(), e.stack, e)\r\n    }\r\n\r\n    mail.data.messageId = parsedEml.headers['Message-ID'] || ''\r\n    mail.data.threadId = parsedEml.headers['Thread-Index'] || ''\r\n\r\n    if (parsedEml.attachments?.length) {\r\n      for (const attachment of parsedEml.attachments) {\r\n        const datei = await Aeppic.new(this.FORMS.Datei, mail.id)\r\n        datei.data.name = attachment.name\r\n\r\n        const mimeType = attachment.contentType.split(';')[0] || 'application/octet-stream'\r\n\r\n        const base64ToFile = (base64, fileName, type = 'application/octet-stream') => {\r\n          const decoded = atob(base64)\r\n          let length = decoded.length\r\n          const u8Array = new Uint8Array(length)\r\n          while (length--) {\r\n            u8Array[length] = decoded.charCodeAt(length)\r\n          }\r\n\r\n          return new File([u8Array], fileName, { type: mimeType })\r\n        }\r\n\r\n \r\n        const file = base64ToFile(attachment.data64, mimeType)\r\n        datei.setFile('datei', file)\r\n        Aeppic.save(datei)\r\n\r\n        mail.addReference('anhaenge', datei)\r\n      }\r\n    }\r\n\r\n    Aeppic.save(mail)\r\n\r\nconsole.log('addRef', { fieldName, mail })\r\n    document.addReference(fieldName, mail)\r\n  }\r\n\r\n  _emlReceiverAsMailto(receiver) {\r\n    if (!receiver || (!receiver.email?.trim() && !receiver.name?.trim())) {\r\n      return ''\r\n    }\r\n\r\n    if (receiver.name.trim()) {\r\n      return `${receiver.name?.trim()} <${receiver.email.trim()}>`\r\n    } \r\n\r\n    return receiver.email?.trim()\r\n  }\r\n\r\n  async lookupContact(email, name) {\r\n    const matchingEmployee = await Aeppic.findOne(`a:${document.a[0]} f.id:${this.FORMS.Mitarbeiter} data.email:${email}`, { sort: { field: 'modified.at', descending: true } })\r\n\r\n    if (matchingEmployee) {\r\n      return matchingEmployee\r\n    }\r\n\r\n    const contactEmailFieldsQuery = this.contactEmailFields.map(f => `data.${f}:${email}`).join(' OR ')\r\n    const matchingContact = await Aeppic.findOne(`a:${document.a[0]} f.id:${this.FORMS.Kontakt} (${contactEmailFieldsQuery})`, { sort: { field: 'modified.at', descending: true } })\r\n\r\n    if (matchingContact) {\r\n      return matchingContact\r\n    }\r\n\r\n    return null\r\n  }\r\n\r\n  sleep(ms = 500) {\r\n    return new Promise(resolve => setTimeout(resolve, ms))\r\n  }\r\n\r\n  async handleMsg(file, data, reader) {\r\n    const mail = await Aeppic.new(this.FORMS.Mail, document.id)\r\n    mail.data.name = data.subject\r\n    Aeppic.save(mail)\r\n    \r\n    await this.sleep(340)\r\n    \r\n    mail.setFile('msgFile', file)\r\n\r\n    if (data.senderName.trim() !== '') {\r\n      mail.data.von = `${data.senderName} <${data.senderEmail}>`\r\n    } else {\r\n      mail.data.von = data.senderEmail\r\n    } \r\n\r\n    if (data.senderEmail) {\r\n      const senderContact = await this.lookupContact(data.senderEmail, data.senderName)\r\n\r\n      if (senderContact) {\r\n        mail.addReference('absender', senderContact)\r\n      }\r\n    }\r\n\r\n    mail.data.text = data.body\r\n\r\n\r\n    if (data.recipients) {\r\n      for (const recipient of data.recipients) {\r\n        const contact = await this.lookupContact(recipient.email, recipient.name)\r\n\r\n        if (contact) {\r\n          mail.addReference('empfaenger', contact)\r\n        } else {\r\n          mail.data.an.push(`${recipient.email} <${recipient.name}>`)\r\n        }\r\n      }\r\n    }\r\n\r\n    if (data.headers) {\r\n      const headers = []\r\n\r\n      const LINES_STARTS_WHITESPACE_REGEX = /^\\s+/\r\n\r\n      for (const line of data.headers.split('\\n')) {\r\n        if (!line) {\r\n          continue\r\n        }\r\n        \r\n        if (line.match(LINES_STARTS_WHITESPACE_REGEX)) {\r\n          let lastHeaderLine = headers[headers.length - 1]\r\n\r\n          if (!lastHeaderLine) {\r\n            console.warn('Header corrupted. Should not start with whitespace')\r\n            continue\r\n          } \r\n\r\n          lastHeaderLine += line\r\n          headers[headers.length] = lastHeaderLine\r\n        } else {\r\n          headers.push(line)\r\n        }\r\n      }\r\n\r\n      mail.data.headers = headers\r\n\r\n      mail.data.empfangen = this._readReceived(headers)\r\n      mail.data.gesendet = this._readDate(headers)\r\n      mail.data.messageId = this._readHeader(headers, 'Message-ID')\r\n      mail.data.threadId = this._readHeader(headers, 'Thread-Index')\r\n    }\r\n\r\n    const attachmentFiles = data.attachments.map((a, index) => {\r\n      const fileInfo = reader.getAttachment(index)\r\n      return new File([fileInfo.content], a.fileName,  { type: a.mimeType ? a.mimeType : 'application/octet-stream' })\r\n    })\r\n\r\n    for (const attachmentFile of attachmentFiles) {\r\n      const datei = await Aeppic.new(this.FORMS.Datei, mail.id)\r\n      datei.data.name = attachmentFile.name\r\n      datei.setFile('datei', attachmentFile)\r\n      Aeppic.save(datei)\r\n\r\n      mail.addReference('anhaenge', datei)\r\n    }\r\n\r\n    Aeppic.save(mail)\r\n\r\n    document.addReference(fieldName, mail)\r\n  }\r\n\r\n  _readDate(headers) {\r\n    const dateString = this._readHeader(headers, 'Date')\r\n\r\n    if (!dateString) {\r\n      return ''\r\n    }\r\n\r\n    try {\r\n      const date = Aeppic.DateTime.fromRFC2822(dateString)\r\n      return date.toISO()\r\n    } catch (e) {\r\n      console.error(e)\r\n      return ''\r\n    }\r\n  }\r\n\r\n  _readHeader(headers, headerFieldName) {\r\n    const header = headers.find(h => h.startsWith(`${headerFieldName}:`))\r\n\r\n    if (!header) {\r\n      return ''\r\n    }\r\n    \r\n    const value = header.substr(headerFieldName.length + 1)\r\n    return value.trim()\r\n  }\r\n\r\n  _readReceived(headers) {\r\n    const topMostReceivedHeaderWithDate = headers.find(h => h.startsWith('Received:') && h.indexOf(';') > 0)\r\n\r\n    if (!topMostReceivedHeaderWithDate) {\r\n      return ''\r\n    }\r\n\r\n    const dateString = topMostReceivedHeaderWithDate.split(';').pop().trim()\r\n    if (!dateString) {\r\n      return ''\r\n    }\r\n\r\n    try {\r\n      return Aeppic.DateTime.fromRFC2822(dateString).toISO()\r\n    } catch(e) {\r\n      console.error(e)\r\n    }\r\n\r\n\r\n    // fallback to JSDate parsing\r\n    try {\r\n      return Aeppic.DateTime.fromJSDate(new Date(dateString)).toISO()\r\n    } catch (e) {\r\n      console.error(e)\r\n      return ''\r\n    }\r\n  }\r\n\r\n \r\n  async mailSelected(event, args1) {\r\n    const doc = await Aeppic.get(event)\r\n    \r\n    if (!doc) {\r\n      return\r\n    }\r\n\r\n    const hasMailAttached = doc.data.msgFile?.dataUrl || doc.data.emlFile?.dataUrl\r\n    const hasMailExtractionMarks =  doc.data.name ||\r\n                                    doc.data.gesendet ||\r\n                                    doc.data.empfangen ||\r\n                                    doc.data.von ||\r\n                                    doc.data.an.length ||\r\n                                    doc.data.text.length ||\r\n                                    doc.data.html ||\r\n                                    doc.data.fullMail ||\r\n                                    doc.data.headers.length\r\n\r\n\r\n    if (hasMailAttached && !hasMailExtractionMarks) {\r\n      const fileResponse = await Aeppic.fetch(Aeppic.Content.getUrl(doc, 'msgFile') || Aeppic.Content.getUrl(doc, 'emlFile'))\r\n\r\n      $refs.convertingInfo.show({ target: $refs.control.$el }) \r\n\r\n\r\n      await this.handleFiles([await fileResponse.blob()])\r\n\r\n      document.removeReference('mails', doc)\r\n      return \r\n    }\r\n \r\n    Aeppic.drillInto(doc, args1) \r\n  } \r\n \r\n}  ","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":["ref"],"packages":[{"id":"6116c361-ed38-4e7c-81d3-a8120cb2d158","v":"8ef24dc38517c324","text":"Msg Reader"},{"id":"cc3737e4-1695-44ef-bb6a-6e4ab6eca60e","v":"cee430379ce75925","text":"eml-parse-js"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-08-22T12:18:47+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2024-02-16T13:28:21+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936844,"ct":1721209936844,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"0d39189ab3922f5f","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-08-24T14:16:37+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936844,"data":{"cardinality":"BOTH","controller":"class Controller {\r\n  init() {\r\n    this.FORMS = {\r\n      Mail: '59b7188e-1296-4466-a9c2-d6255496e7d6',\r\n      Datei: params.fileFormId || 'dbee6e16-d63a-493c-bba6-505f79918f9f',\r\n      EnergieKontakt: 'f8fd3176-4995-455f-a8a8-d2742a836816',\r\n      Kontakt: params.contactFormId || '6f641574-7cb6-424a-9657-4b8d652a2cf7',\r\n      Mitarbeiter: 'd2ac3563-3c3f-4f02-b923-9b67abf98d10',\r\n    }\r\n\r\n    this.paramContactEmailFields = params.contactEmailFields ? params.contactEmailFields.split(',') : []\r\n    this.energyContactEmailFields = ['email', 'email2', 'email3']\r\n    this.contactEmailFields = ['email']\r\n\r\n    locals.working = false\r\n    locals.previewDocument = null\r\n    locals.dragHover = false\r\n    locals.error = false\r\n    locals.sort = [\r\n      {\r\n        fields: ['data.gesendet', 'data.empfangen', 'created.at'],\r\n        descending: true,\r\n      },\r\n    ]\r\n\r\n    locals.dropdownOptions = [\r\n      {\r\n        identifier: 'resort',\r\n        icon: 'far fa-sort',\r\n        display: 'Neu sortieren',\r\n        title: 'Neu sortieren (gesendet -> empfangen -> erstellt)',\r\n      }\r\n    ]\r\n\r\n    this.dropHandler = (event) => this.drop(event)\r\n    this.selectedHandler = (event) => this.selected(event)\r\n    this.pasteHandler = (event) => this.paste(event)\r\n\r\n    $on('drop', this.dropHandler)\r\n    // $on('input', this.selectedHandler)\r\n    // $on('paste', this.pasteHandler)\r\n  }\r\n\r\n  get readonly() {\r\n    if (params.readonly === true) {\r\n      return true\r\n    }\r\n\r\n    if (typeof readonly === 'boolean') {\r\n      return readonly\r\n    }\r\n\r\n    return false\r\n  }\r\n\r\n  async onFilesSelected(event) {\r\n    const files = event.target.files\r\n    await this.handleFiles(files)\r\n\r\n    if ($refs.input) {\r\n      $refs.input.value = ''\r\n    }\r\n  }\r\n\r\n  async handleFiles(files) {\r\n    if (locals.working) {\r\n      return\r\n    }\r\n\r\n    locals.error = false\r\n    locals.working = true\r\n\r\n    try {\r\n      for (const f of files) {\r\n        if ('file' in f) {\r\n          const asFile = await this.convertToFile(f)\r\n          await this.handleFile(asFile)\r\n        } else {\r\n          await this.handleFile(f)\r\n        }\r\n      }\r\n    } catch (e) {\r\n      console.error('Error handling files', e)\r\n\r\n      locals.error = true\r\n    }\r\n\r\n    locals.working = false\r\n  }\r\n\r\n  async convertToFile(fileEntry) {\r\n    return new Promise((resolve, reject) => {\r\n      fileEntry.file(resolve, reject)\r\n    })\r\n  }\r\n\r\n  onDragOver(e) {\r\n    var dt = e.dataTransfer\r\n    dt.dropEffect = 'copy'\r\n  }\r\n\r\n  async drop(event) {\r\n    locals.dragHover = false\r\n\r\n    const fileList = event.dataTransfer.files || event.dataTransfer.items\r\n\r\n    if (!fileList.length) {\r\n      return\r\n    }\r\n\r\n    const files = []\r\n    const directories = []\r\n\r\n    for (const fileListItem of fileList) {\r\n      if (fileListItem.webkitGetAsEntry) {\r\n        const fileSystemEntry = fileListItem.webkitGetAsEntry()\r\n        fileSystemEntry.type = fileListItem.type\r\n\r\n        if (fileSystemEntry.isFile) {\r\n          files.push(fileSystemEntry)\r\n        } else {\r\n          directories.push(fileSystemEntry)\r\n        }\r\n      } else {\r\n        files.push(fileListItem)\r\n      }\r\n    }\r\n\r\n    params.debug && console.log('drop', files, directories)\r\n\r\n    await this.handleFiles(files)\r\n  }\r\n\r\n  async handleFile(file) {\r\n    return new Promise((resolve, reject) => {\r\n      const fileReader = new FileReader()\r\n\r\n      fileReader.onload = (loadEvent) => {\r\n        const buffer = loadEvent.target.result\r\n\r\n        if (file.type === 'message/rfc822') {\r\n          const asString = new TextDecoder('utf-8').decode(new Uint8Array(buffer))\r\n          const parsed = packages.EmlParseJs.readEml(asString)\r\n          console.log('eml parsing', { parsed })\r\n          // console.log(parsed, asString)\r\n          // return\r\n\r\n          resolve(this.handleEml(file, parsed))\r\n        } else {\r\n          console.log(packages)\r\n          const msgReader = new packages.MSGReader(buffer)\r\n          const fileData = msgReader.getFileData()\r\n\r\n          if (fileData.error) {\r\n            return reject(fileData.error)\r\n          }\r\n\r\n          resolve(this.handleMsg(file, fileData, msgReader))\r\n        }\r\n      }\r\n\r\n      fileReader.readAsArrayBuffer(file)\r\n    })\r\n  }\r\n\r\n  async handleEml(file, parsedEml) {\r\n    const mail = await Aeppic.new(this.FORMS.Mail, document.id)\r\n    mail.data.name = parsedEml.subject || file.name || ''\r\n    Aeppic.save(mail)\r\n\r\n    // wait some time - but why?\r\n    await this.sleep(345)\r\n\r\n    mail.setFile('emlFile', file)\r\n\r\n    if (parsedEml.from) {\r\n      mail.data.von = this._emlReceiverAsMailto(parsedEml.from)\r\n    }\r\n\r\n    mail.data.text = parsedEml.text || ''\r\n\r\n    if (parsedEml.htmlheaders?.['Content-Transfer-Encoding'] === 'base64') {\r\n      try {\r\n        mail.data.html = decodeURIComponent(\r\n          atob(parsedEml.html)\r\n            .split('')\r\n            .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\r\n            .join(''),\r\n        )\r\n      } catch (error) {\r\n        console.error(\r\n          { error },\r\n          'Content-Transfer-Encoding says html content is base64 encoded. But got error while decoding. Fallback to take html content as it is',\r\n        )\r\n        mail.data.html = parsedEml.html || ''\r\n      }\r\n    } else {\r\n      mail.data.html = parsedEml.html || ''\r\n    }\r\n\r\n    console.log(parsedEml)\r\n\r\n    if (parsedEml.from) {\r\n      const senderContact = await this.lookupContact(parsedEml.from.email, parsedEml.from.name)\r\n\r\n      if (senderContact) {\r\n        mail.addReference('absender', senderContact)\r\n      }\r\n    }\r\n\r\n    if (parsedEml.to) {\r\n      const recipients = Array.isArray(parsedEml.to) ? parsedEml.to : [parsedEml.to]\r\n\r\n      for (const recipient of recipients) {\r\n        const contact = await this.lookupContact(recipient.email, recipient.name)\r\n\r\n        if (contact) {\r\n          mail.addReference('empfaenger', contact)\r\n        }\r\n\r\n        const asMailto = this._emlReceiverAsMailto(recipient)\r\n\r\n        if (asMailto) {\r\n          mail.data.an.push(asMailto)\r\n        }\r\n      }\r\n    }\r\n\r\n    if (parsedEml.headers) {\r\n      mail.data.headers = [JSON.stringify(parsedEml.headers)]\r\n      mail.data.messageId = parsedEml.headers['Message-ID'] || ''\r\n      mail.data.threadIndex = parsedEml.headers['Thread-Index'] || ''\r\n    }\r\n\r\n    if (parsedEml.headers?.['Delivery-date']) {\r\n      try {\r\n        mail.data.empfangen = parsedEml.headers['Delivery-date']\r\n          ? Aeppic.DateTime.fromRFC2822(parsedEml.headers['Delivery-date'].split(/\\n|\\r\\n/)[0]).toISO()\r\n          : ''\r\n      } catch (e) {\r\n        console.error(\r\n          'Could not parse headers.Develivery-Date: ',\r\n          parsedEml.headers['Delivery-date'],\r\n          e.toString(),\r\n          e.stack,\r\n          e,\r\n        )\r\n      }\r\n    } else if (parsedEml.headers?.['X-Received']) {\r\n      try {\r\n        // example: 0000:a11:1111:11a1:: with SMTP id somename.000.1638449117913 \\n Thu, 02 Dec 2021 04:45:17 -0800 (PST)\r\n        mail.data.empfangen = parsedEml.headers['X-Received']\r\n          ? Aeppic.DateTime.fromRFC2822(parsedEml.headers['X-Received'].split(/\\n|\\r\\n/)[1]).toISO()\r\n          : ''\r\n      } catch (e) {\r\n        console.error(\r\n          'Could not parse headers.Develivery-Date: ',\r\n          parsedEml.headers['X-Received'],\r\n          e.toString(),\r\n          e.stack,\r\n          e,\r\n        )\r\n      }\r\n    }\r\n\r\n    try {\r\n      // example: Thu, 2 Dec 2021 12:45:15 +0000 \\r\\n <FOOBAR@FOOBAR.foobar.foobar.example.com>\r\n      mail.data.gesendet = parsedEml.headers?.Date\r\n        ? Aeppic.DateTime.fromRFC2822(parsedEml.headers.Date.split(/\\n|\\r\\n/)[0]).toISO()\r\n        : ''\r\n    } catch (e) {\r\n      console.error('Could not parse headers.Date: ', parsedEml.headers.Date, e.toString(), e.stack, e)\r\n    }\r\n\r\n    if (parsedEml.attachments?.length) {\r\n      for (const attachment of parsedEml.attachments) {\r\n        const datei = await Aeppic.new(this.FORMS.Datei, mail.id)\r\n\r\n        datei.data.name =\r\n          attachment.name ||\r\n          attachment.contentType\r\n            ?.split(';')?.[1]\r\n            ?.replace('name=', '')\r\n            .replace(/[^A-Za-z0-9_.]/g, '') ||\r\n          ''\r\n\r\n        const mimeType = attachment.contentType.split(';')[0] || 'application/octet-stream'\r\n\r\n        const base64ToFile = (base64, fileName, type = 'application/octet-stream') => {\r\n          const decoded = atob(base64)\r\n          let length = decoded.length\r\n          const u8Array = new Uint8Array(length)\r\n          while (length--) {\r\n            u8Array[length] = decoded.charCodeAt(length)\r\n          }\r\n\r\n          return new File([u8Array], fileName, { type: mimeType })\r\n        }\r\n\r\n        const file = base64ToFile(attachment.data64, mimeType)\r\n        datei.setFile('datei', file)\r\n        Aeppic.save(datei)\r\n\r\n        mail.addReference('anhaenge', datei)\r\n      }\r\n    }\r\n\r\n    Aeppic.save(mail)\r\n\r\n    document.addReference(fieldName, mail)\r\n  }\r\n\r\n  _emlReceiverAsMailto(receiver) {\r\n    if (!receiver || (!receiver.email?.trim() && !receiver.name?.trim())) {\r\n      return ''\r\n    }\r\n\r\n    if (receiver.name.trim()) {\r\n      return `${receiver.name?.trim()} <${receiver.email.trim()}>`\r\n    }\r\n\r\n    return receiver.email?.trim()\r\n  }\r\n\r\n  async lookupContact(email, name) {\r\n    const matchingEmployee = await Aeppic.findOne(\r\n      `a:${document.a[0]} f.id:${this.FORMS.Mitarbeiter} data.email:${email}`,\r\n      { sort: { field: 'modified.at', descending: true } },\r\n    )\r\n\r\n    if (matchingEmployee) {\r\n      return matchingEmployee\r\n    }\r\n\r\n    if (params.contactFormId && this.paramContactEmailFields.length) {\r\n      const contactEmailFieldsQuery = this.paramContactEmailFields.map((f) => `data.${f}:${email}`).join(' OR ')\r\n      const matchingContact = await Aeppic.findOne(\r\n        `a:${document.a[0]} f.id:${params.contactFormId} (${contactEmailFieldsQuery})`,\r\n        { sort: { field: 'modified.at', descending: true } },\r\n      )\r\n\r\n      if (matchingContact) {\r\n        return matchingContact\r\n      }\r\n    }\r\n\r\n    const energyContactEmailFieldsQuery = this.energyContactEmailFields.map((f) => `data.${f}:${email}`).join(' OR ')\r\n    const matchingEnergyContact = await Aeppic.findOne(\r\n      `a:${document.a[0]} f.id:${this.FORMS.EnergieKontakt} (${energyContactEmailFieldsQuery})`,\r\n      { sort: { field: 'modified.at', descending: true } },\r\n    )\r\n\r\n    if (matchingEnergyContact) {\r\n      return matchingEnergyContact\r\n    }\r\n\r\n    const contactEmailFieldsQuery = this.contactEmailFields.map((f) => `data.${f}:${email}`).join(' OR ')\r\n    const matchingContact = await Aeppic.findOne(\r\n      `a:${document.a[0]} f.id:${this.FORMS.Kontakt} (${contactEmailFieldsQuery})`,\r\n      { sort: { field: 'modified.at', descending: true } },\r\n    )\r\n\r\n    if (matchingContact) {\r\n      return matchingContact\r\n    }\r\n\r\n    return null\r\n  }\r\n\r\n  sleep(ms = 500) {\r\n    return new Promise((resolve) => setTimeout(resolve, ms))\r\n  }\r\n\r\n  async handleMsg(file, data, reader) {\r\n    const mail = await Aeppic.new(this.FORMS.Mail, document.id)\r\n    mail.data.name = data.subject\r\n    Aeppic.save(mail)\r\n\r\n    await this.sleep(340)\r\n\r\n    mail.setFile('msgFile', file)\r\n\r\n    if (data.senderName.trim() !== '') {\r\n      mail.data.von = `${data.senderName} <${data.senderEmail}>`\r\n    } else {\r\n      mail.data.von = data.senderEmail\r\n    }\r\n\r\n    if (data.senderEmail) {\r\n      const senderContact = await this.lookupContact(data.senderEmail, data.senderName)\r\n\r\n      if (senderContact) {\r\n        mail.addReference('absender', senderContact)\r\n      }\r\n    }\r\n\r\n    mail.data.text = data.body || ''\r\n\r\n    if (data.recipients) {\r\n      for (const recipient of data.recipients) {\r\n        const contact = await this.lookupContact(recipient.email, recipient.name)\r\n\r\n        if (contact) {\r\n          mail.addReference('empfaenger', contact)\r\n        } else {\r\n          mail.data.an.push(`${recipient.email} <${recipient.name}>`)\r\n        }\r\n      }\r\n    }\r\n\r\n    if (data.headers) {\r\n      const headers = []\r\n\r\n      const LINES_STARTS_WHITESPACE_REGEX = /^\\s+/\r\n\r\n      for (const line of data.headers.split('\\n')) {\r\n        if (!line) {\r\n          continue\r\n        }\r\n\r\n        if (line.match(LINES_STARTS_WHITESPACE_REGEX)) {\r\n          let lastHeaderLine = headers[headers.length - 1]\r\n\r\n          if (!lastHeaderLine) {\r\n            console.warn('Header corrupted. Should not start with whitespace')\r\n            continue\r\n          }\r\n\r\n          lastHeaderLine += line\r\n          headers[headers.length] = lastHeaderLine\r\n        } else {\r\n          headers.push(line)\r\n        }\r\n      }\r\n\r\n      mail.data.headers = headers\r\n\r\n      mail.data.empfangen = this._readReceived(headers)\r\n      mail.data.gesendet = this._readDate(headers)\r\n      mail.data.messageId = this._readHeader(headers, 'Message-ID')\r\n      mail.data.threadIndex = this._readHeader(headers, 'Thread-Index')\r\n    }\r\n\r\n    const attachmentFiles = data.attachments.map((a, index) => {\r\n      const fileInfo = reader.getAttachment(index)\r\n      return new File([fileInfo.content], a.fileName, { type: a.mimeType ? a.mimeType : 'application/octet-stream' })\r\n    })\r\n\r\n    for (const attachmentFile of attachmentFiles) {\r\n      const datei = await Aeppic.new(this.FORMS.Datei, mail.id)\r\n      datei.data.name = attachmentFile.name\r\n      datei.setFile('datei', attachmentFile)\r\n      Aeppic.save(datei)\r\n\r\n      mail.addReference('anhaenge', datei)\r\n    }\r\n\r\n    Aeppic.save(mail)\r\n\r\n    document.addReference(fieldName, mail)\r\n  }\r\n\r\n  _readDate(headers) {\r\n    const dateString = this._readHeader(headers, 'Date')\r\n\r\n    if (!dateString) {\r\n      return ''\r\n    }\r\n\r\n    try {\r\n      const date = Aeppic.DateTime.fromRFC2822(dateString)\r\n      return date.toISO()\r\n    } catch (e) {\r\n      console.error(e)\r\n      return ''\r\n    }\r\n  }\r\n\r\n  _readHeader(headers, headerFieldName) {\r\n    const header = headers?.find((h) => h?.startsWith(`${headerFieldName}:`))\r\n\r\n    if (!header) {\r\n      return ''\r\n    }\r\n\r\n    const value = header.substr(headerFieldName.length + 1)\r\n    return value.trim()\r\n  }\r\n\r\n  _readReceived(headers) {\r\n    const topMostReceivedHeaderWithDate = headers.find((h) => h.startsWith('Received:') && h.indexOf(';') > 0)\r\n\r\n    if (!topMostReceivedHeaderWithDate) {\r\n      return ''\r\n    }\r\n\r\n    const dateString = topMostReceivedHeaderWithDate.split(';').pop().trim()\r\n    if (!dateString) {\r\n      return ''\r\n    }\r\n\r\n    try {\r\n      return Aeppic.DateTime.fromRFC2822(dateString).toISO()\r\n    } catch (e) {\r\n      console.error(e)\r\n    }\r\n\r\n    // fallback to JSDate parsing\r\n    try {\r\n      return Aeppic.DateTime.fromJSDate(new Date(dateString)).toISO()\r\n    } catch (e) {\r\n      console.error(e)\r\n      return ''\r\n    }\r\n  }\r\n\r\n  async mailSelected(event) {\r\n    const doc = await Aeppic.get(event)\r\n    locals.previewDocument = doc\r\n\r\n    await Aeppic.nextRenderPass()\r\n    $refs.preview.show()\r\n  }\r\n\r\n  get hasAnyDocumentLinked() {\r\n    if (!fieldInfo.cardinality) {\r\n      return !!document.data[fieldName].id\r\n    }\r\n\r\n    if (fieldInfo.cardinality) {\r\n      return !!document.data[fieldName].length\r\n    }\r\n  }\r\n\r\n  async resortMails() {\r\n    if (this.readonly) {\r\n      return\r\n    }\r\n\r\n    const sorted = Aeppic.sort(await Aeppic.getAll(getField()), { fields: ['data.gesendet','data.empfangen','created.at'], missingToEnd: true })\r\n    setField(sorted)\r\n  }\r\n\r\n  async showExpandedFilterUi() {\r\n    locals.filterUiLoading = true\r\n    locals.mails = []\r\n    locals.mailsYears = []\r\n    locals.mailsByDate = {}\r\n    locals.activeTimeFilter = ''\r\n\r\n    $refs.expandedFilterUi.show({ target: $refs.expandedFilterUiAncor })\r\n\r\n    locals.mails = Aeppic.sort(await Aeppic.getAll(getField()), { fields: ['data.gesendet','data.empfangen','created.at'], missingToEnd: true })\r\n    locals.filteredMails = locals.mails\r\n\r\n    const mailsByDate = new Map()\r\n    const mailsYears = []\r\n    let maxNumberMails = 0\r\n\r\n    for (const mail of locals.mails) {\r\n      const dateString = mail.data.gesendet || mail.data.empfangen || mail.created.at\r\n      const date = Aeppic.DateTime.fromISO(dateString)\r\n\r\n      const year = date.toFormat('yyyy')\r\n      const month = date.toFormat('yyyy-MM')\r\n\r\n      if (!mailsByDate.has(year)) {\r\n        mailsByDate.set(year, [])\r\n        mailsYears.push(year)\r\n      }\r\n      mailsByDate.get(year).push(mail)\r\n\r\n      if (!mailsByDate.has(month)) {\r\n        mailsByDate.set(month, [])\r\n      }\r\n      mailsByDate.get(month).push(mail)\r\n    }\r\n\r\n    for (const [_, mails] of mailsByDate) {\r\n      if (mails.length > maxNumberMails) {\r\n        maxNumberMails = mails.length\r\n      }\r\n    }\r\n\r\n    locals.maxNumberMails = maxNumberMails\r\n    locals.percentilePerMail = 100 / locals.maxNumberMails\r\n    locals.mailsYears = mailsYears\r\n    locals.mailsByDate = Object.fromEntries(mailsByDate)\r\n    locals.filterUiLoading = false\r\n  }\r\n\r\n  setMailsTimeFilter(yearOrYearMonth) {\r\n    if (locals.activeTimeFilter === yearOrYearMonth) {\r\n      locals.activeTimeFilter = ''\r\n      locals.filteredMails = locals.mails\r\n      return\r\n    }\r\n\r\n    locals.activeTimeFilter = yearOrYearMonth\r\n\r\n    const interval = {\r\n      start: '',\r\n      end: '',\r\n      fieldNames: ['data.gesendet', 'data.empfangen', 'created.at']\r\n    }\r\n\r\n    const isYear = yearOrYearMonth.includes('-') === false\r\n    const datetime = Aeppic.DateTime.fromISO(yearOrYearMonth)\r\n    if (isYear) {\r\n      interval.start = datetime.startOf('year').toISO()\r\n      interval.end = datetime.endOf('year').toISO()\r\n    } else {\r\n      interval.start = datetime.startOf('month').toISO()\r\n      interval.end = datetime.endOf('month').toISO()\r\n    }\r\n\r\n    locals.filteredMails = packages.SearchHelpers.filterDocumentsToInterval(Aeppic, interval, locals.mails)\r\n    console.log(locals.mails, locals.filteredMails)\r\n  }\r\n}\r\n","css":"._CSS-file__input-container {\r\n  padding: 8px;\r\n  display: flex;\r\n  align-items: center;\r\n}\r\n\r\n._CSS-file__input-link {\r\n  cursor: pointer;\r\n  color: var(--ae-foreground-l1);\r\n  font-weight: bold;\r\n}\r\n\r\n._CSS-file__input-link:hover,_CSS-file__input-link:focus {\r\n  text-decoration: underline;\r\n}\r\n\r\n._CSS-file__description {\r\n  overflow: hidden;\r\n  text-overflow: ellipsis;\r\n  flex: 1;\r\n}\r\n\r\n._CSS-file__drop-area {\r\n  border: var(--ae-border-width-2) dashed var(--ae-border-color);\r\n  border-radius: var(--ae-border-radius);\r\n  padding: 2em 1em;\r\n  color: var(--ae-foreground-l2);\r\n  background: var(--ae-ui-background);\r\n}\r\n._CSS-file__drop-area._CSS-file__drop-area--readonly {\r\n  border: var(--ae-border-width-1) solid var(--ae-border-color);\r\n}\r\n\r\n._CSS-file__drop-area--hover {\r\n  animation: _CSS-pulse 2s infinite;\r\n  background-color: var(--ae-primary-100);\r\n}\r\n\r\n@keyframes _CSS-pulse {\r\n  0% {\r\n    background-color: var(--ae-primary-100);\r\n  }\r\n  50% {\r\n    background-color: var(--ae-primary-200);\r\n  }\r\n  100% {\r\n    background-color: var(--ae-primary-100);\r\n  }\r\n}\r\n\r\n._CSS-file--no-thumbnail {\r\n  align-self:center;\r\n  height: 1.5rem;\r\n  opacity: 0.5;\r\n  padding-right: 8px;\r\n  fill: var(--ae-foreground-l2);\r\n}\r\n\r\n._CSS-preview-header {\r\n  display: flex;\r\n  justify-content: space-between;\r\n}\r\n\r\n._CSS-preview-header__entry {\r\n  display: flex;\r\n  align-items: center;\r\n  flex: 1;\r\n}\r\n\r\n._CSS-file-thumbnail {\r\n  height: 4rem;\r\n  width: 4rem;\r\n  background-size: cover;\r\n  background-repeat: no-repeat;\r\n  background-position-x: center;\r\n  background-color: #e2e2e2;\r\n  cursor: pointer;\r\n}\r\n\r\n._CSS-dialog__header {\r\n  display: flex;\r\n  justify-content: space-between;\r\n  align-items: center;\r\n}","description":"","device":"ALL","label":"","limitTypes":false,"name":"mails","namespace":"rd","packages":[{"id":"6116c361-ed38-4e7c-81d3-a8120cb2d158","text":"Msg Reader","v":"8ef24dc38517c324"},{"id":"cc3737e4-1695-44ef-bb6a-6e4ab6eca60e","text":"eml-parse-js","v":"cee430379ce75925"},{"id":"9fbc0645-b97a-4b98-8520-02670f21ac0b","text":"Aeppic Search Helpers","v":"f13c9400fb9fd0a3"}],"parameters":[],"standalone":"FIELD","template":"<div class=\"border rounded-md border-dashed p-2 bg-white flex flex-col gap-2\">\r\n  <section v-if=\"!controller.readonly\">\r\n    <div v-if=\"params.readonly\" class=\"_CSS-file\" ref=\"container\">\r\n      <div\r\n        class=\"_CSS-file__drop-area _CSS-file__drop-area--readonly _CSS-file__input-container\"\r\n        @drop.prevent.stop\r\n        @dragenter.prevent.stop\r\n        @dragleave.prevent.stop\r\n      >\r\n        <svg class=\"_CSS-file--no-thumbnail\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 384 512\">\r\n          <path\r\n            d=\"M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm160-14.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z\"\r\n          />\r\n        </svg>\r\n        Keine Datei vorhanden\r\n      </div>\r\n    </div>\r\n    <div v-else ref=\"container\">\r\n      <div\r\n        @dragenter.prevent.stop=\"locals.dragHover = true\"\r\n        @dragleave.prevent.stop=\"locals.dragHover = false\"\r\n        @dragover.prevent.stop=\"controller.onDragOver($event)\"\r\n        @drop.prevent.stop=\"controller.drop($event)\"\r\n        :class=\"{ '_CSS-file__drop-area--hover': locals.dragHover }\"\r\n      >\r\n        <template v-if=\"locals.error\">\r\n          <ae-icon class=\"far fa-exclamation-triangle\"></ae-icon>\r\n          <span style=\"margin-left: var(--ae-space-1)\"> Fehler bei der Verarbeitung </span>\r\n        </template>\r\n        <template v-if=\"locals.working\">\r\n          <ae-icon class=\"far fa-circle-notch fa-spin\"></ae-icon>\r\n          <span style=\"margin-left: var(--ae-space-1)\"> Mail wird verarbeitet ... </span>\r\n        </template>\r\n        <div v-else class=\"flex gap-4 items-center flex-wrap\">\r\n          <ae-layout\r\n            layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\r\n            @selected=\"$refs.input.click()\"\r\n            ref=\"button\"\r\n            param-text=\"Mails auswählen\"\r\n            param-classes=\"border border-neutral-200\"\r\n          ></ae-layout>\r\n\r\n          <div class=\"flex gap-2 items-center text-neutral-600\">\r\n            <ae-icon class=\"far fa-envelope\"></ae-icon>\r\n            <span>Mails hier ablegen </span>\r\n          </div>\r\n\r\n          <div\r\n            class=\"text-neutral-600\"\r\n            ref=\"info\"\r\n            @click=\"$refs.popup.show({ target: $refs.info })\"\r\n            @mouseover=\"$refs.popup.show({ target: $refs.info })\"\r\n            aria-title=\"Unterstütze Formate: (Drag&Drop von) Mails aus Outlook Postfach, *.msg- oder *.eml-Dateien\"\r\n          >\r\n            <ae-icon class=\"far fa-info-circle\"></ae-icon>\r\n          </div>\r\n\r\n          <ae-popup ref=\"popup\">\r\n            <div class=\"whitespace-pre-line p-1\">Unterstütze Formate: (Drag&Drop von) Mails aus Outlook Postfach. *.msg- oder *.eml-Dateien</div>\r\n          </ae-popup>\r\n\r\n          <div class=\"flex-1\"></div>\r\n\r\n          <ae-layout\r\n            layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\r\n            param-icon=\"far fa-magnifying-glass\"\r\n            param-title=\"E-Mails durchsuchen\"\r\n            param-border=\"border border-neutral-200\"\r\n            @selected=\"controller.showExpandedFilterUi()\"\r\n          ></ae-layout>\r\n\r\n          <ae-layout layout-id=\"8bc795f2-7ac8-4b95-9019-0b27b178a36e\"\r\n          :params=\"{ options: locals.dropdownOptions, buttonParams: { icon: 'far fa-ellipsis-v', border: 'border border-neutral-200'} }\"\r\n          @selected=\"controller.resortMails()\"\r\n          ></ae-layout>\r\n        </div>\r\n      </div>\r\n    </div>\r\n    <input @change=\"controller.onFilesSelected($event)\" multiple ref=\"input\" type=\"file\" style=\"display: none\" />\r\n    <!-- <img ref=\"hiddenImage\" :src=\"field.dataUrl\" @load=\"controller.imageLoaded()\" style=\"display: none;\"> -->\r\n  </section>\r\n\r\n  <div ref=\"expandedFilterUiAncor\"></div>\r\n\r\n  <!-- @xselected=\"controller.mailSelected($event)\" -->\r\n  <ae-control\r\n    v-if=\"controller.hasAnyDocumentLinked\"\r\n    name=\"rd:ref\"\r\n    :document=\"document\"\r\n    :field-name=\"fieldName\"\r\n    :param-no-add=\"true\"\r\n    @selected=\"Aeppic.drillInto($event, arguments[1])\"\r\n    :param-readonly=\"controller.readonly\"\r\n    :param-handle-remove=\"true\"\r\n    :param-confirm-remove=\"true\"\r\n    :param-list=\"true\"\r\n    :param-sort=\"locals.sort\"\r\n    :param-disable-sort=\"true\"\r\n    param-design=\"rd:ref-card\"\r\n    :param-no-border=\"true\"\r\n    :param-collapse-after=\"20\"\r\n  ></ae-control>\r\n\r\n  <ae-dialog ref=\"expandedFilterUi\" placement=\"bottom\" :dialog-size=\"{height: '90vh', width: '80vw'}\">\r\n    <template #header=\"scope\">\r\n      <div class=\"flex justify-between\">\r\n        <div>E-Mail Filter</div>\r\n        <ae-layout\r\n          layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\r\n          param-icon=\"fa fa-times\"\r\n          param-text=\"\"\r\n          @selected=\"scope.close()\"\r\n        ></ae-layout>\r\n      </div>\r\n    </template>\r\n    <template #body>\r\n      <div class=\"flex flex-col size-full\">\r\n        <div class=\"flex overflow-x-auto min-h-[130px] select-none\">\r\n          <div v-for=\"year of locals.mailsYears\" class=\"w-[480px] flex flex-col\">\r\n            <div class=\"h-[100px] px-1 flex\">\r\n              <div\r\n                v-for=\"yearMonth of Object.keys(locals.mailsByDate).filter(key => key !== year && key.startsWith(year))\"\r\n                class=\"h-full flex items-end cursor-pointer\"\r\n                @click.stop=\"controller.setMailsTimeFilter(yearMonth)\"\r\n              >\r\n                <div\r\n                  class=\"w-[40px] relative\"\r\n                  :class=\"[locals.activeTimeFilter === yearMonth ? 'bg-amber-500' : ' bg-blue-600 hover:bg-blue-400',\r\n              'h-[' + 80 * ((locals.percentilePerMail * locals.mailsByDate[yearMonth].length)/100) + 'px]']\"\r\n                >\r\n                  <span class=\"absolute top-[-20px]\"> {{Aeppic.DateTime.fromISO(yearMonth).toFormat('LLL')}} </span>\r\n                </div>\r\n              </div>\r\n            </div>\r\n            <div\r\n              class=\"mx-1 flex-1\"\r\n              :class=\"[ locals.activeTimeFilter === year ? 'bg-amber-500' :  'bg-slate-200 hover:bg-blue-400']\"\r\n              @click.stop=\"controller.setMailsTimeFilter(year)\"\r\n            >\r\n              {{ year }}\r\n            </div>\r\n          </div>\r\n        </div>\r\n        <ae-design\r\n          class=\"p-2\"\r\n          name=\"rd:toolbar\"\r\n          :document=\"document\"\r\n          @view=\"locals.view = $event\"\r\n          @filter=\"locals.filter = $event\"\r\n          :params=\"{ ...params, \r\n            listFormId: '59b7188e-1296-4466-a9c2-d6255496e7d6',\r\n            filterFields: 'NOOP',\r\n            disableNew: true,\r\n            disableSort: true,\r\n          }\"\r\n        ></ae-design>\r\n        <!-- @sort=\"locals.sort = $event\" -->\r\n\r\n        <ae-list\r\n          class=\"overflow-y-scroll\"\r\n          list-id=\"922e8251-6e60-45a4-9df6-3d434363ac01\"\r\n          :documents=\"locals.filteredMails\"\r\n          :filter=\"locals.filter\"\r\n          :sort=\"locals.sort\"\r\n          :search-options=\"{ searchAllFields: true }\"\r\n          :params=\"{ design: 'rd:ref-card', designParams: { readonly: true }}\"\r\n        ></ae-list>\r\n        <!-- @selected=\"controller.mailSelected($event)\" -->\r\n      </div>\r\n    </template>\r\n  </ae-dialog>\r\n</div>\r\n\r\n<ae-dialog ref=\"preview\">\r\n  <template v-slot:header=\"scope\">\r\n    <div class=\"_CSS-dialog__header\">\r\n      <ae-design name=\"title\" :document=\"locals.previewDocument\"></ae-design>\r\n      <button class=\"ae-button\" @click.stop=\"scope.close()\">\r\n        <ae-icon class=\"far fa-times\"></ae-icon>\r\n      </button>\r\n    </div>\r\n  </template>\r\n  <ae-design slot=\"body\" name=\"page\" :document=\"locals.previewDocument\" :param-readonly=\"true\"></ae-design>\r\n</ae-dialog>\r\n","types":["ref"]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"e4d4b6b2-5fac-42b9-9ba4-660c1a7e847e","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-03-12T11:14:58+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"f27ff8c4be3abaf5"},"stamps":[],"t":1773314098264,"v":"cad5d4ec6d88ed93"},{"id":"5d6a0bdc-e4b5-4d4d-9be3-dcdc8d002164","v":"e963d568d34507ee","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"map","template":"<leaflet\n  v-model=\"field\"\n  style=\"flex:1\"\n  :style=\"{ 'height': params.height ? params.height + 'px' : '200px' }\"\n  :readonly=\"readonly\">\n</leaflet>","css":"._CSS {\n  height: 200px;\n  width: 100%;\n  display: flex;\n  min-height: 200px;\n} \n","controller":"","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"581f0308-1f9a-4319-99e1-c65244206583","v":"a12730e85f049b23","text":"Leaflet"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-10-10T16:02:55+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","from":"","client":"server"},"modified":{"at":"2020-09-04T13:17:05+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936844,"ct":1721209936844,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"b5ae5d7e66a7aafb","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"59f55405-1ba4-4502-8cb7-65f7817fa8b6","v":"dddf8388315f8031","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"mark-spot","template":"<div>\n  <button v-if=\"!readonly && !locals.imageSrc\" @click=\"$refs.input.click()\" class=\"_CSS--button _CSS--button--camera\">\n    <ae-icon class=\"fa fa-camera-retro\"></ae-icon> Take Picture\n  </button>\n  <div style=\"position:relative\">\n    <div v-if=\"!readonly\" style=\"position:absolute; left:0; right:0; z-index:1;\" class=\"_CSS--button-bar\">\n      <button v-show=\"locals.imageSrc\" @click=\"controller.showFullScreenDialog(true)\" class=\"_CSS--button\"> \n        <div v-if=\"locals.markerPos\">\n          <ae-icon class=\"fa fa-thumbtack\"></ae-icon> Edit Marker</div>\n        <div v-else>\n          <ae-icon class=\"fa fa-thumbtack\"></ae-icon> Set Marker\n        </div>\n      </button>\n      <button v-show=\"locals.imageSrc\" @click=\"controller.deleteImage()\" class=\"_CSS--button\">\n        <ae-icon class=\"fa fa-trash-alt\"></ae-icon> Delete Image\n      </button>\n    </div> \n  </div>\n  <div @click=\"controller.showFullScreenDialog(false)\" style=\"overflow:hidden\" ref=\"letterbox\" class=\"_CSS--letterbox\">\n    <div v-if=\"locals.imageSrc\" style=\"display:inline-block; position:relative; width:100%\">\n      <img :src=\"locals.imageSrc\" style=\"display:block; width:100%\" ref=\"imageInForm\" @load=\"controller.onImageLoaded($refs.canvasInForm, $refs.imageInForm)\">\n      <canvas ref=\"canvasInForm\" style=\"position:absolute; top:0; left:0; bottom:0 right:0; width:100%; height:100%\"></canvas>\n    </div>\n  </div> \n</div>\n\n<input @change=\"controller.onFilesSelected($event.target.files)\" ref=\"input\" type=\"file\" accept=\"image/*\" capture=\"camera\" style=\"display: none;\">\n<canvas ref=\"orientationCanvas\" style=\"display:none\"></canvas>\n\n<ae-dialog :full-screen=\"true\" ref=\"markerDialog\">\n  <template v-slot:header=\"scope\">\n    <div class=\"_CSS-dialog__header\">\n      <ae-design name=\"title\" :document-id=\"document.p\"></ae-design>\n      <button class=\"bp-button bp-button-icon\" @click=\"scope.close()\">\n        <ae-icon class=\"fa fa-times\"></ae-icon>\n      </button>\n    </div>\n  </template>\n  <div slot=\"body\" @click=\"!locals.editMarker && $refs.markerDialog.close()\" style=\"position:relative; display:flex; flex-direction:column; flex:1; justify-content:space-around;\" class=\"_CSS--fullscreen\">\n    <div v-if=\"!readonly\" style=\"position:absolute; top:0; left:0; right:0; z-index:1;\" class=\"_CSS--button-bar\">\n      <button v-show=\"!locals.editMarker\" @click=\"locals.editMarker = true\" class=\"_CSS--button\">\n        <div v-if=\"locals.markerPos\">\n          <ae-icon class=\"fa fa-thumbtack\"></ae-icon> Edit Marker</div>\n        <div v-else>\n          <ae-icon class=\"fa fa-thumbtack\"></ae-icon> Set Marker\n        </div>\n      </button>\n      <button v-show=\"locals.editMarker && locals.markerPos\" @click=\"controller.deleteMarker()\" class=\"_CSS--button\">\n        <ae-icon class=\"fa fa-trash-alt\"></ae-icon> Delete Marker\n      </button>\n      <button @click=\"controller.deleteImage()\" class=\"_CSS--button\">\n         <ae-icon class=\"fa fa-trash-alt\"></ae-icon> Delete Image\n      </button>\n      <button v-show=\"locals.editMarker\" @click=\"$refs.markerDialog.close()\" style=\"float:right\" class=\"_CSS--button _CSS--button--ok\">Ok</button>\n    </div>\n    <div @click=\"locals.editMarker && controller.setMarkerViaDialog($event)\" style=\"display:inline-block; position:relative; width:100%\">\n      <img :src=\"locals.imageSrc\" style=\"display:block; width:100%\" ref=\"imageInDialog\" @load=\"controller.onImageLoaded($refs.canvasInDialog, $refs.imageInDialog)\">\n      <canvas ref=\"canvasInDialog\" style=\"position:absolute; top:0; left:0; bottom:0 right:0; width:100%; height:100%\"></canvas>\n    </div>\n  </div>\n</ae-dialog>","css":"._CSS--letterbox{\n  position: relative;\n  max-height: 250px;\n}\n\n._CSS--fullscreen{\n  background: black;\n}\n\n._CSS--button-bar{\n  background: rgba(255, 255, 255, 0.7)\n}\n\n._CSS--button{\n  border: 0;\n  padding: 0 6px;\n  margin: 6px 3px;\n  min-height: 36px;\n  min-width: 70px;\n  background: transparent;\n  color: #333;\n  text-transform: uppercase;\n  font-weight: 500;\n  font-size: 14px;\n  cursor: pointer;\n  transition: all 0.1s ease-in;\n}\n\n._CSS--button:hover{\n  background: rgba(0, 0, 0, 0.1)\n}\n\n._CSS--button--camera,\n._CSS--button--ok{\n  background: var(--ae-primary-l1);\n  color: var(--ae-theme-text-primary-on-primary);\n}\n\n._CSS--button--camera:hover,\n._CSS--button--ok:hover{\n  background: var(--ae-primary-d1);\n  color: var(--ae-theme-text-primary-on-primary);\n}\n\n\n._CSS-dialog__header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  overflow: hidden;\n}","controller":"class Controller {\n  init(){ \n    this.observers = [] \n\n    locals.imageSrc = Aeppic.Content.getSrc(document, fieldName)\n    //locals.imageSrc = Aeppic.Content.getThumbnailSrc(document, fieldName)\n    locals.markerPos = null\n    locals.editMarker = false\n\n    if(params.markerX && params.markerX\n     && typeof document.data[params.markerX] !== 'null'\n     && typeof document.data[params.markerX] !== 'undefined'\n     && typeof document.data[params.markerY] !== 'null'\n     && typeof document.data[params.markerX] !== 'undefined'){\n      locals.markerPos = {\n        x:document.data[params.markerX],\n        y:document.data[params.markerY]\n      }\n    }\n  } \n\n  setupIntersectionObserver(el, cb){\n    var options = {}\n    //  {\n    //   root: null,\n    //   rootMargin: '0px',\n    //   threshold: 1.0\n    // }\n    \n\n    var observer = new IntersectionObserver(cb, options)\n    observer.observe(el)\n    return observer\n  }\n\n  async onFilesSelected(fileList) {\n    if (!fileList.length) {\n      return\n    }\n\n    const files = []\n    if (fileList instanceof DataTransferItemList) {\n      files.push(fileList[0].getAsFile())\n    } else {\n      files.push(fileList.item(0))\n    }\n\n    const file = files[0]\n    const field = Object.assign({}, fieldInfo.defaultValue)\n    field.name = file.name\n    field.type = file.type\n    field.size = file.size\n    const dateString = Aeppic.DateTime.fromMillis(file.lastModified).toJSON()\n    field.fileInfo.created = dateString\n    field.fileInfo.modified = dateString\n\n    if (params.fileNameToField) {\n      // TODO: once a writeable reference to field is available use that\n      document.data[params.fileNameToField.replace(/\\[|\\]/g,'')] = file.name\n    }\n\n    const orientedImageBase64 = await this.resolveImageOrientation(file)\n  \n    setField(field)\n\n    document.setFile(fieldName, orientedImageBase64)\n    locals.imageSrc = orientedImageBase64\n    // locals.imageSrc = Aeppic.Content.getSrc(document, fieldName)\n    locals.markerPos = null\n    this.updateMarkerFields()\n\n    Aeppic.commit(document)\n    this.showFullScreenDialog(true) \n  }\n \n  async onImageLoaded(canvas, image){  \n    if(!this.observers[image]){\n\n      const cb = ()=>{\n        console.log('observer fired')\n        this.alignCanvasSizeWithImage(canvas, image)\n\n        if(locals.markerPos){\n          const pos = this.translateImageToCanvasCoordinates(canvas, image, locals.markerPos.x, locals.markerPos.y)\n          this.drawMarker(canvas, pos.x, pos.y)\n          this.scrollMarkerIntoView()\n        }\n      }\n      const observer = this.setupIntersectionObserver(image, cb)\n\n      this.observers.push(observer)\n      console.log('pushed new observer.', this.observers.length)\n    }\n\n    this.alignCanvasSizeWithImage(canvas, image)\n    if(locals.markerPos){\n      const pos = this.translateImageToCanvasCoordinates(canvas, image, locals.markerPos.x, locals.markerPos.y)\n      this.drawMarker(canvas, pos.x, pos.y)\n      this.scrollMarkerIntoView()\n    }\n  }\n\n  async showFullScreenDialog(editMarker){\n    locals.editMarker = editMarker\n    $refs.markerDialog.show();\n\n    await Aeppic.nextRenderPass()\n    this.alignCanvasSizeWithImage($refs.canvasInDialog, $refs.imageInDialog)\n\n    if(locals.markerPos){\n      const pos = this.translateImageToCanvasCoordinates($refs.canvasInDialog, $refs.imageInDialog, locals.markerPos.x, locals.markerPos.y)\n      this.drawMarker($refs.canvasInDialog, pos.x, pos.y)\n    }\n  }\n\n  deleteImage() {\n    setField(fieldInfo.defaultValue)\n    $refs.input.value = ''\n    locals.imageSrc = ''\n\n    locals.markerPos = null\n    this.updateMarkerFields()\n\n    $refs.markerDialog.close()\n  }\n\n  deleteMarker(){\n    locals.markerPos = null\n    this.updateMarkerFields()\n\n    this.clearCanvas($refs.canvasInForm)\n    this.clearCanvas($refs.canvasInDialog)\n  }\n\n  setMarkerViaDialog(event){\n    \n    //translate incase canvas has been resized since initial setup\n    const x = event.offsetX * ($refs.canvasInDialog.width/$refs.canvasInDialog.clientWidth)\n    const y = event.offsetY * ($refs.canvasInDialog.height/$refs.canvasInDialog.clientHeight)\n\n    this.drawMarker($refs.canvasInDialog, x,y) \n    locals.markerPos = this.translateCanvasToImageCoordinates($refs.canvasInDialog, $refs.imageInDialog, x, y);\n    \n    const pos = this.translateImageToCanvasCoordinates($refs.canvasInForm, $refs.imageInForm, locals.markerPos.x, locals.markerPos.y)\n    this.drawMarker($refs.canvasInForm, pos.x, pos.y)\n    this.scrollMarkerIntoView()\n\n    this.updateMarkerFields()\n\n    Aeppic.commit(document)\n  }\n\n  alignCanvasSizeWithImage(canvas, image){\n    canvas.width = image.clientWidth\n    canvas.height = image.clientHeight\n  }\n\n  updateMarkerFields(){\n    if(params.markerX && document.data.hasOwnProperty(params.markerX)){\n      document.data[params.markerX] = locals.markerPos ? locals.markerPos.x : null\n    }\n    if(params.markerY && document.data.hasOwnProperty(params.markerY)){\n      document.data[params.markerY] = locals.markerPos ? locals.markerPos.y : null\n    }\n  }\n\n  scrollMarkerIntoView(){\n    let scrollTop = 0;\n    \n    if( locals.markerPos){\n      let pos = this.translateImageToCanvasCoordinates($refs.canvasInForm, $refs.imageInForm, locals.markerPos.x, locals.markerPos.y)\n      scrollTop = pos.y - $refs.letterbox.clientHeight/2\n    }\n    \n    $refs.letterbox.scrollTop = scrollTop\n  }\n\n  clearCanvas(canvas){\n    const ctx = canvas.getContext('2d')\n    ctx.clearRect(0, 0, canvas.width, canvas.height);\n  }\n\n  drawMarker(canvas, x, y){\n    const radius = 20\n    const ctx = canvas.getContext('2d')\n\n    this.clearCanvas(canvas)\n\n    ctx.beginPath();\n    ctx.arc(x, y, radius, 0, 2 * Math.PI);\n    ctx.lineWidth = 2;\n    ctx.strokeStyle = '#FFFFFF';\n    ctx.stroke();\n\n    ctx.fillStyle = 'rgba(128,128,128,0.4)';\n    ctx.fill();\n  }\n\n  translateCanvasToImageCoordinates(canvas, image, canvasX,canvasY){\n    if (!canvas || !image) {\n      return {x: 0, y: 0}\n    }\n\n    const imageX = Math.round(canvasX * (image.naturalWidth / canvas.width))\n    const imageY = Math.round(canvasY * (image.naturalHeight / canvas.height))\n\n    return {x:imageX, y:imageY}  \n  }\n\n  translateImageToCanvasCoordinates(canvas, image, imageX, imageY){\n    if (!canvas || !image) {\n      return {x: 0, y: 0}\n    }\n\n    console.log(`canvas width/height ${canvas.width}/${canvas.height}`)\n\n    const canvasX = Math.round(imageX * (canvas.width / image.naturalWidth))\n    const canvasY = Math.round(imageY * (canvas.height / image.naturalHeight))\n\n    return {x:canvasX, y:canvasY} \n  }\n\n  resolveImageOrientation(file){\n    return new Promise(function(resolve, reject) {\n      getImageOrientation(file, (orientation) => {\n        resetImageOrientation(file, orientation, function(resetBase64Image) {\n          resolve(resetBase64Image);\n        })\n      })\n    });\n\n    function getImageOrientation(file, callback) {\n      var reader = new FileReader();\n      reader.onload = function(e) {\n\n        var view = new DataView(e.target.result);\n        if (view.getUint16(0, false) != 0xFFD8)\n        {\n          return callback(-2);\n        }\n        var length = view.byteLength, offset = 2;\n        while (offset < length) \n        {\n          var marker = view.getUint16(offset, false);\n          offset += 2;\n          if (marker == 0xFFE1) \n          {\n            if (view.getUint32(offset += 2, false) != 0x45786966) \n            {\n              return callback(-1);\n            }\n\n            var little = view.getUint16(offset += 6, false) == 0x4949;\n            offset += view.getUint32(offset + 4, little);\n            var tags = view.getUint16(offset, little);\n            offset += 2;\n            for (var i = 0; i < tags; i++)\n            {\n              if (view.getUint16(offset + (i * 12), little) == 0x0112)\n              {\n                return callback(view.getUint16(offset + (i * 12) + 8, little));\n              }\n            }\n          }\n          else if ((marker & 0xFF00) != 0xFF00)\n          {\n            break;\n          }\n          else\n          { \n            offset += view.getUint16(offset, false);\n          }\n        }\n        return callback(-1);\n      };\n      reader.readAsArrayBuffer(file);\n    }\n\n\n    function resetImageOrientation(file, srcOrientation, callback) {\n      \n      var reader = new FileReader();\n      var img = new Image();\n\n      reader.onload = function(e) {\n        img.src = e.target.result\n      }\n          \n      img.onload = function() {\n        var width = img.width,\n            height = img.height,\n            canvas = $refs.orientationCanvas,\n            ctx = canvas.getContext(\"2d\");\n        \n        // set proper canvas dimensions before transform & export\n        if (4 < srcOrientation && srcOrientation < 9) {\n          canvas.width = height;\n          canvas.height = width;\n        } else {\n          canvas.width = width;\n          canvas.height = height;\n        }\n      \n        // transform context before drawing image\n        switch (srcOrientation) {\n          case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;\n          case 3: ctx.transform(-1, 0, 0, -1, width, height ); break;\n          case 4: ctx.transform(1, 0, 0, -1, 0, height ); break;\n          case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;\n          case 6: ctx.transform(0, 1, -1, 0, height , 0); break;\n          case 7: ctx.transform(0, -1, -1, 0, height , width); break;\n          case 8: ctx.transform(0, -1, 1, 0, 0, width); break;\n          default: break;\n        }\n\n        // draw image\n        ctx.drawImage(img, 0, 0);\n\n        // export base64\n        callback(canvas.toDataURL());\n      };\n\n      reader.readAsDataURL(file);\n    }\n  }\n\n  destroy() {\n    for (const observer of this.observers) {\n      observer.disconnect()\n    }\n\n    this.observers = [] \n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-04-05T14:30:31+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936844,"ct":1721209936844,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"f443d6b1ce59ec9d","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"60695e2a-9ea4-4651-8745-7399f6ab47d3","v":"962b8eff9904772a","cloneOf":{"id":"c06bbf61-5813-4ec3-9048-e288037d3c97","v":"0ae26e8b20dc66e5","text":"markdown"},"originalCloneOf":{"id":"c06bbf61-5813-4ec3-9048-e288037d3c97","v":"0ae26e8b20dc66e5","text":"markdown"},"previous":{"v":"initial"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"markdown","template":"<ae-expandable>\n  <monaco-editor @paste.native=\"controller.paste($event)\" @drop.native.stop.prevent=\"controller.drop($event)\" ref=\"editor\" style=\"min-height:40em\" v-model=\"field\" :readonly=\"readonly\" theme=\"vs-light\" language=\"markdown\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>\n\n<ae-layout  v-if=\"locals.canEdit\"\n            layout-id=\"f5a5c2a0-bf9c-4b44-b05b-8e545156df10\"\n            :param-document=\"document\"\n            :param-show-ui=\"true\"\n            param-file-form-id=\"6d3f6460-c6d2-4df1-8c05-e8bb66c5b8d2\"\n            param-file-form-field=\"datei\"\n            param-file-name-to-field=\"name\"\n            :param-save-new-documents=\"true\"\n            :param-flatten=\"true\"\n            ref=\"dropHandler\"\n            @added=\"controller.added($event)\"\n></ae-layout>\n","css":"","controller":"class Controller {\n  async init() {\n    // this._currentlyUploadingDocument = new Set()\n\n    // Aeppic.Uploads.on('uploaded', (d) => {\n    //   if (this._currentlyUploadingDocument.has(d.id)) {\n    //     this._currentlyUploadingDocument.delete(d.id)\n    //     this._insertDocumentInEditor(d.id)\n    //   }\n    // })\n    const canEdit = await Aeppic.canEdit(document)\n    locals.canEdit = Aeppic.Account && Aeppic.Account.data.isGroupAccount === false && canEdit === true\n  }\n\n  async getHoverContent(word) {\n    const infos = await Aeppic.find(`id:${word}`)\n    if (!infos || !infos.length) {\n      return []\n    }\n\n    const doc = infos[0]\n\n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n\n  async paste(event) {\n    $refs.dropHandler.$emit('paste', event)\n  }\n\n  async drop(event) {\n    $refs.dropHandler.$emit('drop', event)\n  }\n\n  async input(event) {\n    $refs.dropHandler.$emit('input', event)\n  }\n\n  async added(added) {\n    console.log(added)\n    if (!$refs.editor) {\n      return\n    }\n    \n    if (!added.documentIds) {\n      return\n    }\n\n    for (const id of added.documentIds) {\n      this._insertDocumentInEditor(id)\n    //   Aeppic.watch(id, (pastedDocument) => {\n\n    //     if (!pastedDocument || Aeppic.Uploads.isDocumentUploading(pastedDocument)) {\n    //       return\n    //     }\n\n        // this._insertDocumentInEditor(pastedDocument.id)\n      // })\n    }\n  }\n\n  async _insertDocumentInEditor(id) {\n    console.log('_insertDocumentInEditor' ,id)\n    \n    const doc = await Aeppic.get(id)\n\n    console.log(doc)\n\n    if (this._canConvert(doc)) {\n      const name = doc.data.name\n      const url = doc.data.datei.type.startsWith('image/') ? Aeppic.Content.getUrl(doc, 'datei') : Aeppic.Content.getPreviewSrc(doc, 'datei', { format: 'jpg' })\n      \n      $refs.editor.insertTextAtCursor(`![${name}](${url} \"${name}\")`)\n    } else {\n      console.warn('user tried to insert not convertable document', doc)\n    }\n  }\n\n  _canConvert(doc) {\n    const notConvertableTypes = ['application/gzip', 'application/zip']\n\n    return doc.data.datei.type && !doc.data.datei.type.startsWith('video/') && !notConvertableTypes.includes(doc.data.datei.type)\n  }\n}\n\n","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0a99614c3b79c920","text":"Monaco Editor"}],"device":"ALL","label":"","description":"A Markdown Text Renderer"},"locks":[],"created":{"at":"2025-01-16T11:12:47+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2025-01-16T11:12:57+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1737025977189,"ct":1737025967223,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"c06bbf61-5813-4ec3-9048-e288037d3c97","v":"0ae26e8b20dc66e5","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"markdown","template":"<ae-expandable>\n  <monaco-editor @paste.native=\"controller.paste($event)\" @drop.native.stop.prevent=\"controller.drop($event)\" ref=\"editor\" style=\"min-height:40em\" v-model=\"field\" :readonly=\"readonly\" theme=\"vs-light\" language=\"markdown\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>\n\n<ae-layout  v-if=\"locals.canEdit\"\n            layout-id=\"f5a5c2a0-bf9c-4b44-b05b-8e545156df10\"\n            :param-document=\"document\"\n            :param-show-ui=\"true\"\n            param-file-form-id=\"6d3f6460-c6d2-4df1-8c05-e8bb66c5b8d2\"\n            param-file-form-field=\"datei\"\n            param-file-name-to-field=\"name\"\n            :param-save-new-documents=\"true\"\n            :param-flatten=\"true\"\n            ref=\"dropHandler\"\n            @added=\"controller.added($event)\"\n></ae-layout>\n","css":"","controller":"class Controller {\n  async init() {\n    // this._currentlyUploadingDocument = new Set()\n\n    // Aeppic.Uploads.on('uploaded', (d) => {\n    //   if (this._currentlyUploadingDocument.has(d.id)) {\n    //     this._currentlyUploadingDocument.delete(d.id)\n    //     this._insertDocumentInEditor(d.id)\n    //   }\n    // })\n    const canEdit = await Aeppic.canEdit(document)\n    locals.canEdit = Aeppic.Account && Aeppic.Account.data.isGroupAccount === false && canEdit === true\n  }\n\n  async getHoverContent(word) {\n    const infos = await Aeppic.find(`id:${word}`)\n    if (!infos || !infos.length) {\n      return []\n    }\n\n    const doc = infos[0]\n\n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n\n  async paste(event) {\n    $refs.dropHandler.$emit('paste', event)\n  }\n\n  async drop(event) {\n    $refs.dropHandler.$emit('drop', event)\n  }\n\n  async input(event) {\n    $refs.dropHandler.$emit('input', event)\n  }\n\n  async added(added) {\n    console.log(added)\n    if (!$refs.editor) {\n      return\n    }\n    \n    if (!added.documentIds) {\n      return\n    }\n\n    for (const id of added.documentIds) {\n      this._insertDocumentInEditor(id)\n    //   Aeppic.watch(id, (pastedDocument) => {\n\n    //     if (!pastedDocument || Aeppic.Uploads.isDocumentUploading(pastedDocument)) {\n    //       return\n    //     }\n\n        // this._insertDocumentInEditor(pastedDocument.id)\n      // })\n    }\n  }\n\n  async _insertDocumentInEditor(id) {\n    console.log('_insertDocumentInEditor' ,id)\n    \n    const doc = await Aeppic.get(id)\n\n    console.log(doc)\n\n    if (this._canConvert(doc)) {\n      const name = doc.data.name\n      const url = doc.data.datei.type.startsWith('image/') ? Aeppic.Content.getUrl(doc, 'datei') : Aeppic.Content.getPreviewSrc(doc, 'datei', { format: 'jpg' })\n      \n      $refs.editor.insertTextAtCursor(`![${name}](${url} \"${name}\")`)\n    } else {\n      console.warn('user tried to insert not convertable document', doc)\n    }\n  }\n\n  _canConvert(doc) {\n    const notConvertableTypes = ['application/gzip', 'application/zip']\n\n    return doc.data.datei.type && !doc.data.datei.type.startsWith('video/') && !notConvertableTypes.includes(doc.data.datei.type)\n  }\n}\n\n","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0a99614c3b79c920","text":"Monaco Editor"}],"device":"ALL","label":"","description":"A Markdown Text Renderer"},"locks":[],"created":{"at":"2019-05-28T14:47:01+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936844,"ct":1721209936844,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"bf1ce6bd8a6f8a69","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"b80d2635-0e77-4761-b30d-623edb771f95","v":"a66cded79e6f40f4","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"multiline","template":"<textarea :value=\"locals.value\"\n          class=\"_CSS-textarea\"\n          :class=\"{ '_CSS-textarea--growing': params.autoGrow, '_CSS-textarea--static': !params.autoGrow, '_CSS-textarea--readonly': readonly }\"\n          :rows=\" params.autoGrow ? 2 : 10\"\n          :readonly=\"readonly\"\n          :required=\"fieldInfo.required\"\n          ref=\"textarea\"\n          @input=\"controller.updateInput($event.target)\"\n></textarea>\n\n<ae-control class=\"_CSS-speech\" v-if=\"!readonly && params.showSpeechToText\" name=\"speech-to-text\" :document=\"document\" :watch=\"false\" :field-name=\"fieldName\"></ae-control>","css":"._CSS {\n  position: relative;\n}\n\n._CSS-textarea {\n  width: 100%;\n  border: solid 1px rgba(0,0,0,0.2);\n  padding: 6px;\n}\n\n._CSS-textarea._CSS-textarea--readonly {\n  resize: none;\n}\n\n._CSS-textarea--static {\n  resize: vertical;\n}\n\n._CSS-textarea--growing {\n  xoverflow-y: hidden;\n  resize: vertical;\n}\n\n._CSS-speech {\n  position: absolute;\n  right: 10px;\n  top: 10px;\n}","controller":"class MultilineControl {\n  async init() {\n    locals.value = getField()\n\n    await Aeppic.nextRenderPass()\n\n    if ($refs.textarea.scrollHeight > 0) {\n      $refs.textarea.style.height = $refs.textarea.scrollHeight + 'px'\n    }\n\n    this.debounceUpdate = Aeppic.debounce(() => {\n      setField(locals.value)\n    })\n\n    this.resize($refs.textarea)\n  }\n  \n  updateInput(target) {\n    locals.value = target?.value\n    this.debounceUpdate()\n  }\n\n  resize(input) {\n    // NOTE: setting to auto seems not nescessary. BUT it leads to 'jump-to-top' view-issue on tiping in the middle of the text\n    // input.style.height = 'auto'  \n\n    // add a small number to the height to prevent the scrollbar from being shown\n    input.style.height = (input.scrollHeight + 2) + 'px' \n  }\n}\n\n","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-03-28T14:50:58+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2023-06-14T11:44:46+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936845,"ct":1721209936845,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"182d539094e3f250","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-09-09T13:50:21+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936845,"data":{"cardinality":"BOTH","controller":"class MultilineControl {\n  async init() {\n    const colorMap = {\n      green: 'green-500',\n      red: 'red-500',\n      blue: 'blue-500',\n      grey: 'gray-500',\n      gray: 'gray-500',\n    }\n\n    locals.textColor = ''\n\n    if (params.color) {\n      locals.textColor = colorMap[params.color] || ''\n    }\n\n    locals.value = getField()\n\n    locals.characterCounterColorsClass = ''\n    locals.hadInputOnce = false\n\n    await Aeppic.nextRenderPass()\n\n    this.resize()\n  }\n\n  onInput(event) {\n    locals.hadInputOnce = true\n    \n    const input = event.target.value\n\n    if (params.max) {\n      if (input.length > params.max) {\n        event.target.value = input.slice(0, params.max)\n        event.preventDefault()\n      }\n    }\n\n    if (params.removeNewline) {\n      event.target.value = event.target.value.replace(/\\n|\\r|\\t/g, '')\n    }\n\n\n    locals.value = event.target.value \n\n    setField(locals.value, { delay: true }) \n\n    this.updateCharacterCounterColors(locals.value.length)\n    this.resize()\n  }\n\n  updateCharacterCounterColors(newLength) {\n    if (!params.max) {\n      locals.characterCounterColorsClass = ''\n      return\n    }\n\n    const remainingCharacters = params.max - newLength\n\n    if (remainingCharacters === 0) {\n      locals.characterCounterColorsClass = 'text-red-600'\n      return\n    }\n\n    if (remainingCharacters <= 20) {\n      locals.characterCounterColorsClass = 'text-rose-500'\n      return\n    }\n\n    if (remainingCharacters <= 40) {\n      locals.characterCounterColorsClass = 'text-amber-500'\n      return\n    }\n\n    locals.characterCounterColorsClass = ''\n  }\n\n  resize() {\n    if (CSS.supports('field-sizing', 'content')) {\n      return\n    }\n\n    // NOTE: setting to auto seems not nescessary. BUT it leads to 'jump-to-top' view-issue on tiping in the middle of the text\n    // input.style.height = 'auto'  \n\n    let newHeight = ($refs.textarea.scrollHeight + 2)\n\n    if (newHeight < 30) {\n      newHeight = 30\n    }\n\n    // add a small number to the height to prevent the scrollbar from being shown\n    $refs.textarea.style.height = newHeight + 'px' \n  }\n}\n\n","css":"._CSS-textarea {\n  width: 100%;\n}\n\n._CSS-textarea._CSS-textarea--readonly {\n  resize: none;\n}\n\n._CSS-textarea--static {\n  resize: none;\n}\n\n._CSS-textarea--growing {\n  resize: vertical;\n}\n","description":"","device":"ALL","label":"","limitTypes":false,"name":"multiline","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<div class=\"relative flex flex-col\"\n    :data-test=\"fieldName\"\n>\n  <textarea :value=\"locals.value\"\n            class=\"min-h-[2em] _CSS-textarea border rounded-md p-1 bg-white\"\n            :class=\"{ '_CSS-textarea--growing': params.autoGrow, '_CSS-textarea--static': !params.autoGrow, '_CSS-textarea--readonly': readonly, 'ae-form-cell__input ae-form-cell__input--invalid': fieldInfo.required && !locals.value, 'border-red-500': validationErrors?.required, ['text-' + locals.textColor]: true }\"\n            :rows=\" params.autoGrow ? 1 : 10\"\n            :readonly=\"readonly\"\n            :required=\"validationErrors?.required\"\n            ref=\"textarea\"\n            @input=\"controller.onInput($event)\"\n            style=\"field-sizing:content;\"\n\n  ></textarea>\n  <div v-if=\"params.max\"\n      class=\"self-end text-sm text-neutral-500\"\n      :class=\"[locals.characterCounterColorsClass]\"\n  >\n    {{ locals.value.length }} / {{ params.max }}\n  </div>\n</div>\n       \n       ","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"dad096a8-2a8d-40a5-ad5b-72a8f87ba1d0","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-07-09T11:40:48+00:00","backedBy":null,"by":"47325375-27fb-4303-8bf5-9438a0524219","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"e5c0e9f361ea632d"},"stamps":[],"t":1752061248981,"v":"a6e64f1c405441c1"},{"id":"0776b54a-a957-4ae3-a488-b1847207d811","v":"9102dbad33b06f5f","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"WIP","name":"new-child","template":"<ae-layout  layout-id=\"21e13528-924d-4eaf-b3cc-99cc7c5bfbed\"\n            :param-document=\"document\"\n            v-if=\"!readonly\"\n></ae-layout>\n\n<ae-list list-id=\"simple-list\" :search=\"'p:' + document.id\">\n</ae-list>\n<!-- @show=\"() => params.documentChangedBus && params.documentChangedBus.changed()\" -->\n<!-- @close=\"() => params.documentChangedBus && params.documentChangedBus.reset()\" -->","css":"","controller":"","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-09-29T12:59:41+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2021-09-29T13:25:32+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936845,"ct":1721209936845,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"324df4771321bb6e","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"52883178-39ed-4dbe-92c7-aa4c65e041d8","v":"b08fb776d27ee101","previous":{"v":"b603f7081515b8d9"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"core-rd","name":"NOTUSED?position-draft","template":"<div class=\"ae-form-cell__input\">\n  <div class=\"_CSS-header\">\n    <label class=\"ae-form-cell__label\">\n      Leistungen\n    </label>\n\n    <div class=\"_CSS-header__actions\">\n      <button class=\"ae-button\" @click.stop=\"controller.show()\">\n        <ae-icon class=\"fa fa-plus\"></ae-icon>\n      </button>\n\n      <ae-layout layout-id=\"cf3b721d-9825-40ba-8076-4e6cbaf164d1\" v-if=\"!locals.readonly\" :param-document-id=\"document.id\" :xparam-single-button=\"true\"></ae-layout>\n    </div>\n\n    <ae-design name=\"timetracking:progress\" :document=\"document\" style=\"flex: 1;\"></ae-design>\n  </div>\n  \n\n  <section v-if=\"locals.documents.length\">\n    <!-- <div class=\"ae-form-cell__label _CSS-label\" style=\"width: 100%;\">\n      <div>Erfasst</div>\n    </div> -->\n    <ae-list  list-id=\"922e8251-6e60-45a4-9df6-3d434363ac01\" \n              :documents=\"locals.documents\" \n              param-design=\"line-item\" \n              @selected=\"Aeppic.drillInto($event)\"\n              @merge-up=\"controller.mergeTime($event, -1)\"\n              @merge-down=\"controller.mergeTime($event, 1)\"\n    ></ae-list>\n  </section>\n</div> \n\n<ae-dialog ref=\"dialogNew\" @close=\"controller.reset()\" :backdrop-close=\"false\" :dialog-size=\"{ height: '50vh' }\">\n  <template #header=\"scope\">\n    <div class=\"_CSS-dialog__header\">\n      <span>\n        Neue Leistung erfassen\n      </span>\n      <button @click.stop=\"scope.close()\" class=\"ae-button\">\n        <ae-icon class=\"fa fa-times\"></ae-icon>\n      </button>\n    </div>\n  </template>\n  <template #body>\n    <ae-form v-if=\"locals.newPosition\" :document=\"locals.newPosition\"></ae-form>\n  </template>\n  <template #commands=\"scope\">\n    <div>\n    <button @click.stop=\"scope.close()\" class=\"ae-button\">Abbrechen</button>\n    <button class=\"ae-button ae-button--primary\" @click.stop=\"controller.save()\" :disabled=\"!locals.newPosition || !locals.newPosition.data.titel\">Speichern</button>\n    </div>\n  </template>\n</ae-dialog>","css":"._CSS {\n  width: 100%;\n}\n\n._CSS-new {\n  display: flex;\n  flex-flow: column;\n  align-items: baseline;\n}\n\n._CSS-dialog__header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n._CSS-header {\n  display: flex;\n  align-items: baseline;\n}\n\n._CSS-header__actions {\n  display: flex;\n}","controller":"class Controller {\n  async init() {\n    this.FORMS = {\n      AbzurechnendePosition: '651d8603-6d29-4534-be59-4449387e857c',\n      ErfassteZeit: 'f9279010-1935-4139-abbb-82d928aa09dc',\n    }\n\n    locals.documents = []\n    locals.newPosition = null\n\n    Aeppic.subscribe(`p:${document.id} (f.id:${this.FORMS.AbzurechnendePosition} OR  f.id:${this.FORMS.ErfassteZeit})`, { sort: ['data.datum', 'created.at'] }, (documents) => {\n      locals.documents = documents\n    }) \n\n    await Aeppic.nextRenderPass()\n  }\n\n  async show() {\n    locals.newPosition = null\n    locals.newPosition = await Aeppic.new(this.FORMS.AbzurechnendePosition, document)\n\n    locals.newPosition.data.abrechenbar = true\n    locals.newPosition.data.menge = 1\n    locals.newPosition.data.dauer = {}\n\n    $refs.dialogNew.show()\n  }\n\n\n  async save() {\n    if (!locals.newPosition) {\n      return\n    }\n\n    const data = locals.newPosition.data\n\n    if (data.typ === 'ZEIT') {\n      data.menge = 1\n    } else if (data.typ === 'ENTFERNUNG') {\n      data.dauer = {}\n    } else if (data.typ === 'MATERIAL') {\n      data.dauer = {}\n    }\n\n    data.titel = data.titel.trim()\n    Aeppic.save(locals.newPosition)\n\n    this.reset()\n\n    $refs.dialogNew.close()\n  }\n\n  reset() {\n    locals.newPosition = null\n  }\n\n  async mergeTime(doc, addOrRemoveIndexByNumber) {\n    const times = locals.documents.filter(d => d.f.id === this.FORMS.ErfassteZeit)\n    const index = times.findIndex(d => d.id === doc.id)\n\n    const ensuredDoc = times[index]\n\n    const otherTimeDocIndex = index + addOrRemoveIndexByNumber\n\n    if (index >= 0 && otherTimeDocIndex >= 0 && otherTimeDocIndex < times.length) {\n      const otherTimeDoc = await Aeppic.edit(times[otherTimeDocIndex])\n      console.log('before', otherTimeDoc.data.dauer)\n      otherTimeDoc.data.dauer = Aeppic.DateTimeDuration.fromObject(otherTimeDoc.data.dauer).plus(ensuredDoc.data.dauer).toObject()\n      console.log('after', otherTimeDoc.data.dauer)\n\n      Aeppic.save(otherTimeDoc)\n      Aeppic.delete(ensuredDoc)\n    } \n  } \n} ","parameters":[],"standalone":"REQUIRED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-10-27T14:10:03+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2024-08-21T11:53:53+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1724241233550,"ct":1721209936845,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"20d13e0a-95ef-4904-8a90-64da29053730","v":"a748ba15322c1248","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"now","template":"<ae-control :name=\"params.control || 'date'\"\n            :document=\"document\"\n            :field-name=\"fieldName\"\n            :readonly=\"readonly\"\n            :params=\"params\"\n></ae-control>","css":"","controller":"class Controller {\n  async init () {\n    const field = getField()\n\n    if (!field) {\n      if (readonly && !params.writeReadonly)  {\n        return\n      }\n\n      if (params.control === 'datetime') {\n        document.data[fieldName] = this.nowWithTime()\n      } else {\n        document.data[fieldName] = this.now()\n      }\n    }\n  }\n\n  now(value) {\n    const date = new Date()\n\n    // return Aeppic.DateTime.fromJSDate(date).toFormat(\"yyyy-LL-dd'T'TTZZZ\")\n    const dateTimeObject = Aeppic.DateTime.fromJSDate(date).startOf('day')\n    const day = dateTimeObject.toFormat('yyyy-LL-dd')\n\n    let timeWithTimeZone = dateTimeObject.toFormat('TTZZZ')\n\n    // FIXME: Chrome 75 (Windows 10) does output an invalid timezone (2:00:00+0200) with a length of 12 characters.\n    if (timeWithTimeZone.length === 12) {\n      const offset = (Math.abs(new Date().getTimezoneOffset() / 60)).toString().padStart(2, '0')\n      timeWithTimeZone = `${offset}${timeWithTimeZone.slice(1)}`\n    }\n\n    return `${day}T${timeWithTimeZone}`\n  }\n  \n  nowWithTime(value) {\n    const date = new Date()\n\n    // return Aeppic.DateTime.fromJSDate(date).toFormat(\"yyyy-LL-dd'T'TTZZZ\")\n    const dateTimeObject = Aeppic.DateTime.fromJSDate(date)\n    const day = dateTimeObject.toFormat('yyyy-LL-dd')\n\n    let timeWithTimeZone = dateTimeObject.toFormat('TTZZZ')\n\n    // FIXME: Chrome 75 (Windows 10) does output an invalid timezone (2:00:00+0200) with a length of 12 characters.\n    if (timeWithTimeZone.length === 12) {\n      const offset = (Math.abs(new Date().getTimezoneOffset() / 60)).toString().padStart(2, '0')\n      timeWithTimeZone = `${offset}${timeWithTimeZone.slice(1)}`\n    }\n\n    return `${day}T${timeWithTimeZone}`\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-08-16T08:54:48+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2021-12-15T09:03:15+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936845,"ct":1721209936845,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"47aaee4ff10275aa","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-09-09T13:56:28+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936846,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init () {\n    const field = getField()\n\n    // \"UNDEFINED\" is a well-known value to prevent this control from updating the field\n    if (!field && field !== 'UNDEFINED') {\n      if (readonly && !params.writeReadonly)  {\n        return\n      }\n\n      if (params.control === 'datetime') {\n        document.data[fieldName] = this.nowWithTime()\n      } else {\n        document.data[fieldName] = this.now()\n      }\n    }\n  }\n\n  now(value) {\n    return Aeppic.DateTime.local().startOf('day').toISODate()\n\n\n    // WARNING: this code leads to LOCALE characters (e.g. korean symbols) to be added to string\n    // const date = new Date()\n\n    // // return Aeppic.DateTime.fromJSDate(date).toFormat(\"yyyy-LL-dd'T'TTZZZ\")\n    // const dateTimeObject = Aeppic.DateTime.fromJSDate(date).startOf('day')\n    // const day = dateTimeObject.toFormat('yyyy-LL-dd')\n\n    // let timeWithTimeZone = dateTimeObject.toFormat('TTZZZ')\n\n    // // FIXME: Chrome 75 (Windows 10) does output an invalid timezone (2:00:00+0200) with a length of 12 characters.\n    // if (timeWithTimeZone.length === 12) {\n    //   const offset = (Math.abs(new Date().getTimezoneOffset() / 60)).toString().padStart(2, '0')\n    //   timeWithTimeZone = `${offset}${timeWithTimeZone.slice(1)}`\n    // }\n\n    // return `${day}T${timeWithTimeZone}`\n  }\n  \n  nowWithTime(value) {\n    return Aeppic.DateTime.local().toISO()\n\n    // WARNING: this code leads to LOCALE characters (e.g. korean symbols) to be added to string\n    // const date = new Date()\n\n    // // return Aeppic.DateTime.fromJSDate(date).toFormat(\"yyyy-LL-dd'T'TTZZZ\")\n    // const dateTimeObject = Aeppic.DateTime.fromJSDate(date)\n    // const day = dateTimeObject.toFormat('yyyy-LL-dd')\n\n    // let timeWithTimeZone = dateTimeObject.toFormat('TTZZZ')\n\n    // // FIXME: Chrome 75 (Windows 10) does output an invalid timezone (2:00:00+0200) with a length of 12 characters.\n    // if (timeWithTimeZone.length === 12) {\n    //   const offset = (Math.abs(new Date().getTimezoneOffset() / 60)).toString().padStart(2, '0')\n    //   timeWithTimeZone = `${offset}${timeWithTimeZone.slice(1)}`\n    // }\n\n    // return `${day}T${timeWithTimeZone}`\n  }\n}","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"now","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<ae-control :name=\"params.control && 'rd:' + params.control || 'rd:date'\"\n            :document=\"document\"\n            :field-name=\"fieldName\"\n            :readonly=\"readonly\"\n            :params=\"params\"\n></ae-control>","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"3afccb15-0dde-49c2-b31a-5651309505d3","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-09-19T14:35:46+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"a16b3d0c18664478"},"stamps":[],"t":1758292546091,"v":"0d81ee39b866f903"},{"id":"37359c46-1edf-4b0e-9d76-5d5885cb4de1","v":"a631f35a41ba6532","p":"91f205bc-9989-4fbe-8966-bd4ba9777c26","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"number","template":"<!-- FIXME: got negative customer feedback on mousewheel functionality  -->\n<!-- NOTE: @mousewheel enables increase/decrease automatically on Chrome -->\n<!-- @mousewheel  -->\n\n<pre v-if=\"params.debug\">\ninputValue {{ locals.inputValue  }}\nfield {{ field }}\ndisplayValue {{ controller.displayValue }}\n</pre>\n\n<div v-if=\"!fieldInfo.cardinality\">\n  <input  style=\"width:100%\" \n          type=\"text\"\n          inputmode=\"decimal\" \n          pattern=\"^([-+,0-9.]+)\" \n          :value=\"locals.focus ? locals.inputValue : controller.displayValue\"\n          @input=\"controller.setField($event.target.value)\"\n          :required=\"fieldInfo.required\"\n          :readonly=\"params.readonly != null ? params.readonly : readonly\"\n          :aria-label=\"label\"\n          :step=\"params.step || '1'\"\n          @focus=\"locals.focus = true\"\n          @blur=\"locals.focus = false\"\n  >\n</div>\n<template v-else>\n  <input type=\"number\" v-for=\"value of locals.values\" v-model.number=\"value.value\" style=\"width:100%\" :readonly=\"readonly\" :aria-label=\"label\"  @blur=\"controller.clean()\" @input=\"controller.update()\" autocomplete=\"off\">\n\n  <button v-if=\"!locals.showAdd\" class=\"ae-button\" @click.stop=\"locals.showAdd = true\">\n    <ae-icon class=\"fa fa-plus\"></ae-icon>\n  </button>\n  \n\n    <div class=\"_CSS-new\" v-if=\"locals.showAdd && !readonly && !params.noAdd\">\n      <h5>Neuer Eintrag</h5> \n      <div class=\"_CSS-new__input\">\n        \n        <input ref=\"add\" type=\"number\" style=\"flex: 1;\" @keypress.enter.prevent=\"controller.add()\" v-model.number=\"locals.add\">\n        <button class=\"ae-button\" @click.stop=\"controller.add()\">\n          <ae-icon class=\"fa fa-save\"></ae-icon>\n        </button>\n      <button v-if=\"locals.showAdd\" class=\"ae-button\" @click.stop=\"locals.showAdd = false\">\n        <ae-icon class=\"fa fa-times\"></ae-icon>\n      </button>\n      </div>\n    </div>\n</template>\n","css":"._CSS-new {\n  padding: var(--bp-input-content-padding);\n}\n\n._CSS-new__input {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n}","controller":"class Controller {\n  init() {\n    this.nonDecimalSymbolsRegex = /([^-+,0-9.])/g\n\n    locals.inputValue = getField()\n\n    // reformat dots as comma for german locale\n    if (Intl?.NumberFormat()?.resolvedOptions()?.locale === 'de') {\n      locals.inputValue = `${locals.inputValue}`.replace('.', ',')\n    }\n\n    locals.add = 0\n    locals.showAdd = false\n\n    if (fieldInfo.cardinality) {\n      locals.values = getField().map(num => {return { value: num }})\n    }\n  }  \n\n  add(force = false) {\n    if (force || !Number.isNaN(locals.add)) {\n      locals.values.push({ value: locals.add })\n      this.update()\n      locals.add = 0\n      $refs.add.focus()\n    }  \n  }\n\n  update() {\n    setField(locals.values.map(v => v.value).filter(v => v !== ''))\n  }\n\n  clean() {\n    locals.values = getField().map(num => {return { value: num }})\n  }\n\n  setField(value) {\n    // set inputValue twice to allow input element to \"remove\" invalid symbols\n    locals.inputValue = value\n    locals.inputValue = value.replace(this.nonDecimalSymbolsRegex, '')\n\n    const parsed = value ? Number.parseFloat(value.replace(this.nonDecimalSymbolsRegex, '').replace(',', '.')) : 0\n    const sanitizedValue = Number.isNaN(parsed) ? 0 : parsed\n\n    console.debug({ value, parsed, sanitizedValue })\n\n    setField(sanitizedValue)  \n  }\n\n  get displayValue() {\n    const field = getField()\n\n    console.debug({field})\n\n    return new Intl.NumberFormat('de-DE').format(field)\n  }\n}    \n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":true,"types":["number"],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-02-21T20:01:55+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2024-07-17T09:50:56+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936846,"ct":1721209936846,"a_depth":3,"a":["system","controls-folder","91f205bc-9989-4fbe-8966-bd4ba9777c26"],"a_forms":["system-folder-form","repository-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null,null],"stamps":[],"previous":{"v":"6749e39847b06d79","f":{"id":"control-form","v":"9bddead515b3a305","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-12-01T15:32:11+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936846,"data":{"cardinality":"BOTH","controller":"class Controller {\n  init() {\n    this.nonDecimalSymbolsRegex = /([^\\-\\+,0-9.])/g\n\n    locals.inputValue = getField()\n\n    // reformat dots as comma for german locale\n    if (Intl?.NumberFormat()?.resolvedOptions()?.locale === 'de') {\n      locals.inputValue = `${locals.inputValue}`.replace('.', ',')\n    }\n\n    locals.add = 0\n    locals.showAdd = false\n\n    if (fieldInfo.cardinality) {\n      locals.values = getField().map(num => {return { value: num }})\n    }\n  }  \n\n  add(force = false) {\n    if (force || !Number.isNaN(locals.add)) {\n      locals.values.push({ value: locals.add })\n      this.update()\n      locals.add = 0\n      $refs.add.focus()\n    }  \n  }\n\n  update() {\n    setField(locals.values.map(v => v.value).filter(v => v !== ''))\n  }\n\n  clean() {\n    locals.values = getField().map(num => {return { value: num }})\n  }\n\n  setField(value) {\n    // set inputValue twice to allow input element to \"remove\" invalid symbols\n    locals.inputValue = value\n    locals.inputValue = value.replace(this.nonDecimalSymbolsRegex, '')\n\n    const parsed = value ? Number.parseFloat(value.replace(this.nonDecimalSymbolsRegex, '').replace(',', '.')) : 0\n    const sanitizedValue = Number.isNaN(parsed) ? 0 : parsed\n\n    console.debug({ value, parsed, sanitizedValue })\n\n    setField(sanitizedValue)  \n  }\n\n  get displayValue() {\n    const field = getField()\n\n    console.debug({field})\n\n    return new Intl.NumberFormat('de-DE').format(field)\n  }\n}   ","css":"._CSS-new {\n  padding: var(--bp-input-content-padding);\n}\n\n._CSS-new__input {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n}","description":"","device":"ALL","label":"","limitTypes":false,"name":"number","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<!-- FIXME: got negative customer feedback on mousewheel functionality  -->\n<!-- NOTE: @mousewheel enables increase/decrease automatically on Chrome -->\n<!-- @mousewheel  -->\n\n<pre v-if=\"params.debug\">\ninputValue {{ locals.inputValue  }}\nfield {{ field }}\ndisplayValue {{ controller.displayValue }}\n</pre>\n \n<!-- FIXME: pattern v-flag does not allow any hyphen to be matched. But it should?! -->\n<!-- pattern=\"^([0-9.,+\\-]+)\"  -->\n<div v-if=\"!fieldInfo.cardinality\">\n  <input  class=\"w-full border rounded-md py-1 px-2 flex gap-1 bg-white\"\n          type=\"text\"\n          inputmode=\"numeric\" \n          pattern=\"^([0-9.,+]+)\" \n          :value=\"locals.focus ? locals.inputValue : controller.displayValue\"\n          @input=\"controller.setField($event.target.value)\"\n          :required=\"fieldInfo.required\"\n          :readonly=\"params.readonly != null ? params.readonly : readonly\"\n          :aria-label=\"label\"\n          :step=\"params.step || '1'\"\n          @focus=\"locals.focus = true\"\n          @blur=\"locals.focus = false\"\n  >\n</div>\n<template v-else>\n  TODO!\n  <!-- <input type=\"number\" v-for=\"value of locals.values\" v-model.number=\"value.value\" style=\"width:100%\" :readonly=\"readonly\" :aria-label=\"label\"  @blur=\"controller.clean()\" @input=\"controller.update()\" autocomplete=\"off\">\n\n  <button v-if=\"!locals.showAdd\" class=\"ae-button\" @click.stop=\"locals.showAdd = true\">\n    <ae-icon class=\"fa fa-plus\"></ae-icon>\n  </button>\n  \n\n    <div class=\"_CSS-new\" v-if=\"locals.showAdd && !readonly && !params.noAdd\">\n      <h5>Neuer Eintrag</h5> \n      <div class=\"_CSS-new__input\">\n        \n        <input ref=\"add\" type=\"number\" style=\"flex: 1;\" @keypress.enter.prevent=\"controller.add()\" v-model.number=\"locals.add\">\n        <button class=\"ae-button\" @click.stop=\"controller.add()\">\n          <ae-icon class=\"fa fa-save\"></ae-icon>\n        </button>\n      <button v-if=\"locals.showAdd\" class=\"ae-button\" @click.stop=\"locals.showAdd = false\">\n        <ae-icon class=\"fa fa-times\"></ae-icon>\n      </button>\n      </div>\n    </div> -->\n</template>\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"d309508a-1351-4141-a44e-5437ab25eeec","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-01-05T12:36:35+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"15bfc6534c74a3ff"},"stamps":[],"t":1767616595759,"v":"9cb505eff47fab61"},{"id":"64c459a2-aeb2-467c-90fd-f5caaafce30f","v":"fd777ae287dca414","cloneOf":{"id":"67b47546-04c6-455e-8a0b-e2d687f08331","v":"2d7a68a9d241ea4b","text":"number-sums"},"originalCloneOf":{"id":"67b47546-04c6-455e-8a0b-e2d687f08331","v":"2d7a68a9d241ea4b","text":"number-sums"},"previous":{"v":"069574e0227e0944"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"number-sums","template":"<div data-ae-form-field-input class=\"p-2 rounded-md border\"> \n  <div class=\"pb-4\">\n    <div class=\"text-neutral-700\"> Gesamt </div>\n    <div>\n      {{ locals.sum }}  \n    </div>\n  </div>\n  <div v-if=\"locals.month && Object.keys(locals.month).length\">\n    <div class=\"text-neutral-700\"> nach Monaten </div>\n    <div class=\"grid grid-cols-2\" v-for=\"(value, month) in locals.month\">\n      <span>{{ Aeppic.DateTime.fromISO(month).toFormat('LLLL yyyy') }}</span>\n      <span class=\"text-end\">{{ value }}</span>\n    </div>\n  </div>\n  <div v-if=\"locals.year && Object.keys(locals.year).length\">\n    <div class=\"text-neutral-700\"> nach Jahren </div>\n    <div class=\"grid grid-cols-2\" v-for=\"(value, year) in locals.year\">\n      <span>{{ Aeppic.DateTime.fromISO(year).toFormat('yyyy') }}</span>\n      <span class=\"text-end\">{{ value }}</span>\n    </div>\n  </div>\n</div>\n","css":"","controller":"class Controller {\n  init() {\n    if (params.sum) {\n      locals.sum = this.format(document.data[params.sum])\n    }\n\n    if (params.month && document.data[params.month]) {\n      locals.month = JSON.parse(document.data[params.month])\n\n      for (const month in locals.month) {\n        locals.month[month] = this.format(locals.month[month])\n      }\n    }\n    \n    if (params.year && document.data[params.year]) {\n      locals.year = JSON.parse(document.data[params.year])\n\n      for (const year in locals.year) {\n        locals.year[year] = this.format(locals.year[year])\n      }\n    }\n  }\n\n  format(value) {\n    if (value === '' || value == null) {\n      return value\n    }\n\n    if (params.type === 'currency') {\n      return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(value)\n    }\n\n    return new Intl.NumberFormat('de-DE').format(value)\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2025-02-04T12:49:53+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2025-02-04T14:48:55+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1738680535452,"ct":1738673393088,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"67b47546-04c6-455e-8a0b-e2d687f08331","v":"2d7a68a9d241ea4b","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"number-sums","template":"<div  class=\"ae-form-cell__input _CSS-container\"\n      :class=\"{ 'ae-form-cell--readonly' : readonly }\"\n>\n  <div>\n    <div class=\"_CSS-heading\"> Gesamt </div>\n    <div>\n      {{ locals.sum }}  \n    </div>\n  </div>\n  <div v-if=\"locals.month && Object.keys(locals.month).length\">\n    <div class=\"_CSS-heading\"> nach Monaten </div>\n    <div class=\"_CSS-times\" v-for=\"(value, month) in locals.month\">\n      <span>{{ Aeppic.DateTime.fromISO(month).toFormat('LLLL yyyy') }}</span>\n      <span class=\"_CSS-box--align-right\">{{ value }}</span>\n    </div>\n  </div>\n  <div v-if=\"locals.year && Object.keys(locals.year).length\">\n    <div class=\"_CSS-heading\"> nach Jahren </div>\n    <div class=\"_CSS-times\" v-for=\"(value, year) in locals.year\">\n      <span>{{ Aeppic.DateTime.fromISO(year).toFormat('yyyy') }}</span>\n      <span class=\"_CSS-box--align-right\">{{ value }}</span>\n    </div>\n  </div>\n</div>\n\n","css":"._CSS-container {\n  display: grid;\n  grid-template-columns: 1fr 2.5fr 2.5fr;\n  grid-gap: 50px;\n}\n\n._CSS-heading {\n  xfont-weight: bold;\n  color: var(--ae-background-600);\n  margin-bottom: var(--ae-space-2);\n}\n\n._CSS-times {\n  max-width: 250px;\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n}\n\n._CSS-box--align-right {\n  text-align: right;\n}\n\n","controller":"class Controller {\n  init() {\n    if (params.sum) {\n      locals.sum = document.data[params.sum]\n    }\n    if (params.month && document.data[params.month]) {\n      locals.month = JSON.parse(document.data[params.month])\n    }\n    if (params.year && document.data[params.year]) {\n      locals.year = JSON.parse(document.data[params.year])\n    }\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-06-23T14:06:32+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2021-06-23T14:06:32+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936846,"ct":1721209936846,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"1e7795cae3909be5","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"b7d88871-266b-49c0-aa02-1cc589ee6a8e","v":"b80e1f23b09d2034","p":"controls-folder","f":{"id":"control-form","v":"8f342d57244fe7b5","text":"Control"},"data":{"namespace":"","name":"optional","template":"<ae-control name=\"\" :field-name=\"params.switchField\" :document=\"document\" :readonly=\"readonly\" :label=\"label\" :disable-default-control=\"false\"></ae-control>\n<ae-control v-if=\"document.data[params.switchField]\" :name=\"params.controlName\" :params=\"params\" class=\"flex-1 ml-2\" :field-name=\"fieldName\" :document=\"document\" :readonly=\"readonly\" :label=\"label\" :disable-default-control=\"false\"></ae-control>","css":"._CSS {\n  display: flex;\n  width: 100%;\n  grid-gap:2px;\n  margin-top:4px;\n  align-items: center;\n} ","controller":"","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-07-04T07:08:57+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-07-11T13:30:25+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721144004096,"ct":1721144004096,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"64ee8666df15b6fa","f":{"id":"control-form","v":"7672ea0b1b6d4c70","text":"Control"}},"meta":{"modified":{"at":"2024-07-16T17:33:24+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"9ddead93-060d-44a6-98e4-15eeda47879e","v":"48c4594f58672dab","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"page","template":"<div v-show=\"!locals.hidden\" class=\"border rounded-md px-2 min-h-[2em]\">\n  <ae-design :name=\"params.design || 'rd:page-inner'\"\n            :document=\"locals.document\"\n            :style=\"{ 'min-height': params.minHeight }\"\n            @hide=\"locals.hidden = true\"\n            @show=\"locals.hidden = false\"\n  ></ae-design>\n \n  <div class=\"absolute top-[-32px] right-0\">\n    <ae-layout v-if=\"locals.document\" \n              layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n              param-border=\"border\"\n              param-icon=\"far fa-external-link text-xs\"\n              @selected=\"Aeppic.drillInto(locals.document)\"\n              param-title=\"Zum Dokument navigieren\"\n    ></ae-layout>\n    <ae-layout v-else\n          layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n          param-border=\"border\"\n          param-icon=\"far fa-times text-xs\"\n          @selected=\"$refs.removeConfirmDialog.$emit('show', field)\"\n          param-title=\"Entfernen\"\n    ></ae-layout>\n  </div> \n</div>\n\n<ae-layout layout-id=\"6ee77ae8-b2f6-4fce-8fbc-788a55d5f7da\"\n          ref=\"removeConfirmDialog\"\n          @delete=\"controller.removeReference($event)\"\n></ae-layout>","css":"","controller":"class Controller {\n  async init() {\n    locals.hidden = false\n\n    locals.document = await Aeppic.get(getField().id)\n  }\n\n  removeReference(reference) {\n    if (fieldInfo.cardinality)  {\n      document.removeReference(fieldName, reference)\n    } else {\n      document.removeReference(fieldName)\n    }\n    \n    // this.resetDataInFieldByParamAs()\n\n    // this._announceDeleteToInlineLayouts()\n\n    // this._announceFieldChange(document.data[fieldName])\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-04-04T08:38:50+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-04-04T09:25:22+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936846,"ct":1721209936846,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"eca82d36c1797d20","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"e0ca944e-fe27-40b6-91b2-944ec9141d75","v":"5d6c99365df0244c","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"page","template":"<div class=\"ae-form-cell__input\" v-show=\"!locals.hidden\" @ae-drill-into.stop.prevent=\"controller.onDrillInto($event)\">\n  <ae-design :name=\"params.design || 'page-with-toolbar'\"\n            :document-id=\"controller.id\"\n            :style=\"{ 'min-height': params.minHeight }\"\n            :params=\"{ rightOverlay: params.rightOverlay ?? true }\"\n            @hide=\"locals.hidden = true\"\n            @show=\"locals.hidden = false\"\n  ></ae-design>\n \n  <div class=\"_CSS-bar\" xv-if=\"!Aeppic.isEditableDocument(document)\">\n    <button class=\"ae-button\" @click.stop=\"Aeppic.drillInto(controller.id)\" title=\"In Vollbild anzeigen\">\n      <ae-icon class=\"fad fa-expand-arrows-alt\"></ae-icon>\n    </button>\n  </div> \n</div>\n","css":"._CSS-bar {\n  display: flex;\n  justify-content: flex-end;\n\n  position: absolute;\n  right: 0;\n  top: 0;\n}","controller":"init() {\n  locals.hidden = false\n  console.warn('FIXME: i need params.documentChangedBus!!!!!!!!!!!')\n}\n\nget id() {\n  const field = getField()\n  if (field) {\n    return field.id\n  }\n\n  return document.id\n}\n\nonDrillInto(event) {\n  // re-emit drillInto event, but default the mode to dialog.\n  // 'dialog' makes sense because we will most likely want to quickly view things from within the embedded reference.\n  // This is especially true if the document containing the page Control is already opened with the DrillInto Dialog. Without this mode, the DrillInto Dialog would just clouse\n  Aeppic.drillInto(\n    event.detail.targetDocument,\n    { mode: 'dialog', ...event.detail }\n  )\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-10-06T13:46:13+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-02-22T15:57:50+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936846,"ct":1721209936846,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"0f3a31d74f6e80be","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder","91f205bc-9989-4fbe-8966-bd4ba9777c26"],"a_depth":3,"a_forms":["system-folder-form","repository-folder-form","repository-folder-form"],"created":{"at":"2017-08-29T11:10:30+02:00","by":"","client":"26846b0d-2bd3-48e4-a529-817e3643a1a9","from":""},"ct":1721209936847,"data":{"cardinality":"SINGLE","controller":"init() {\n  locals.isEncryptedField = fieldInfo.subType === 'password'\n  locals.showPassword = false\n}","css":"._CSS {\r\n  display: flex;\r\n  background-color: var(--ae-ui-element-canvas-color);\r\n  border: var(--ae-border-width) solid var(--ae-border-color);\r\n  border-radius: var(--ae-border-radius);\r\n}\r\n\r\n._CSS input {\r\n  flex: 1;\r\n  border:none;\r\n}","description":"","device":"ALL","label":"","limitTypes":true,"name":"password","namespace":"","packages":[],"parameters":[],"standalone":"FIELD","template":"<input :type=\"locals.showPassword ? 'text' : 'password'\" v-model=\"field\" :required=\"fieldInfo.required\" :readonly=\"readonly\" :aria-label=\"label\" style=\"width:100%\">\n<template v-if=\"!locals.isEncryptedField\">\n  <button class=\"ml-2 mr-2 opacity-50 hover:opacity-100\" @click=\"Aeppic.copyToClipboard(field)\">\n    <ae-icon class=\"fa fa-clipboard\" title=\"Copy password\"></ae-icon>\n  </button>\n  <button class=\"ml-2 mr-2 opacity-50 hover:opacity-100\" @mousedown.prevent.stop=\"locals.showPassword = true\" @mouseup.prevent.stop=\"locals.showPassword = false\">\n    <ae-icon class=\"fa fa-eye\" title=\"Show password\"></ae-icon>\n  </button>\n</template>","types":["string","password"]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"66f2df2e-b6ff-4341-9817-6b1822f9b4cf","inheritedLocks":[null,null,null,null],"locks":[],"modified":{"at":"2024-07-17T09:52:28+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"p":"91f205bc-9989-4fbe-8966-bd4ba9777c26","previous":{"v":"5cc174435c5de739"},"stamps":[],"t":1721209948935,"v":"4eaaa8b7502c0166"},{"id":"e609280c-5b9c-4658-81d0-ac3e144a64fe","v":"89ff1b23f9d48f8d","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"energy","name":"payment-planner","template":"<button class=\"ae-button\" @click.stop=\"$refs.planner.show()\">\n  <ae-icon class=\"fa fa-credit-card-blank\"></ae-icon>\n  Ratenzahlung\n</button>\n\n<ae-control name=\"children\" \n            :document=\"document\"\n            :params=\"params\"\n            :field-name=\"fieldName\"\n            v-on=\"$listeners\"\n></ae-control>  \n\n\n<ae-dialog ref=\"planner\">\n  <template #header=\"scope\">\n    <div style=\"display:flex;justify-content:space-between;\">\n      <div>\n        Ratenzahlung\n      </div>\n      <button class=\"ae-button\" @click.stop=\"scope.close()\">\n        <ae-icon class=\"fa fa-times\"></ae-icon>\n      </button>\n    </div>\n  </template>\n\n  <template #body>\n    <div>\n      <ae-form :readonly=\"locals.working\" :document=\"locals.paymentPlanning\" @data-changed=\"Aeppic.commit(locals.paymentPlanning)\"></ae-form>\n    </div>\n  </template>\n\n  <template #commands=\"scope\">\n    <div style=\"display:flex;gap:var(--ae-space-1)\">\n      <button class=\"ae-button\" @click.stop=\"scope.close()\">\n        Abbrechen\n      </button>\n\n      <button class=\"ae-button ae-button--primary\" @click.stop=\"controller.createPlannings()\">\n        <ae-icon class=\"fa fa-save\"></ae-icon>\n        Planungen erstellen\n      </button>\n    </div>\n  </template>\n</ae-dialog>","css":"","controller":"class Controller {\n  async init() {\n    this.FORMS = {\n      PaymentPlanning: '771536d9-af83-45ca-933d-37a445bbe3cc',\n      Planning: '6c501e3a-25c4-43c1-bd96-ec99333f5930',\n    }\n    \n    locals.paymentPlanning = null\n\n    locals.paymentPlanning = await Aeppic.new(this.FORMS.PaymentPlanning, document)\n    locals.paymentPlanning.data.anzahl = 10\n  }\n\n  async createPlannings() {\n    if (!locals.paymentPlanning.data.abschlag || !locals.paymentPlanning.data.anzahl) {\n      return\n    }\n\n    locals.working = true \n\n    const contract = await Aeppic.edit(document)\n    contract.data.zahlungsmodalitaet = 'ZEIT'\n    Aeppic.save(contract)\n\n    contract.data.planungen.forEach(p => contract.removeReference('planungen', p))\n    contract.data.planungen = []\n\n    let lastDate = Aeppic.DateTime.fromISO(locals.paymentPlanning.data.datum).startOf('month')\n\n    const plannings = []\n\n    for (let i = 1; i <= locals.paymentPlanning.data.anzahl; i += 1) {\n      const planning = await Aeppic.new(this.FORMS.Planning, document)\n      plannings.push(planning)\n\n      contract.addReference('planungen', planning)\n\n      planning.data = {\n        datum: lastDate.toISO(),\n        prozent: locals.paymentPlanning.data.abschlag,\n        name: locals.paymentPlanning.data.beschreibung || ''\n      }\n\n\n      lastDate = lastDate.plus({ months: 1 })\n    }\n\n    Aeppic.saveAll([...plannings, contract])\n\n    $refs.planner.close()\n\n    locals.working = false\n  }\n} ","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2023-10-18T08:51:36+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2023-10-18T09:42:52+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936847,"ct":1721209936847,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"397911459ed8e5b5","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"cloneOf":{"id":"d309508a-1351-4141-a44e-5437ab25eeec","text":"number","v":"9cb505eff47fab61"},"created":{"at":"2026-03-12T12:44:32+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"ct":1773319472199,"data":{"cardinality":"BOTH","controller":"class Controller {\n  init() {\n    this.nonDecimalSymbolsRegex = /([^\\-\\+,0-9.])/g\n\n    locals.inputValue = getField()\n\n    // reformat dots as comma for german locale\n    if (Intl?.NumberFormat()?.resolvedOptions()?.locale === 'de') {\n      locals.inputValue = `${locals.inputValue}`.replace('.', ',')\n    }\n\n    locals.add = 0\n    locals.showAdd = false\n\n    if (fieldInfo.cardinality) {\n      locals.values = getField().map(num => {return { value: num }})\n    }\n  }  \n\n  add(force = false) {\n    if (force || !Number.isNaN(locals.add)) {\n      locals.values.push({ value: locals.add })\n      this.update()\n      locals.add = 0\n      $refs.add.focus()\n    }  \n  }\n\n  update() {\n    setField(locals.values.map(v => v.value).filter(v => v !== ''))\n  }\n\n  clean() {\n    locals.values = getField().map(num => {return { value: num }})\n  }\n\n  setField(value) {\n    // set inputValue twice to allow input element to \"remove\" invalid symbols\n    locals.inputValue = value\n    locals.inputValue = value.replace(this.nonDecimalSymbolsRegex, '')\n\n    const parsed = value ? Number.parseFloat(value.replace(this.nonDecimalSymbolsRegex, '').replace(',', '.')) : 0\n    const sanitizedValue = Number.isNaN(parsed) ? 0 : parsed\n\n    console.debug({ value, parsed, sanitizedValue })\n\n    setField(sanitizedValue)  \n  }\n\n  get displayValue() {\n    const field = getField()\n\n    console.debug({field})\n\n    return new Intl.NumberFormat('de-DE').format(field)\n  }\n}   ","css":"._CSS-new {\n  padding: var(--bp-input-content-padding);\n}\n\n._CSS-new__input {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n}","description":"","device":"ALL","label":"","limitTypes":false,"name":"percent","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<!-- TODO: implement actual percent logic. This is just a copy of number -->\n\n<pre v-if=\"params.debug\">\ninputValue {{ locals.inputValue  }}\nfield {{ field }}\ndisplayValue {{ controller.displayValue }}\n</pre>\n \n<!-- FIXME: pattern v-flag does not allow any hyphen to be matched. But it should?! -->\n<!-- pattern=\"^([0-9.,+\\-]+)\"  -->\n<div v-if=\"!fieldInfo.cardinality\">\n  <input  class=\"w-full border rounded-md py-1 px-2 flex gap-1 bg-white\"\n          type=\"text\"\n          inputmode=\"numeric\" \n          pattern=\"^([0-9.,+]+)\" \n          :value=\"locals.focus ? locals.inputValue : controller.displayValue\"\n          @input=\"controller.setField($event.target.value)\"\n          :required=\"fieldInfo.required\"\n          :readonly=\"params.readonly != null ? params.readonly : readonly\"\n          :aria-label=\"label\"\n          :step=\"params.step || '1'\"\n          @focus=\"locals.focus = true\"\n          @blur=\"locals.focus = false\"\n  >\n</div>\n<template v-else>\n  TODO!\n  <!-- <input type=\"number\" v-for=\"value of locals.values\" v-model.number=\"value.value\" style=\"width:100%\" :readonly=\"readonly\" :aria-label=\"label\"  @blur=\"controller.clean()\" @input=\"controller.update()\" autocomplete=\"off\">\n\n  <button v-if=\"!locals.showAdd\" class=\"ae-button\" @click.stop=\"locals.showAdd = true\">\n    <ae-icon class=\"fa fa-plus\"></ae-icon>\n  </button>\n  \n\n    <div class=\"_CSS-new\" v-if=\"locals.showAdd && !readonly && !params.noAdd\">\n      <h5>Neuer Eintrag</h5> \n      <div class=\"_CSS-new__input\">\n        \n        <input ref=\"add\" type=\"number\" style=\"flex: 1;\" @keypress.enter.prevent=\"controller.add()\" v-model.number=\"locals.add\">\n        <button class=\"ae-button\" @click.stop=\"controller.add()\">\n          <ae-icon class=\"fa fa-save\"></ae-icon>\n        </button>\n      <button v-if=\"locals.showAdd\" class=\"ae-button\" @click.stop=\"locals.showAdd = false\">\n        <ae-icon class=\"fa fa-times\"></ae-icon>\n      </button>\n      </div>\n    </div> -->\n</template>\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"23b9d878-8cb8-497a-a11c-370aab0e6636","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-03-12T12:45:16+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"originalCloneOf":{"id":"d309508a-1351-4141-a44e-5437ab25eeec","text":"number","v":"9cb505eff47fab61"},"p":"controls-folder","previous":{"v":"e10f7ac1d1282260"},"stamps":[],"t":1773319516744,"v":"cdc2a548eace9d94"},{"id":"028ddb7a-adc0-4867-a5ee-482e4ebd87d6","v":"1cb1a27dbe3e360d","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"phone","template":"<div class=\"_CSS-line\">\n\n  <input v-if=\"controller.isEditing\"\n          type=\"text\"\n          :value=\"field\"\n          ref=\"input\"\n          style=\"flex: 1\"\n          @input=\"controller.update($event)\"\n          class=\"_CSS-input\"\n          :required=\"fieldInfo.required\"\n  >\n\n  <div style=\"flex:1\" v-else class=\"ae-form-cell__input _CSS-value\">\n    <a class=\"ae-link\" :href=\"'tel:' + field\" :tcxhref=\"field\" target=\"_blank\">{{field}}</a>\n  </div>\n\n  <button v-if=\"!readonly\" class=\"ae-button ae-button--small _CSS-button\" :class=\"{ 'ae-button--tertiary': locals.editing }\" @click.stop=\"locals.editing = !locals.editing\"> \n    <ae-icon class=\"fa fa-edit fa-flip-horizontal\" v-if=\"locals.editing\"></ae-icon>\n    <ae-icon class=\"fa fa-edit\" v-else></ae-icon>\n  </button>\n</div>\n","css":"._CSS-line {\n  display: flex;\n  align-items: center;\n}\n\n._CSS-button {\n  position: absolute;\n  right: 1px;\n  top: 1px;\n} \n\n._CSS ._CSS-value,\n._CSS ._CSS-input {\n  padding-right: 40px;\n}","controller":"class Controller {\n  init() {\n    locals.editing = !getField()\n  }\n\n  get isEditing() {\n    return !readonly && locals.editing\n  }\n\n  update(event) {\n    const raw = event.target.value\n    const sanitized = raw.replace(/[^0-9|\\+]/g, '')\n    event.target.value = sanitized\n    setField(sanitized)\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-10-19T10:23:08+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-08-15T13:13:51+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936847,"ct":1721209936847,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"26bbea8c64db76a7","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-09-06T12:36:01+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936847,"data":{"cardinality":"BOTH","controller":"class Controller {\n  init() {\n  }\n\n  get field() {\n    const field = getField() \n\n    if (Array.isArray(field)) {\n      return field\n    }\n\n    if (!field) {\n      return []\n    }\n\n    return [field]\n  }\n\n  add() {\n    this.edit(null)\n\n    const field = getField()\n\n    if (Array.isArray(field)) {\n      field.push('')\n\n      setField(field)\n\n      this.edit(field.length - 1)\n      return \n    } \n\n    if (!field) {\n      // extranous whitespace will be trimmed on update\n      setField(' ') \n    }\n\n    this.edit(0)\n  }\n\n  edit(index) {\n    $refs.layout.$emit('focus-at-index', index)\n  }\n\n  update(args) {\n    const { index, value } = args\n\n    let field = getField()\n    \n    const sanitized = this.sanitizeValue(value)\n\n    if (Array.isArray(field)) { \n      if (sanitized) {\n        field.splice(index, 1, sanitized)\n      } else {\n        field.splice(index, 1, '') \n      }\n    } else {\n      field = sanitized\n    }\n\n \n    setField(field) \n  }\n\n  sanitizeValue(value) {\n    const raw = `${value}`.trim()\n    const sanitized = raw.replace(/[^0-9|\\+]/g, '')\n\n    return sanitized\n  }\n\n  remove(args) {\n    const { index } = args\n\n    let field = getField()\n\n    if (Array.isArray(field)) { \n      field.splice(index, 1) \n    } else {\n      field = ''\n    }\n\n    setField(field)\n\n    this.edit(null)\n  }\n}","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"phone","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<div v-if=\"params.simpleInput\" \n    class=\"border rounded-md flex gap-2 min-h-[2em]\"\n    :class=\"[validationErrors?.required && 'border-red-500']\"\n    data-ae-form-field-input\n>\n  <div class=\"flex items-center rounded-l-md border-r px-2 text-neutral-500 bg-white\" @click.stop>\n    <ae-icon class=\"far fa-phone fa-rotate-90\"></ae-icon>\n  </div> \n\n  <ae-control class=\"flex-1\" name=\"rd:text\" :document=\"document\" :field-name=\"fieldName\" :param-without-border=\"true\"\n              :param-value-sanitizer-function=\"(value) => controller.sanitizeValue(value)\"\n  ></ae-control>\n</div>\n<ae-layout  v-else \n          layout-id=\"866974ba-6e69-483b-8933-2ec6ac66ef1c\"\n          :param-entries=\"controller.field\"\n          :param-cardinality=\"fieldInfo.cardinality\"\n          param-icon=\"far fa-phone fa-rotate-90\"\n          @add=\"controller.add()\"\n          @remove=\"controller.remove($event)\"\n          @update=\"controller.update($event)\"\n          ref=\"layout\"\n          param-additional-link-attribute=\"tcxhref\"\n          param-href-prefix=\"tel:\"\n          :param-border-color=\"validationErrors?.required && 'border-red-500'\"\n></ae-layout>\n\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"73f79e6c-d9e7-43da-ae19-f03b97d789cf","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-05-19T11:24:30+00:00","backedBy":null,"by":"729fa027-7e7e-4b06-9602-f535c43824f0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"067a18dd37d7a37d"},"stamps":[],"t":1747653870269,"v":"2ff9cdd425ed4db8"},{"id":"02800bdf-3526-4dc3-94a6-f42cb082ed40","v":"1489af3052fa8f0a","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"photos","template":"<div class=\"_CSS-images\">\n  <ae-design\n    name=\"card\"\n    v-for=\"image in field\"\n    :key=\"image.id\"\n    :document-id=\"image.id\"\n    ></ae-design>\n  <div class=\"_CSS-add-image bp-button bp-button-icon\" @click=\"controller.showTakePicture()\">\n    <ae-icon class=\"fa fa-plus\"></ae-icon>\n  </div>\n</div>\n\n<ae-dialog :full-screen=\"true\" ref=\"takePhotoDialog\" :dialog-classes=\" { '_CSS-dialog': true }\">\n  <template v-slot:header=\"scope\">\n    <!-- <div class=\"_CSS-header\">\n      <div></div> <button class=\"bp-button bp-button--transparent\" @click=\"scope.close()\"><ae-icon class=\"fa fa-times\"></ae-icon></button>\n    </div> -->\n  </template>\n  <template v-slot:body>\n    <div class=\"_CSS-body\">\n      <vue-photos v-if=\"$refs.takePhotoDialog && $refs.takePhotoDialog.isOpen\"\n                  :key=\"locals.photosKey\"\n                  :allow-drawing=\"true\"\n                  @captured=\"controller.imagesCaptured($event)\"\n      ></vue-photos>\n    </div>\n  </template>\n</ae-dialog>\n","css":"._CSS {\n  min-height: 400px;\n  display: flex;\n}\n\n._CSS-container {\n  flex: 1;\n  position: relative;\n}\n\n._CSS-expandable.ae-expandable--expanded {\n  background-color: white;\n  max-height: none;\n}\n\n._CSS-images {\n  flex: 1;\n\n  display: grid;\n  grid-gap: 1rem;\n  grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));\n  grid-template-rows: repeat(auto-fill, 11rem);\n}\n\n._CSS-add-image {\n  height: 100%;\n  width: 100%;\n  font-size: 1.5rem;\n\n  border-radius: 2px;\n  box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.16), 0 0 1px 0 rgba(0, 0, 0, 0.16);\n  background-color: white;\n}\n\n._CSS-dialog .ae-dialog__header {\n  padding: 0;\n}\n\n._CSS-body {\n  flex: 1;\n  position: relative;\n  margin: 0 !important;\n}\n\n._CSS-header {\n  display: flex;\n  justify-content: space-between;\n}","controller":"class Controller {\n  init() {\n    locals.photosKey = 0\n  }\n\n\n  showTakePicture() {\n    locals.photosKey += 1\n    $refs.takePhotoDialog.show()\n  }\n\n  closeTakePicture() {\n    $refs.takePhotoDialog.close()\n  }\n\n  async imagesCaptured(images) {\n    console.log('captured images: ', images)\n    for (const image of images) {\n      const doc = await Aeppic.new('c3fcbfa9-f948-483b-a706-afcdf2ca1fa6', document.id)\n      doc.setFile(this._mountableParamToStringPropertyName(params.bild), image.data, {\n        type: image.type\n      })\n      \n      if (image.overlay) {\n        doc.setFile(this._mountableParamToStringPropertyName(params.overlay), image.overlay.data, {\n          type: image.type\n        })\n      }\n\n      Aeppic.save(doc)\n      document.addReference(fieldName, doc)\n    }\n\n    this.closeTakePicture()\n  }\n\n  _mountableParamToStringPropertyName(param) {\n    return param.replace(/\\[|\\]/g,'')\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"3b1f179a-de50-456e-9fda-798e572e4f99","v":"d1b1a57d7ac04d89","text":"Photos"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-04-05T13:31:01+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936847,"ct":1721209936847,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"05d164fa4a3e0a68","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"7c19ad9d-41f9-4578-ad29-ca401b25ccc9","v":"25c658dbfe28dae1","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"pings","template":"<div class=\"_CSS-heading\">\n  <span style=\"font-size: 1.5em; margin-block-start: 0.83em; margin-block-end: 0.83em;font-weight:bold;\">Pings</span>\n  <button class=\"ae-button\" title=\"Ping senden\" @click.stop=\"$refs.sendPing.$emit('show')\">\n  <ae-icon class=\"fad fa-comment-plus\"></ae-icon> \n</button>\n</div>\n\n\n\n<ul class=\"_CSS-list-unstyled\">\n  <li v-for=\"(pings, receiver) in locals.pingsByReceiver\" class=\"_CSS-ping\">\n    <div class=\"_CSS-ping__who\">\n      <ae-design name=\"avatar--resizes-with-font\" :document-id=\"receiver\" style=\"margin-right: var(--ae-size-2);font-size:var(--ae-font-size-12);\"></ae-design>\n      <template v-if=\"receiver === Aeppic.Account.id\">\n        <span style=\"margin-top: var(--ae-size-2);width:100%;text-align:center;\">\n          Ich\n        </span>\n      </template>\n      <template v-else>\n        <!-- <ae-design name=\"avatar--resizes-with-font\" :document-id=\"receiver\" style=\"margin-right: var(--ae-size-2);font-size:var(--ae-font-size-12);\"></ae-design> -->\n        <ae-design name=\"profile-title\" :document-id=\"receiver\" style=\"margin-top: var(--ae-size-2);width:100%;\"></ae-design>\n      </template>\n    </div>\n    <div class=\"_CSS-ping__right\">\n      <ae-list list-id=\"simple-list\" :documents=\"pings\" param-design=\"ping\"></ae-list>\n      <div class=\"_CSS-new bp-card bp-card--bordered\" v-if=\"!controller.readonly\">\n        <input class=\"_CSS-new__input\" type=\"text\" ref=\"input\" v-if=\"locals.newPing.receiver === receiver\" v-model=\"locals.newPing.editableDocument.data.content\" v-focus @keydown.enter.prevent=\"controller.savePing(receiver)\">\n        <input class=\"_CSS-new__input\" v-else ref=\"inputPlaceholder\" type=\"text\" @click.stop.prevent=\"controller.newPing(receiver)\" @focus=\"controller.newPing(receiver)\">\n        <button class=\"ae-button ae-button--secondary\" :disabled=\"locals.newPing.receiver !== receiver\" @click.stop=\"controller.savePing(receiver)\">\n          <ae-icon class=\"fa fa-comment-plus\"></ae-icon> Senden\n        </button>\n      </div>\n    </div>\n  </li>\n</ul>\n\n\n<ae-layout layout-id=\"f4d43a5a-2177-49cc-9a2b-bec78a0ab12a\" :param-document=\"document\" ref=\"sendPing\"></ae-layout>\n\n","css":"._CSS-heading {\n  margin-top: 4em;\n}\n\n._CSS-ping {\n  background: var(--ae-ui-card-background);\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  border-radius: var(--ae-border-radius);\n\n  display: flex;\n  width: 100%;\n}\n\n._CSS-ping + ._CSS-ping {\n  margin-top: 4px;\n}\n\n._CSS-content {\n  padding: 8px;\n}\n\n._CSS-list-unstyled {\n  list-style:none;\n  margin-left:0;\n  padding-left:0;\n}\n\n._CSS-date-modified-at {\n  display: flex;\n  justify-content: flex-end;\n  font-size: 0.8em;\n}\n\n._CSS-add-ping {\n}\n\n._CSS-ping__who {\n  background: var(--ae-background);\n  border-radius: var(--ae-border-radius) 0 0 var(--ae-border-radius);\n  border-right: var(--ae-border-width) solid var(--ae-border-color);\n\n  display: flex;\n  align-items: center;\n  xjustify-content: center;\n  flex-flow: column;\n\n  padding: var(--ae-size-5) var(--ae-size-3) var(--ae-size-3) var(--ae-size-3);\n  margin-right: var(--ae-size-2);\n  width: 120px;\n}\n\n._CSS-ping__right {\n  flex: 1;\n  padding: var(--ae-size-2);\n  overflow: hidden;\n}\n\n._CSS-new {\n  margin-top: var(--ae-size-4);\n  background: var(--ae-background);\n}\n\n._CSS-new__input {\n  margin: 0 var(--ae-size-2);\n  flex: 1;\n}","controller":"class Controller {\n  async init() {\n    this.FORMS = {\n      Notification: '6d164aff-df9b-470d-9632-cfb6f5892509',\n      Ping: '97902782-7315-4837-94fc-8e080c6cfbf3',\n      NotificationRequest: 'ddaa6921-770e-4a73-b8a4-b4a99abed24b',\n    }\n    // this.publishStampId = '04a939a8-6732-4f94-ad75-6f17ac417136'\n\n    // locals.newComment = null\n    locals.newPing = {\n      receiver: '',\n      editableDocument: null\n    }\n    // locals.pings = []\n    locals.pingsByReceiver = {}\n    locals.canCreate = false\n    locals.canEdit = false\n    \n    Aeppic.subscribe(`p:${Aeppic.Account.id} (f.id:${this.FORMS.Notification} OR f.id:${this.FORMS.NotificationRequest}) data.category:${this.FORMS.Ping} data.document.id:${document.id}`, { sort: 'created.at' }, (documents) => this._refresh(documents))\n      \n    // await this.newComment()\n \n    if (!Aeppic.Account) {\n      return\n    } \n    \n    // const canEdit = await Aeppic.canEdit(document)\n\n    // if (Aeppic.Account.data.isGroupAccount) {\n    //   const groupAccountCanCreate = await Aeppic.Preferences.getSetting('bizpin:comments:groupAccountAllowCreate')\n    //   locals.canCreate = (canEdit && groupAccountCanCreate)\n    // } else {\n    //   locals.canCreate = (canEdit)\n    // }\n  }\n\n  async _refresh(pings) {\n    const pingsByReceiver = {}\n\n    for (const ping of pings) {\n      const receivingPing = ping.f.id === this.FORMS.Notification\n \n      if (receivingPing) {\n        if (!pingsByReceiver[ping.data.who.id]) {\n          pingsByReceiver[ping.data.who.id] = []\n        }\n\n        pingsByReceiver[ping.data.who.id].push(ping)\n      } else {\n        for (const account of ping.data.accounts) {\n          // pings to own are handled as 'normal' notification\n          const isPingToMyself = account.id === Aeppic.Account.id\n          if (isPingToMyself) {\n            continue\n          }\n\n          if (!pingsByReceiver[account.id]) {\n            pingsByReceiver[account.id] = []\n          }\n\n          pingsByReceiver[account.id].push(ping)\n        }\n      }\n    }\n\n    locals.pingsByReceiver = pingsByReceiver\n  }\n\n  async newPing(receiver) {\n    const ping = await Aeppic.new(this.FORMS.NotificationRequest, Aeppic.Account.id)\n    const form = await Aeppic.get(document.f.id)\n    const app = await Aeppic.asReference(form.a[1])\n\n    const receivingAccount = await Aeppic.asReference(receiver)\n\n    ping.data = {\n      app,\n      category: await Aeppic.asReference(this.FORMS.Ping),\n      content: '',\n      accounts: [receivingAccount],\n      document: await Aeppic.asReference(document),\n      who: await Aeppic.asReference(Aeppic.Account)\n    }\n\n    locals.newPing = {\n      receiver,\n      editableDocument: ping\n    }\n  }\n\n  async savePing(receiver) {\n    if (locals.newPing.receiver !== receiver) {\n      return\n    }\n\n    Aeppic.save(locals.newPing.editableDocument)\n\n    locals.newPing = {\n      receiver: '',\n      editableDocument: null\n    }\n  }\n\n \n  async newComment() {\n    locals.newComment = await Aeppic.new('cf155969-ede1-43a4-9b10-3a697b19db91', document.id)\n    Aeppic.commit(locals.newComment)\n  }\n\n  async saveComment() {\n    const filledOut = !!locals.newComment.data.content || locals.newComment.data.attachments.length\n\n    if (!filledOut) {\n      return\n    }\n\n    Aeppic.save(locals.newComment)\n    \n    const comment = await Aeppic.get(locals.newComment)\n    Aeppic.stamp(comment, this.publishStampId)\n\n    await this.newComment()\n  }\n\n  get readonly() {\n    if (params.readonly === true) {\n      return true\n    }\n\n    if (typeof readonly === 'boolean') {\n      return readonly\n    }\n\n    return false\n  }\n\n  async focus() {\n    if (!await Aeppic.get(locals.newComment)) {\n      Aeppic.save(locals.newComment)\n    }\n  }\n\n  displayComment(comment) {\n    const commentHasNewForm = 'attachments' in comment.data\n\n    if (commentHasNewForm) {\n      return comment.stamps && comment.stamps.find(s => s.type && s.type.id === this.publishStampId)\n    }\n\n    return true\n  }\n\n  destroy() {\n    // Aeppic.delete(locals.newComment)\n  }\n}\n","parameters":[],"standalone":"REQUIRED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-08-27T13:49:25+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2020-08-28T14:54:01+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936847,"ct":1721209936847,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"e0cb08613d9d3315","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"b05fbb17-2cef-4146-b640-1384b6f9709d","v":"5ede8c6cbec72b63","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"plaintext","template":"<ae-expandable>\n  <monaco-editor style=\"min-height:40em\" v-model=\"field\" :theme=\"locals.theme\" :readonly=\"readonly\" language=\"\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>","css":"","controller":"class Controller {\n  async init() {\n    locals.theme = (await Aeppic.Preferences.getSetting('aeppic:developer:codeTheme')) || 'vs-dark'\n  }\n  async getHoverContent(word) {\n    const infos = await Aeppic.find(`id:${word}`)\n    if (!infos || !infos.length) {\n      return []\n    }\n\n    const doc = infos[0]\n\n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0a99614c3b79c920","text":"Monaco Editor"}],"device":"ALL","label":"","description":"A control to display plain text"},"locks":[],"created":{"at":"2019-03-20T10:05:06+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936847,"ct":1721209936847,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"d8ff202d2cf60882","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-08-25T13:33:25+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936847,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    this.FORMS = {\n      AbzurechnendePosition: '651d8603-6d29-4534-be59-4449387e857c',\n      ErfassteZeit: 'f9279010-1935-4139-abbb-82d928aa09dc',\n      CostEstimate: 'bcb673e1-40de-4c35-941a-fc13a1e99c49',\n      Statistic: 'e3ffa807-5e3c-47f8-aa86-c3940236e3df',\n    }\n\n    this.NEW_COMMANDS = {\n      PositionDraft: '227d9734-e778-4811-ada4-d352b7a0e0aa',\n    }\n\n    this.subscription = null\n\n    locals.documents = []\n    locals.newPosition = null\n    locals.costEstimate = null\n    locals.trackedTimeMaximum = 20\n\n    locals.canEditCostEstimate = false\n\n    // NOTE: this disables timetracking display completly until an user manually chooses to display it\n    //        Workaround for slow loading times with energy customers \n\n    const accountsToExclude = [\n      'd4fe4f6d-a85f-4cd6-bce9-4a2f30101cc9',\n      '03634097-fe19-444e-8240-3e3f15beeb79',\n      'd2240783-b416-4d5f-ac3e-69e36db1d155',\n    ]\n\n    locals.showTimeTracking = true //accountsToExclude.includes(Aeppic.Account?.id) ? true : false\n\n    // NOTE: This query is strange. (p AND (f.id OR f.id)) ((p f.id) OR (p f.id)) area better at different times.\n    locals.query = `p:${document.id} (f.id:${this.FORMS.AbzurechnendePosition} OR p:${document.id} f.id:${this.FORMS.ErfassteZeit})`\n    locals.queryOptions = { sort: { fields: ['data.datum', 'created.at'], descending: true }, size: 101 }\n\n    if (locals.showTimeTracking) {\n      this.loadTimetracking()\n    }\n \n    await Aeppic.nextRenderPass()\n\n    locals.canEditCostEstimate = await Aeppic.Commands.execute('845f8c83-d859-4d43-9043-9b9395b37939', document)\n\n    if (locals.canEditCostEstimate?.value?.ok === true) {\n      locals.costEstimate = await this._getCostEsimate(document)\n    }\n  }\n\n  loadTimetracking() {\n    locals.showTimeTracking = true\n\n    this.subscription = Aeppic.updateSubscription(this.subscription, locals.query, locals.queryOptions, (documents) => {\n      locals.documents = documents\n    }) \n  }\n\n  onPageSize(newPageSize) {\n    locals.currentPageSize = newPageSize\n\n    if (locals.currentPageSize >= locals.currentResultSize) {\n      locals.queryOptions.size += 100\n\n      this.subscription = Aeppic.updateSubscription(this.subscription, locals.query, locals.queryOptions, (documents) => {\n        locals.documents = documents\n      }) \n    }\n  }\n\n  async _getCostEsimate(doc) {\n    if (typeof params.costEstimate === 'string') {\n      return await Aeppic.get(doc.data[params.costEstimate])\n    } else {\n      return await Aeppic.findOne(`p:${doc.id} f.id:${this.FORMS.CostEstimate}`)\n    }\n  }\n\n  async show() {\n    locals.newPosition = null\n\n    const positionDraftResult = await Aeppic.Commands.execute(this.NEW_COMMANDS.PositionDraft, document, {\n      Account: Aeppic.Account,\n      ProfilePage: Aeppic.ProfilePage\n    })\n\n    if (!positionDraftResult.ok || positionDraftResult.error) {\n      console.error('Error creating tracked time', positionDraftResult)\n      return\n    }\n\n    locals.newPosition = positionDraftResult.value\n\n    locals.newPosition.data.abrechenbar = true\n    locals.newPosition.data.menge = 1\n\n    $refs.dialogNew.show()\n  }\n\n\n  async save() {\n    if (!locals.newPosition) {\n      return\n    }\n\n    const data = locals.newPosition.data\n\n    if (data.typ === 'ZEIT') {\n      data.menge = 1\n    } else if (data.typ === 'ENTFERNUNG') {\n      // data.dauer = {}\n    } else if (data.typ === 'MATERIAL') {\n      // data.dauer = {}\n    }\n\n    data.titel = data.titel.trim()\n    Aeppic.save(locals.newPosition)\n\n    this.reset()\n\n    $refs.dialogNew.close()\n  }\n\n  reset() {\n    locals.newPosition = null\n  }\n\n  async showCostEstimate() {\n    if (!locals.canEditCostEstimate) {\n      return\n    }\n\n    const existing = await this._getCostEsimate(document) \n\n    if (existing) {\n      locals.costEstimate = await Aeppic.edit(existing)\n    } else {\n      const costEstimateLocks = (await Aeppic.getAll(await Aeppic.Preferences.getSettingForDocument(`apps:0924db0f-7095-4b51-8966-bad1e45d68ec:costEstimateLocks`, document.a[0]))).filter(Boolean)\n\n      locals.costEstimate = await Aeppic.new(this.FORMS.CostEstimate, document)\n      costEstimateLocks.forEach(lock => locals.costEstimate.addLock(lock))\n\n      // check if costEstimate should be added to parent document\n      if (typeof params.costEstimate === 'string') {\n        document.addReference(params.costEstimate, locals.costEstimate)\n      }\n    }\n\n    $refs.popupCostEstimate.show({ target: $refs.buttonCostEstimate.$el })\n  }\n\n  async mergeTime(doc, addOrRemoveIndexByNumber) {\n    const times = locals.documents.filter(d => d.f.id === this.FORMS.ErfassteZeit)\n    const index = times.findIndex(d => d.id === doc.id)\n\n    const ensuredDoc = times[index]\n\n    const otherTimeDocIndex = index + addOrRemoveIndexByNumber\n\n    if (index >= 0 && otherTimeDocIndex >= 0 && otherTimeDocIndex < times.length) {\n      const otherTimeDoc = await Aeppic.edit(times[otherTimeDocIndex])\n      console.log('before', otherTimeDoc.data.dauer)\n      otherTimeDoc.data.dauer = Aeppic.DateTimeDuration.fromObject(otherTimeDoc.data.dauer).plus(ensuredDoc.data.dauer).toObject()\n      console.log('after', otherTimeDoc.data.dauer)\n\n      Aeppic.save(otherTimeDoc)\n      Aeppic.delete(ensuredDoc)\n    } \n  } \n} ","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"position-draft","namespace":"rd","packages":[],"parameters":[],"standalone":"REQUIRED","template":"<section class=\"h-full flex justify-between flex-col gap-1\" :data-readonly=\"locals.readonly\">\n  <div class=\"flex justify-between\">\n    <label\n      :for=\"id('input_id_for_label')\"\n      class=\"font-medium flex-1 text-neutral-600 dark:text-neutral-200 flex items-center gap-1\"\n    >\n      <span> Leistungen & Zeiterfassung </span>\n    </label>\n\n    <!-- <ae-layout\n      layout-id=\"e0ab58b1-614c-495a-94ec-288c623b4d48\"\n      :param-document=\"document\"\n      :param-control-params=\"params\"\n      :param-invoiced-documents-length=\"locals.invoicedDocuments.length\"\n      @filter=\"locals.fixedControlFilter = $event\"\n      class=\"mx-1\"\n    ></ae-layout> -->\n  </div>\n\n  <section class=\"border rounded-md p-2 divide-y flex flex-col gap-2 w-full\">\n    <section class=\"flex justify-between gap-10\">\n      <div class=\"flex items-center gap-2\">\n        <ae-layout\n          layout-id=\"ca6783b2-ce5e-4115-a28e-da2c8cdbf38e\"\n          v-if=\"!locals.readonly\"\n          :param-document-id=\"document.id\"\n        ></ae-layout>\n\n        <ae-layout\n          layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n          @selected=\"controller.show()\"\n          param-icon=\"far fa-plus\"\n          param-title=\"Neue Leistung erfassen\"\n          param-border=\"border\"\n        ></ae-layout>\n\n        <ae-layout\n          v-if=\"locals.canEditCostEstimate\"\n          layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n          param-icon=\"far fa-euro-sign\"\n          param-title=\"Kostenschätzung\"\n          param-border=\"border border-neutral-200\"\n          @selected=\"controller.showCostEstimate()\"\n          :param-text=\"locals.costEstimate ? locals.costEstimate.data.cost.amount : ''\"\n          ref=\"buttonCostEstimate\"\n        ></ae-layout>\n\n        <ae-popup v-if=\"locals.canEditCostEstimate\" ref=\"popupCostEstimate\" @close=\"Aeppic.save(locals.costEstimate)\">\n          <div data-test=\"popup_cost-estimate\">\n            <ae-form\n              :document=\"locals.costEstimate\"\n              layout-id=\"efdb156e-9350-4cb3-8e00-4a69adec691b\"\n              :auto-save=\"true\"\n              :auto-save-after=\"1000\"\n            ></ae-form>\n          </div>\n        </ae-popup>\n      </div>\n\n      <ae-design\n        v-if=\"locals.showTimeTracking\"\n        name=\"rd:timetracking-progress\"\n        :document=\"document\"\n        class=\"flex-1\"\n        :params=\"{ costEstimateFieldName: params.costEstimate, subscribe: true }\"\n      ></ae-design>\n      <div v-else>\n        Zeiterfassung nicht geladen\n      </div>\n    </section>\n\n    <section v-if=\"locals.showTimeTracking\" class=\"pt-2\">\n      <ae-list\n        v-if=\"locals.documents.length\"\n        list-id=\"922e8251-6e60-45a4-9df6-3d434363ac01\"\n        :documents=\"locals.documents\"\n        param-design=\"rd:card-inner\"\n        :param-design-params=\"{ parentId: document.id }\"\n        @selected=\"Aeppic.drillInto($event)\"\n        @merge-up=\"controller.mergeTime($event, -1)\"\n        @merge-down=\"controller.mergeTime($event, 1)\"\n        :param-page-size=\"20\"\n        @size=\"locals.currentResultSize = $event\"\n        @page-size=\"controller.onPageSize($event)\"\n      ></ae-list>\n      <div v-else class=\"py-2 text-neutral-500 text-sm\">Keine Zeiten oder Positionen erfasst</div>\n    </section>\n    <section v-else> \n      <ae-layout\n        layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n        param-icon=\"far fa-cloud-arrow-down\"\n        param-text=\"Zeiterfassung laden/anzeigen\"\n        :param-primary=\"true\"\n        @selected=\"controller.loadTimetracking()\"\n      ></ae-layout>\n    </section>\n  </section>\n</section>\n\n<ae-dialog ref=\"dialogNew\" @close=\"controller.reset()\" :backdrop-close=\"false\" :dialog-size=\"{ height: '50vh' }\">\n  <template #header=\"scope\">\n    <div class=\"flex justify-between items-center\">\n      <span> Neue Leistung erfassen </span>\n      <button @click.stop=\"scope.close()\" class=\"ae-button\">\n        <ae-icon class=\"fa fa-times\"></ae-icon>\n      </button>\n    </div>\n  </template>\n  <template #body>\n    <ae-form\n      v-if=\"locals.newPosition\"\n      :document=\"locals.newPosition\"\n      layout-id=\"efdb156e-9350-4cb3-8e00-4a69adec691b\"\n      @data-changed=\"Aeppic.commit(locals.newPosition)\"\n    ></ae-form>\n  </template>\n  <template #commands=\"scope\">\n    <button @click.stop=\"scope.close()\" class=\"ae-button\">Abbrechen</button>\n    <button\n      class=\"ae-button ae-button--primary\"\n      @click.stop=\"controller.save()\"\n      :disabled=\"!locals.newPosition || !locals.newPosition.data.titel\"\n    >\n      Speichern\n    </button>\n  </template>\n</ae-dialog>\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"9842fc35-b4cb-4148-8c28-10c472f1d1d6","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-05-05T12:06:30+00:00","backedBy":"","by":"b7485498-f1f3-4ab1-87d1-9cb63ea890c8","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"0bdb1b1b6e8c53ad"},"stamps":[],"t":1777982790890,"v":"dd593ce94f3b9877"},{"id":"f27e8108-d22d-4e3f-b22d-c7758460e9e0","v":"078808d092a4cfef","previous":{"v":"4c68002d9114adcf"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"core","name":"position-draft","template":"<ae-control v-if=\"Aeppic.LocalStorage.getValue('appLayoutId')\" name=\"rd:position-draft\" :document=\"document\" :params=\"params\"></ae-control>\n<ae-control v-else name=\"core-rd:position-draft\" :document=\"document\" :params=\"params\"></ae-control> ","css":"","controller":"","parameters":[],"standalone":"REQUIRED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2023-08-23T14:28:01+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2024-08-21T11:53:36+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1724241216840,"ct":1721209936847,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"9a538386-8497-48c0-b80b-5fcae6ee0605","v":"914c144fa942c8eb","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"core","name":"positions-copy","template":"<button class=\"ae-button ae-button--secondary\" v-if=\"!readonly\" @click.stop=\"controller.show()\">\n  <ae-icon class=\"fa fa-copy\"></ae-icon>\n  <span>Erfasste Positionen übernehmen</span>\n</button>\n\n<ae-dialog ref=\"dialog\" :dialog-size=\"{ width:'var(--ae-dialog__width--big)' }\">\n  <template #header=\"scope\">\n    <div class=\"_CSS-dialog__header\">\n      <span>Zu kopierende Positionen auswählen</span>\n      <button class=\"ae-button\" @click.stop=\"scope.close()\">\n        <ae-icon class=\"fa fa-times\"></ae-icon>\n      </button>\n    </div>\n  </template>\n  \n  <template #body>\n    <div class=\"_CSS-entries\">\n      <div class=\"_CSS-entry bp-card bp-card--bordered\" :class=\"{ '_CSS-entry--inactive': !entry.active }\" v-for=\"entry of locals.entries\">\n        <div class=\"_CSS-entry__title\" :title=\"entry.noPositonsInAngebot ? 'Keine Positionen dieses Typs im Angebot gefunden' : ''\">\n          <ae-design name=\"ref-card-title\" :document=\"entry.position\"></ae-design>\n          <div style=\"flex: 1;\"></div>\n          <ae-control name=\"core:positions-select\" :document=\"entry.position\" field-name=\"kostenProEinheit\" :param-position-draft=\"entry.draft\" @no-positions=\"{ entry.active = false; entry.noPositonsInAngebot = true; }\"></ae-control>\n\n          <button class=\"ae-button ae-button--small\" @click.stop=\"entry.active = !entry.active\" :disabled=\"entry.noPositonsInAngebot\"> \n            <ae-icon class=\"fa\" :class=\"[ entry.active ? 'fa-times': 'fa-plus']\"></ae-icon>\n          </button>\n        </div>\n        <div class=\"_CSS-entry__path\">\n          <ae-design name=\"path\" :document=\"entry.draft\"></ae-design>\n          <!-- <button class=\"ae-button ae-button--small\" @click.stop=\"Aeppic.drillInto(entry.draft)\">\n            Entwurf bearbeiten\n          </button> -->\n        </div>\n      </div>\n    </div>\n  </template>\n  <template #commands=\"scope\">\n    <button class=\"ae-button\" @click.stop=\"scope.close()\">Abbrechen</button>\n\n    <button class=\"ae-button ae-button--primary\" @click.stop=\"controller.copy()\" :disabled=\"locals.entries.every(e => !e.active)\">\n    Positionen kopieren\n    </button>\n  </template>\n</ae-dialog> ","css":"._CSS-entry {\n  display: flex;\n  flex-flow: column;\n  align-items: flex-start;\n}\n\n._CSS-entry__title {\n  width: 100%;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n\n._CSS-entry__path {\n  width: 100%;\n  overflow: auto;\n  opacity: var(--ae-opacity-1);\n  color: var(--ae-background-700);\n}\n\n._CSS-entry--inactive {\n  opacity: var(--ae-opacity-2);\n  text-decoration: line-through;\n}\n\n._CSS-dialog__header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}","controller":"class Controller {\n  async init() {\n    this.FORMS = {\n      Kunde: '8fc08287-dbcc-4e41-9d5d-28e4cd828943',\n      PositionRechnung: 'a40633b4-c769-4457-a267-830738d4b549',\n      Rechnung: 'e24202d4-aa6f-4ea9-933c-5c1be7e38556',\n      PositionsEntwurf: '651d8603-6d29-4534-be59-4449387e857c',\n      ErfassteZeit: 'f9279010-1935-4139-abbb-82d928aa09dc'\n    }\n\n    locals.positions = []\n    locals.entries = []\n  }\n\n  async show() {\n    \n    await this._update()\n\n    $refs.dialog.show()\n  }\n\n  async _update() {\n    locals.entries = []\n\n    const customer = (await Aeppic.Commands.execute('48686167-8ada-4964-bab1-370e377c9a71', document)).value\n    const drafts = await Aeppic.find(`a:${customer.data.project.id} (f.id:${this.FORMS.PositionsEntwurf} OR f.id:${this.FORMS.ErfassteZeit}) data.abgerechnet:false`, { sort: 'data.datum,p,data.name', size: 999 }) \n\n    const positions = []\n\n    await Promise.all(\n      drafts.map(async (draft) => {\n        const position = await Aeppic.new(this.FORMS.PositionRechnung, document)\n        const parent = await Aeppic.get(draft.p)\n\n        // DECPRECATED: draft.data.tag\n        position.data.datum = draft.data.datum || draft.data.tag\n\n        const name = draft.data.titel || draft.data.name || ''\n\n        if (parent.data.name) {\n          if (name) {\n            position.data.name = `${parent.data.name} (${name})`\n          } else {\n            position.data.name = parent.data.name\n          }\n        } else {\n          position.data.name = name\n        }\n\n        position.data.dauer = draft.data.dauer\n        position.data.abrechenbar = ('abrechenbar' in draft.data) ? draft.data.abrechenbar : true\n\n        if (draft.f.id === this.FORMS.ErfassteZeit) {\n          position.data.typ = 'ZEIT'\n          position.data.menge = 1\n        } else {\n          position.data.typ = draft.data.typ\n          position.data.menge = draft.data.menge\n        }\nconsole.log(draft.data, position.data)\n        // update calculated fields, if any\n        Aeppic.commit(position)\n\n        positions.push({\n          active: true,\n          noPositonsInAngebot: false,\n          position,\n          draft,\n        })\n      })\n    )\n \n    locals.entries = positions\n  }\n\n  async copy() {\n    for (const entry of locals.entries) {\n      if (!entry.active) {\n        continue\n      }\n\n\n      const editableDraft = await Aeppic.edit(entry.draft)\n      editableDraft.addReference('abrechnungsPosition', entry.position)\n      editableDraft.data.abgerechnet = true\n\n      Aeppic.save(entry.position)\n      Aeppic.save(editableDraft)\n\n      document.addReference(fieldName, entry.position)\n    }\n\n    // FIXME: Save document to ensure consistency with the cleared drafts. This should be changed, once a concept of save-context is there\n    Aeppic.save(document)\n\n    $refs.dialog.close()\n  }\n}  \n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-10-12T12:04:25+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-10-17T13:25:12+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936847,"ct":1721209936847,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"c49d2aa3aa484c19","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"104d8a2d-02f1-4f6d-8226-2c30c8dc5cea","v":"3cf8400b2ec467ae","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"core","name":"positions-select","template":"<select @input=\"controller.addPositionToNewPosition($event.target.value)\" class=\"_CSS-select\">\n  <option v-for=\"(position, index) of locals.positions\" :value=\"position.id\">\n    <ae-design name=\"record-title\" :document=\"position\"></ae-design> \n  </option>\n</select>\n","css":"._CSS-select {\n  width: 100%;\n}","controller":"class Controller {\n  async init() {\n    this.FORMS = {\n      PositionAngebot: '8d1804b0-c5c6-4acf-a15f-1b02872e792e',\n      ErfassteZeit: 'f9279010-1935-4139-abbb-82d928aa09dc',\n    }\n\n    locals.positions = []\n\n    if (!params.positionDraft) {\n      throw new Error('Provide params.positionDraft')\n      return\n    }\n\n    try {\n      const rechnung = await Aeppic.get(document.p)\n      const angebot = await Aeppic.get(rechnung.data.angebot)\n      const allPositions = await Aeppic.getAll(angebot.data.positionen)\n      locals.positions = allPositions.filter(p => p && p.f.id === this.FORMS.PositionAngebot && p.data.typ === document.data.typ)\n\n      if (locals.positions.length) {\n        this.addPositionToNewPosition(locals.positions[0].id)\n      } else {\n        $emit('no-positions')\n      }\n    } catch (e) {\n      $emit('no-positions')\n    }\n  }\n\n  async addPositionToNewPosition(id) {\n    if (!id) {\n      // TODO: the field should accept a number I hope\n      document.data.kostenProEinheit = 0\n      return\n    }\n\n    const selectedPosition = await Aeppic.get(id)\n    document.data.kostenProEinheit = selectedPosition.data.kostenProEinheit\n  \n    if (selectedPosition.data.typ === 'ZEIT' || selectedPosition.data.typ === 'SOCKEL_PLUS_VARIABLE') {\n      const minutesOfAngebot = Aeppic.DateTimeDuration.fromObject(selectedPosition.data.dauer).shiftTo('minutes').minutes * selectedPosition.data.menge\n      const minutesOfNew = Aeppic.DateTimeDuration.fromObject(params.positionDraft.data.dauer).shiftTo('minutes').minutes\n\n      document.data.menge = minutesOfNew / minutesOfAngebot\n      document.data.dauer = selectedPosition.data.dauer\n    } else if (selectedPosition.data.typ === 'ENTFERNUNG') {\n      document.data.menge = params.positionDraft.data.menge / selectedPosition.data.menge\n    }\n\n    Aeppic.commit(document)\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-10-27T10:19:54+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2021-07-12T12:55:54+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936848,"ct":1721209936848,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"93705dcfefbf4823","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"40b3b8b7-bb37-470e-bd7b-315610401aa2","v":"1fee0c525de7b4b7","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"preview","template":"<!-- <div class=\"_CSS-row\"> -->\n  <h3>Temporary overwrite data</h3>\n  <monaco-editor  style=\"min-height:20em;flex: 1;\" \n                  v-model=\"locals.overwriteData\"\n                  language=\"json\">\n  </monaco-editor>\n  <h3>Preview</h3>\n  <ae-design  name=\"preview\" \n              style=\"flex:1;min-height:60em;\"\n              :document=\"document\"\n              :param-field-name=\"fieldName\"\n              :param-view-data=\"controller.viewData\"\n              :param-warn=\"true\"\n              :watch=\"false\"\n  ></ae-design>\n<!-- </div> -->","css":"._CSS {\n  flex: 1;\n  display: flex;\n  flex-flow: column;\n}\n\n._CSS-row {\n  flex: 1;\n  display: flex;\n}\n","controller":"class Controller {\n  init() {\n    locals.overwriteData = ''\n  }\n\n  get viewData() {\n    const overwriteDataJson = locals.overwriteData ? JSON.parse(locals.overwriteData) : {}\n\n    if (params.dataField) {\n      const dataObject = typeof params.dataField === 'object' ? document.data[params.dataField] : JSON.parse(document.data[params.dataField])\n      return Object.assign({}, dataObject, overwriteDataJson)\n    }\n\n    return Object.assign({}, overwriteDataJson)\n  }\n}\n\n\n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"4c5212f15c5d6eb5","text":"Monaco Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-06-07T11:23:35+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-10-13T10:12:08+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936848,"ct":1721209936848,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"3e3f887b88975210","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"06d6980a-fb86-43eb-a420-4634bfa8dc52","v":"273607cb841c1447","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"tasks-energieberater","name":"quick-add","template":"<button class=\"ae-button\" @click.stop=\"locals.counter += 1 && $refs.dialog.show()\">\n  <ae-icon class=\"fa fa-layer-plus\"></ae-icon>\n  Neue Aufgaben\n</button>\n\n<ae-dialog ref=\"dialog\" :backdrop-close=\"false\" :full-screen=\"Aeppic.Device === 'Phone'\">\n  <template v-slot:header=\"scope\">\n    <div class=\"_CSS-header\">\n      <span>Mehrere Aufgaben erstellen</span>\n      <button class=\"ae-button\" @click=\"scope.close()\">\n        <ae-icon class=\"fa fa-times\"></ae-icon>\n      </button>\n    </div>\n  </template>\n  <template v-slot:body=\"scope\">\n    <ae-layout :key=\"locals.counter\" layout-id=\"da1da77a-5796-47c9-abba-62bd952e0386\" :param-document=\"document\" @saved=\"scope.close()\"></ae-layout>\n  </template>\n</ae-dialog>","css":"._CSS-header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}","controller":"init() {\n  locals.counter = 0\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-05-08T09:00:42+00:00","by":"47325375-27fb-4303-8bf5-9438a0524219","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936848,"ct":1721209936848,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"2489ccb77a50ff7f","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"ee49442a-b03d-4787-b952-ed1f465066e7","v":"c3cdb59196178602","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"tasks","name":"quick-add","template":"<button class=\"ae-button\" @click.stop=\"locals.counter += 1 && $refs.dialog.show()\">\n  <ae-icon class=\"fa fa-layer-plus\"></ae-icon>\n  Neue Aufgaben\n</button>\n\n<ae-dialog ref=\"dialog\" :backdrop-close=\"false\" :full-screen=\"Aeppic.Device === 'Phone'\">\n  <template v-slot:header=\"scope\">\n    <div class=\"_CSS-header\">\n      <span>Mehrere Aufgaben erstellen</span>\n      <button class=\"ae-button\" @click=\"scope.close()\">\n        <ae-icon class=\"fa fa-times\"></ae-icon>\n      </button>\n    </div>\n  </template>\n  <template v-slot:body=\"scope\">\n    <ae-layout :key=\"locals.counter\" layout-id=\"6c101d1d-d688-49f6-b819-3001fb5603c3\" :param-document=\"document\" @saved=\"scope.close()\"></ae-layout>\n  </template>\n</ae-dialog>","css":"._CSS-header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}","controller":"init() {\n  locals.counter = 0\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-05-08T09:00:42+00:00","by":"47325375-27fb-4303-8bf5-9438a0524219","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936848,"ct":1721209936848,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"af0828f4ed642b09","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"8212d772-c3b2-4cf5-adb3-3cc0d9da9beb","v":"3067dcd0d54b17f7","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"readonly-if-command","template":"<!-- { readonly-if-command {{params.id}} } -->\n<!-- {{locals.readonly}} -->\n\n<template v-if=\"locals.ready\">\n  <label v-if=\"params.label\" :title=\"Aeppic.translate(params.label)\" class=\"ae-form-cell__label\">\n  {{ Aeppic.translate(params.label) }}\n  </label>\n\n  <ae-control :document=\"document\" :readonly=\"locals.readonly\" :name=\"params.control || ''\" :field-name=\"fieldName\" :params=\"params\" class=\"ae-form-cell__control\" :class=\"{ 'ae-form-cell__control--readonly': readonly, 'ae-form-cell__control--edit': !readonly }\"></ae-control>\n</template>\n\n","css":"","controller":"class Controller {\n  async init() {\n    locals.ready = false\n    locals.readonly = await this.executeCommnd()\n    locals.ready = true\n  }\n\n  async executeCommnd() {\n    if (!params.id) {\n      return null\n    } \n\n    try {\n      const commandResult = await Aeppic.Commands.execute(params.id, document, { \n          fieldName,\n          Account: Aeppic.Account\n      })\n\n      if (!commandResult || !commandResult.ok) {\n        return true\n      }\n\n      return commandResult.value === true\n    } catch (e) {\n      console.error(e)  \n      return true\n    }\n\n  }\n}\n  \n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-06-23T14:06:24+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2021-06-23T14:06:24+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936848,"ct":1721209936848,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"6f75d3a07634e13f","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-08-25T07:55:06+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936848,"data":{"cardinality":"BOTH","controller":"class AeRefController {\n    async init() {\n      // console.log(fieldName, Date.now())\n      // NOTE: this key makes sense because ONLY the controller is refreshed if the bound document changes, but stays the same id\n      locals.key = Aeppic.uuid()\n\n      this._droppedReference = {}\n\n      locals.justSavedAnInlineRefAtBeginning = false\n      locals.justSavedAnInlineRefAtEnd = false\n      locals.listStyle = this._parseListStyle()\n      locals.manualSort = this.parseInitialSort()\n      locals.manualFilter = this.parseInitialFilters() \n      locals.refSearchSettings = {\n        filter: {\n          base: fieldInfo.joinedFilter && `${fieldInfo.joinedFilter.replace(/'/g, '\"')}`,\n          targetDocument: document,\n          ancestorForm: params.ancestorForm,\n          queryLimitCommand: params.queryLimitCommand,\n          ancestorSearchLimit: params.ancestorSearchLimit,\n          ancestorId: params.ancestorId,\n          parentId: params.parentId,\n          ancestorIndex: params.ancestorIndex,\n          formIds: params.forms,\n          locationForm: params.locationForm,\n        },\n        search: { \n          size: 20,\n          sort: [{\n            field: 'data.name',\n            descending: false\n          },\n          {\n            field: 'modified.at',\n            descending: true\n          }],\n          searchAllFields: false,\n          includeHidden: params.includeHidden,\n        },\n      }\n\n      await this.setDefaultValue()\n    }\n\n    _parseListStyle() {\n      if (params.massEdit) {\n        return {\n          style: 'list',\n          listId: '922e8251-6e60-45a4-9df6-3d434363ac01',\n        }\n      }\n\n      if (params.listId) {\n        return {\n          style: 'custom-list',\n          listId: params.listId\n        }\n      }\n\n      if (!params.list) {\n        return {\n          style: 'tags',\n          listId: 'cf22c0e1-a656-4211-93c1-7661da07b882'\n        }\n      }\n\n      if (params.list === 'tiles') {\n        return {\n          style: 'tile',\n          listId: '1541b82b-6238-49fd-924f-c75b5d8956de'\n        }\n      }\n\n      return {\n        style: 'sortable',\n        listId: '792f8540-0b54-4ee9-aba7-084bb1cf6606',          \n      }\n    }\n\n    async setDefaultValue() {\n      if (!params.defaultValueCommand) {\n        return\n      }\n\n      if (document.hidden === false || document.hidden === undefined) {\n        return\n      }\n\n      const field = getField()\n\n      if ((Array.isArray(field) && field.length > 0) || field.id) {\n        return\n      }\n\n      const result = await Aeppic.Commands.execute(params.defaultValueCommand, document, {\n        fieldName,\n        account: Aeppic.Account\n      })\n\n\n      if (!result.value) {\n        return\n      } \n\n      await this.addReference(result.value)\n    }\n\n    parseInitialSort() {\n      if (!params.initialSortField) {\n        return null\n      }\n\n      const sort = {\n        field: params.initialSortField || null,\n        descending: params.initialSortDescending != null ?  params.initialSortDescending : null\n      }\n \n      return sort\n    }\n\n    parseInitialFilters() {\n      if (!params.showFilter || !params.initialFilters) {\n        return null\n      }\n\n    // FIXME: remove this here\n      // if (document.f.id === '17c3a5d3-90ba-4592-961b-0800873ef9c1') { // 17c3a5d3-90ba-4592-961b-0800873ef9c1 = \"Projekt\"\n      //   return null\n      // }\n\n      const parts = params.initialFilters.split(',')\n\n      return parts.join(' AND ')\n    }\n\n    get canAdd() {\n      if (params.noAdd || this.readonly) {\n        return false\n      }\n\n      const field = getField()\n\n      if (!fieldInfo.cardinality) {\n        // allow overwriting the existing ref\n        return true\n        return (!field.id) \n      }\n      \n      if (fieldInfo.cardinality.max === 'n') {\n        return true\n      }\n      \n      return field.length < Number(fieldInfo.cardinality.max)\n    }\n\n    get refs() {\n      const field = getField()\n      if (Array.isArray(field)) {\n        return field\n      }\n\n      if (!field.id && !field.v && !field.text) {\n        return []\n      }\n\n      return [field]\n    }\n\n    get readonly() {\n      if (params.readonly === true) {\n        return true\n      }\n\n      if (typeof readonly === 'boolean') {\n        return readonly\n      }\n\n      return false\n    }\n\n    async addReference(singleOrMultipleReferences, dontAnnounceFieldChange = false) {\n      if (!singleOrMultipleReferences) {\n        return\n      }\n\n      if (Array.isArray(singleOrMultipleReferences)) {\n        const allowedRefsToPick = singleOrMultipleReferences.filter(ref => {\n          if (!params.preventPickSelf) {\n            return ref\n          }\n\n          return singleOrMultipleReferences.id !== document.id\n        })\n\n        if (allowedRefsToPick.length === 0) {\n          return\n        }\n\n        for (const ref of allowedRefsToPick) {\n          await this.addReference(ref, true)\n        }\n\n        this._announceFieldChange(singleOrMultipleReferences)\n\n        return\n      }\n\n      if (params.preventPickSelf && singleOrMultipleReferences.id === document.id) {\n        return\n      }\n\n      const refs = []\n\n      if (params.addTransformCommand) {\n        const commandResult = await Aeppic.Commands.execute(params.addTransformCommand, document, { \n          ref: singleOrMultipleReferences,\n          fieldName,\n        })\n\n        if (!commandResult.ok) {\n          throw new Error(`Error executing addTransformCommand ${error.toString()}`)\n        }\n\n        if (Array.isArray(commandResult.value)) {\n          refs.push(...commandResult.value)\n        } else {\n          refs.push(commandResult.value)\n        }\n      } else {\n        refs.push(singleOrMultipleReferences)\n      }\n\n      for (const ref of refs) {\n        if (params.addToBeginning) {\n          document.addReference(fieldName, ref, {\n            addToBeginning: true\n          })\n        } else {\n          document.addReference(fieldName, ref)\n        }\n      }\n\n      await this.addDataFromReferencedDocumentToFieldGivenInAs()\n\n      if (!dontAnnounceFieldChange) {\n        this._announceFieldChange(document.data[fieldName])\n      }\n    }\n\n    _announceFieldChange(refs) {\n      // IMPORTANT: announce change manually, because (and only if) setField is NOT used.\n      $emit('field-updated', { fieldName, value: refs })\n    }\n\n    removeReference(referenceOrReferences) {\n      const references = Array.isArray(referenceOrReferences) ? referenceOrReferences : [referenceOrReferences]\n\n      for (const reference of references) {\n        if (fieldInfo.cardinality)  {\n          document.removeReference(fieldName, reference)\n        } else {\n          document.removeReference(fieldName)\n        }\n      }\n\n      this.resetDataInFieldByParamAs()\n\n      this._announceDeleteToInlineLayouts()\n\n      this._announceFieldChange(document.data[fieldName])\n    }\n\n    _announceDeleteToInlineLayouts() {\n      if ($refs.inlines) {\n        for(const ref of $refs.inlines) {\n          ref.$emit('delete')\n        }\n      } else if ($refs.inline) {\n        $refs.inline.$emit('delete')\n      }\n    }\n\n  // TODO: support cardinality\n    async addDataFromReferencedDocumentToFieldGivenInAs() {\n      if (!params.fromField || !params.as) {\n        return\n      }\n      if (fieldInfo.cardinality) {\n        console.warn('[WARN]: params.fromField and params.as not supported with cardinality')\n      }\n      \n      const field = getField()\n      const referencedDocument = await Aeppic.get(field.id)\n      document.data[params.as] = referencedDocument.data[params.fromField]\n    }\n\n    resetDataInFieldByParamAs() {\n      if (!params.fromField || !params.as) {\n        return\n      }\n      if (fieldInfo.cardinality) {\n        console.warn('[WARN]: params.fromField and params.as not supported with cardinality')\n      }\n\n      const fieldInfoOfAsField = form.getField(params.as)\n      document.data[params.as] = fieldInfoOfAsField.defaultValue\n    }\n\n    async droppedReference(event) {\n      this._droppedReference = {\n        listId: event.list,\n        index: event.index\n      }\n\n      const field = getField()\n\n      field.splice(event.index, 0, {\n        id: event.droppedDocument.id,\n        v: event.droppedDocument.v,\n        client: '',\n        text: ''\n      })\n\n      setField(field)\n    }\n\n    removeDraggedReference(event) {\n      // NOTE: This will break if the list is changed between the droppedReference and removeDraggedReference event processing\n      const { index, listId } = this._droppedReference\n      \n      // If true: Ref was dragged within this list. So we have shall not remove the, previously, newly added ref \n      const newIndex = event.list === listId ? index : null\n\n      const field = getField()\n\n      const refEntryIndex = field.findIndex((ref, i) => {\n        return ref.id === event.droppedDocument.id && i !== newIndex\n      })\n\n      if (refEntryIndex === -1) {\n        return\n      }\n\n      field.splice(refEntryIndex, 1)\n\n      setField(field)\n\n      this._droppedReferenceIndex = null\n    }\n\n    onSelected(selectedDocument, optionsParam) {\n      const options = optionsParam || {}\n\n      if (typeof params.handleSelected === 'boolean' && params.handleSelected === false) {\n        $emit('selected', selectedDocument, optionsParam)\n\n        $refs.list.$el.dispatchEvent(new CustomEvent('selected', {\n          bubbles: true,\n          detail: selectedDocument\n        }))\n      } else {\n        $refs.preview.$emit('show', { document, selectedDocument, element: options.element, selectedOptions: optionsParam })\n      }\n    }\n\n    // The commented out code can be removed its just the old version of onSelected.\n\n    // onSelected(selectedDocument, optionsParam) {\n    //   const options = optionsParam || {}\n    //   debugger\n\n    //   let handleIt = true\n\n    //   if (typeof params.handleSelected === 'boolean' && params.handleSelected === false) {\n    //     // handleIt = false\n    //     return\n    //   }\n\n    //   if (handleIt) {\n    //     $refs.preview.$emit('show', { document, selectedDocument, element: options.element, selectedOptions: optionsParam })\n    //   } else {\n    //     $emit('selected', selectedDocument, optionsParam)\n\n    //     $refs.list.$el.dispatchEvent(new CustomEvent('selected', {\n    //       bubbles: true,\n    //       detail: selectedDocument\n    //     }))\n    //   }\n    // }\n\n    onRemove(event) {\n      if (this.readonly) {\n        return\n      }\n\n      let handleIt = true\n\n      if (typeof params.handleRemove === 'boolean' && !params.handleRemove) {\n        handleIt = false\n      }\n\n      if (handleIt) {\n        if (params.confirmRemove === false) {\n          this.removeReference(event)\n        } else {\n          $refs.removeConfirmDialog.$emit('show', event)\n        }\n      } else {\n        // $emit('remove', event)\n        $refs.list.$el.dispatchEvent(new CustomEvent('remove', {\n          bubbles: true,\n          detail: event\n        }))\n      }\n    }\n\n    get showCreate() {\n      return params.creationLocationLocalSetting || params.creationLocationGlobalSetting || params.creationLocationForm\n    }\n\n    async createAndLink(filter) {\n      if (!filter) {\n        return\n      }\n\n      const location = await this._getCreationLocation()\n\n      if (!location) {\n        throw new Error('Could not retrieve location to create new document to ref in')\n      }\n\n      const newRef = await Aeppic.new(params.creationForm || fieldInfo.formRef, location)\n      \n      if (params.onCreationWriteSearchToField) {\n        newRef.data[params.onCreationWriteSearchToField] = filter\n      } else if (params.onCreationRunAction) {\n        const actionResults = await Aeppic.Actions.run([params.onCreationRunAction], { document: newRef, params: { search: filter }})\n\n\n        if (!actionResults[0].ok) {\n          throw new Error('Could not execute the \"onCreationRunAction\" action', actionResults)\n        }\n        if (actionResults[0].writes?.length) {\n          throw new Error('Do not try to persist changes within the \"onCreationRunAction\" action')\n        }\n\n        // only allow data to be overwritten\n        newRef.data = actionResults[0].value.data\n      } else {\n        newRef.data.name = filter\n      }\n\n      Aeppic.save(newRef)\n\n      await this.addReference(newRef)    \n    }\n\n    async _getCreationLocation() {\n      if (params.creationLocationLocalSetting) {\n        return Aeppic.Preferences.getSettingForDocument(params.creationLocationLocalSetting, document)\n      }\n\n      if (params.creationLocationGlobalSetting) {\n        return Aeppic.Preferences.getSetting(params.creationLocationGlobalSetting)\n      }\n\n      if (params.creationLocationForm) {\n        const parts = [`a:${document.a[0]}`]\n\n        if (params.creationAncestorForm) {\n          parts.push(`a_forms:${params.creationAncestorForm}`)\n        } else if (params.ancestorForm) {\n          parts.push(`a_forms:${params.ancestorForm}`)\n        } else if (params.creationAncestorIndex != null) {\n          parts.push(`a:${document.a[params.creationAncestorIndex]}`)\n        } else if (params.ancestorIndex != null) {\n          parts.push(`a:${document.a[params.ancestorIndex]}`)\n        }\n \n        parts.push(`f.id:${params.creationLocationForm}`)\n\n        const query = parts.join(' ')\n\n        console.log('creationLocation query', { query })\n\n        return Aeppic.findOne(query)\n      }\n      \n      return null\n    }\n\n    get designParams() {\n      return { \n        ...params.designParams,\n        handleSelectedReadonly: params.handleSelectedReadonly,\n        contextDocument: document, \n        readonly: this.readonly,\n        showRemove: params.showRemove,\n        noRemove: params.disableRemove,\n        disableRemove: params.disableRemove,\n        inline: params.inline,\n        thumbnail: params.thumbnail,\n        showActions: params.showActions,\n        showDownload: true,\n        // icon: false,\n      }\n    }\n\n    moveRef(event) {\n      console.log(event)\n      const { newIndex, oldIndex } = event\n      \n      const field = getField()\n\n      const removedRefs = field.splice(oldIndex, 1)\n      field.splice(newIndex, 0, removedRefs[0])\n\n      setField(field)\n    }\n\n    justSavedAnInlineRefAtBeginning() {\n      locals.justSavedAnInlineRefAtEnd = false\n      locals.justSavedAnInlineRefAtBeginning = true\n    }\n\n    justSavedAnInlineRefAtEnd() {\n      locals.justSavedAnInlineRefAtBeginning = false\n      locals.justSavedAnInlineRefAtEnd = true\n    }\n\n    get filter() {\n      const parts = [params.fixedControlFilter, locals.manualFilter].filter(f => f?.trim().length > 0)\n\n      return parts.join(' ')\n    }\n} \n","css":"._CSS-ref {\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  padding: var(--ae-size-2);\n\n  background: var(--ae-background); \n\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n\n._CSS-tags {\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  padding: var(--ae-size-2);\n\n  background: var(--ae-background); \n\n  display: flex;\n  flex-wrap: wrap;\n}\n\n._CSS-tag {\n  display: flex;\n  flex-flow: row;\n  align-items: center;\n  min-height: 2rem;\n\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  border-radius: var(--ae-border-radius);\n  padding: var(--ae-size-1);\n}\n\n._CSS-tag~._CSS-tag {\n  margin-left: var(--ae-size-2);\n}\n\n._CSS-tag ._CSS-button-remove {\n  padding-left: 6px;\n}\n\n._CSS ._CSS-tags ._CSS-tags__input {\n  flex: 1;\n  width: 100%;\n  border: none;\n  min-width: 4rem;\n}\n\n._CSS-tags__input:focus {\n  outline: none;\n} \n\n._CSS ._CSS-button--required-failed {\n  color: var(--ae-warning);\n}","description":"## parameters\n\nTODO: THIS IS INCOMPLETE @robert\n\n```\nparams.addToBeginning\nparams.ancestor\nparams.ancestorForm\nparams.ancestorSearchLimit\nparams.as\nparams.confirmRemove\nparams.deleteOnRemove\nparams.fromField\nparams.handleRemove\nparams.handleSelected\nparams.handleSelectedReadonly\nparams.noAdd\nparams.parent\nparams.readonly\nparams.searchSize\nparams.design\nparams.useButton\nparams.hidePath\nparams.creationLocationLocalSetting | uses Aeppic.Preferences.getSettingForDocument()\nparams.creationLocationGlobalSetting | uses Aeppic.Preferences.getSetting()\nparams.onCreationWriteSearchToField | requires `creationLocationLocalSetting` or `creationLocationGlobalSetting`. Defaults to data.name\n```","device":"ALL","label":"","limitTypes":false,"name":"ref","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<!-- LS{{locals.listStyle}} -->\n<!-- { ref } -->\n<!-- {{ locals.key }} -->\n<!-- {{ locals.manualFilter }} -->\n<!-- {{params.readonly}} -->\n<!-- {{readonly}} -->\n<!-- {{params.confirmRemove}} needs to be handed down. If it is true, components can disable their remove safeguard -->\n<template v-if=\"!params.onlyAdd\">\n  <section\n  class=\"h-full\"\n  :class=\"[!params.noBorder && 'border rounded-md px-2', !params.noBorder && validationErrors?.required && 'border-red-500']\"\n  data-ae-form-field-input\n  :data-test=\"fieldName\"\n  ref=\"control\"\n  >\n    <div ref=\"invisibleElementAtTop\"></div>\n    \n    <!-- NOTE: rerender filter when controller is re-initalized, the filter layout cannot know that it's @filter event was reset -->\n    <ae-layout\n      :key=\"locals.key\"\n      v-if=\"params.showFilter\"\n      layout-id=\"7487988f-6580-45ce-ac84-14893a6876b2\"\n      @filter=\"locals.manualFilter = $event\"\n      @sort=\"locals.manualSort = $event\"\n      :params=\"{ document: document, listFormId:fieldInfo.formRef, initialSort: locals.manualSort, disableSort:params.disableSort, disableFilter:params.disableFilter, filterFields:params.filterFields, sortFields:params.sortFields, debug:true, hidePath: params.hidePath}\"\n      style=\"margin-bottom: var(--ae-space-1)\"\n    ></ae-layout>\n\n    <ae-layout\n      v-if=\"params.inline && controller.refs.length\"\n      layout-id=\"f267dd57-fdeb-4af7-92ac-20da43d471de\"\n      :param-document=\"document\"\n      :param-field-name=\"fieldName\"\n      :param-field-info=\"fieldInfo\"\n      :param-field=\"field\"\n      :param-design-params=\"controller.designParams\"\n      :param-edit-immediately=\"locals.justSavedAnInlineRefAtBeginning\"\n      :param-add-reference-options=\"{ addToBeginning: true }\"\n      @saved=\"controller.justSavedAnInlineRefAtBeginning()\"\n    ></ae-layout>\n\n    <ae-list\n      ref=\"list\"\n      :list-id=\"locals.listStyle.listId\"\n      :documents=\"controller.refs\"\n      :sort=\"params.sort || locals.manualSort\"\n      :filter=\"controller.filter\"\n      :param-design=\"params.design\"\n      :param-design-params=\"controller.designParams\"\n      :param-listeners=\"['selected', 'remove']\"\n      :param-readonly=\"controller.readonly\"\n      :param-no-add=\"!controller.canAdd\"\n      :param-disable-sorting=\"params.disableSorting || locals.manualSort || params.sort || !fieldInfo.cardinality\"\n      :param-no-border=\"params.noBorder\"\n      :param-without-border=\"params.withoutBorder\"\n      :param-list-empty-text=\"params.listEmptyText\"\n      :param-use-scroller=\"params.useScroller\"\n      :param-scroller-height=\"params.scrollerHeight\"\n      :param-divide=\"true\"\n      :param-disable-remove=\"params.disableRemove\"\n      :param-collapse-after=\"params.collapseAfter\"\n      :param-selection-id=\"params.massEdit ? Aeppic.uuid() : ''\"\n      @drop=\"controller.droppedReference($event)\"\n      @dropped=\"controller.removeDraggedReference($event)\"\n      @selected=\"controller.onSelected($event, arguments[1])\"\n      @remove=\"controller.onRemove($event, arguments[1])\"\n      @add=\"$refs.selectionButton.$emit('show', { target: $refs.invisibleElementAtTop })\"\n      @move=\"controller.moveRef($event)\"\n      :class=\"{ 'h-full' :!params.inline && !params.showFilter }\"\n       data-test=\"list_refs\"\n    ></ae-list>\n\n    <ae-layout\n      v-if=\"params.inline\"\n      layout-id=\"f267dd57-fdeb-4af7-92ac-20da43d471de\"\n      :param-document=\"document\"\n      :param-field-name=\"fieldName\"\n      :param-field-info=\"fieldInfo\"\n      :param-field=\"field\"\n      :param-design-params=\"controller.designParams\"\n      :param-edit-immediately=\"locals.justSavedAnInlineRefAtEnd\"\n      @saved=\"controller.justSavedAnInlineRefAtEnd()\"\n    ></ae-layout>\n  </section>\n</template>\n<ae-layout\n  layout-id=\"ef23b6bd-ec74-4701-ab8d-71a6d67fd9e4\"\n  @selected=\"controller.addReference($event)\"\n  @create-and-link=\"controller.createAndLink($event)\"\n  ref=\"selectionButton\"\n  :params=\"{ hidePath: params.hidePath,\n                      settings: locals.refSearchSettings,\n                      keepOpenAfterSelection: params.keepOpenAfterSelection,\n                      showCreate: controller.showCreate,\n                      fullPath: params.showFullPathm,\n                      icon: params.icon || 'fa fa-angle-down', \n                      hideButton: true,\n                      prominentPickCommand: params.prominentPickCommand,\n                      uiFilterCommand: params.uiFilterCommand,\n                      pickerDesign: params.pickerDesign,\n                      showAddAll: params.showAddAll,\n                    }\"\n></ae-layout>\n<!-- hideButton: !controller.canAdd || (locals.listStyle.style === 'tags' && !params.onlyAdd) -->\n\n<span v-if=\"validationErrors?.required\" style=\"color: var(--ae-warning)\">\n  <!-- TODO: use validationErrors?.required.html -->\n  {{label}} ist erforderlich\n</span>\n\n<ae-layout\n  layout-id=\"b477c02e-0e5a-4baf-b782-3e0e64d8816c\"\n  ref=\"removeConfirmDialog\"\n  @delete=\"controller.removeReference($event)\"\n></ae-layout>\n\n<ae-layout\n  layout-id=\"c649767f-9798-4980-8109-79eba242917e\"\n  ref=\"preview\"\n  @selected=\"Aeppic.drillInto($event, arguments[1])\"\n></ae-layout>\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"0ddd1e98-ab01-4789-bbfe-f0b21e9c83fd","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-05-11T13:24:42+00:00","backedBy":"","by":"09763886-6748-4a56-8e0e-54a2293ee96b","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"52d77ded8d9067c4"},"stamps":[],"t":1778505882923,"v":"e845e87a4e7e1b12"},{"a":["system","controls-folder","91f205bc-9989-4fbe-8966-bd4ba9777c26"],"a_depth":3,"a_forms":["system-folder-form","repository-folder-form","repository-folder-form"],"created":{"at":"2019-03-11T14:10:55+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","client":"server","from":""},"ct":1721209936848,"data":{"cardinality":"BOTH","controller":"class AeRefController {\n    async init() {\n      // console.log(fieldName, Date.now())\n      // NOTE: this key makes sense because ONLY the controller is refreshed if the bound document changes, but stays the same id\n      locals.key = Aeppic.uuid()\n\n      this._droppedReference = {}\n \n      locals.listStyle = this._parseListStyle()\n      locals.manualSort = this.parseInitialSort()\n      locals.manualFilter = this.parseInitialFilters() \n      locals.refSearchSettings = {\n        filter: {\n          base: fieldInfo.joinedFilter && `${fieldInfo.joinedFilter.replace(/'/g, '\"')}`,\n          targetDocument: document,\n          ancestorForm: params.ancestorForm,\n          queryLimitCommand: params.queryLimitCommand,\n          ancestorSearchLimit: params.ancestorSearchLimit,\n          ancestorId: params.ancestorId,\n          parentId: params.parentId,\n          formIds: params.forms,\n        },\n        search: { \n          size: 20,\n          sort: [{\n            field: 'data.name',\n            descending: false\n          },\n          {\n            field: 'modified.at',\n            descending: true\n          }],\n          searchAllFields: false\n        },\n      }\n\n      await this.setDefaultValue()\n    }\n\n    _parseListStyle() {\n      if (!params.list) {\n        return { \n          style: 'tags',\n          listId: '0069cfac-81c9-4fa8-ba34-f6f5f0e7bf7b'\n        }\n      }\n\n      if (params.list === 'tiles') {\n        return {\n          style: 'tile',\n          listId: '1541b82b-6238-49fd-924f-c75b5d8956de'\n        }\n      }\n\n      return {\n        style: 'sortable',\n        listId: '062ef57e-6140-4c55-951e-341a226e5457',\n      }\n    }\n\n    async setDefaultValue() {\n      if (!params.defaultValueCommand) {\n        return\n      }\n\n      if (document.hidden === false || document.hidden === undefined) {\n        return\n      }\n\n      const field = getField()\n\n      if ((Array.isArray(field) && field.length > 0) || field.id) {\n        return\n      }\n\n      const result = await Aeppic.Commands.execute(params.defaultValueCommand, document, {\n        fieldName,\n        account: Aeppic.Account\n      })\n\n\n      if (!result.value) {\n        return\n      } \n\n      await this.addReference(result.value)\n    }\n\n    parseInitialSort() {\n      if (!params.initialSortField) {\n        return null\n      }\n\n      const sort = {\n        field: params.initialSortField || null,\n        descending: params.initialSortDescending != null ?  params.initialSortDescending : null\n      }\n \n      return sort\n    }\n\n    parseInitialFilters() {\n      if (!params.showFilter || !params.initialFilters) {\n        return null\n      }\n\n    // FIXME: remove this here\n      // if (document.f.id === '17c3a5d3-90ba-4592-961b-0800873ef9c1') { // 17c3a5d3-90ba-4592-961b-0800873ef9c1 = \"Projekt\"\n      //   return null\n      // }\n\n      const parts = params.initialFilters.split(',')\n\n      return parts.join(' AND ')\n    }\n\n    get canAdd() {\n      if (params.noAdd || this.readonly) {\n        return false\n      }\n\n      const field = getField()\n\n      if (!fieldInfo.cardinality) {\n        // allow overwriting the existing ref\n        return true\n        return (!field.id) \n      }\n      \n      if (fieldInfo.cardinality.max === 'n') {\n        return true\n      }\n      \n      return field.length < Number(fieldInfo.cardinality.max)\n    }\n\n    get requiredFailed() {\n      if (!fieldInfo.required) {\n        return false\n      }\n\n      return !this.refs.some(ref => !!ref.id)\n    }\n\n    get refs() {\n      const field = getField()\n      \n      if (!field) {\n        console.log('standalone?')\n        return []\n      }\n\n      if (Array.isArray(field)) {\n        return field\n      }\n\n      if (field.id === '' && field.v === '' && field.text === '') {\n        return []\n      }\n\n      return [field]\n    }\n\n    get readonly() {\n      if (params.readonly === true) {\n        return true\n      }\n\n      if (typeof readonly === 'boolean') {\n        return readonly\n      }\n\n      return false\n    }\n\n    async addReference(singleOrMultipleReferences) {\n      if (!singleOrMultipleReferences) {\n        return\n      }\n\n      if (Array.isArray(singleOrMultipleReferences)) {\n        for (const ref of singleOrMultipleReferences) {\n          await this.addReference(ref)\n        }\n\n        return\n      }\n\n      const refs = []\n\n      if (params.addTransformCommand) {\n        const commandResult = await Aeppic.Commands.execute(params.addTransformCommand, document, { \n          ref: singleOrMultipleReferences,\n          fieldName,\n        })\n\n        if (!commandResult.ok) {\n          throw new Error(`Error executing addTransformCommand ${error.toString()}`)\n        }\n\n        if (Array.isArray(commandResult.value)) {\n          refs.push(...commandResult.value)\n        } else {\n          refs.push(commandResult.value)\n        }\n      } else {\n        refs.push(singleOrMultipleReferences)\n      }\n\n      for (const ref of refs) {\n        if (params.addToBeginning) {\n          document.addReference(fieldName, ref, {\n            addToBeginning: true\n          })\n        } else {\n          document.addReference(fieldName, ref)\n        }\n      }\n\n      await this.addDataFromReferencedDocumentToFieldGivenInAs()\n    }\n\n    removeReference(reference) {\n      if (fieldInfo.cardinality)  {\n        document.removeReference(fieldName, reference)\n      } else {\n        document.removeReference(fieldName)\n      }\n      \n      this.resetDataInFieldByParamAs()\n\n      this._announceDeleteToInlineLayouts()\n    }\n\n    _announceDeleteToInlineLayouts() {\n      if ($refs.inlines) {\n        for(const ref of $refs.inlines) {\n          ref.$emit('delete')\n        }\n      } else if ($refs.inline) {\n        $refs.inline.$emit('delete')\n      }\n    }\n\n    async addDataFromReferencedDocumentToFieldGivenInAs() {\n      if (!params.fromField || !params.as) {\n        return\n      }\n      if (fieldInfo.cardinality) {\n        console.warn('[WARN]: params.fromField and params.as not supported with cardinality')\n      }\n      \n      const field = getField()\n      const referencedDocument = await Aeppic.get(field.id)\n      document.data[params.as] = referencedDocument.data[params.fromField]\n    }\n\n    resetDataInFieldByParamAs() {\n      if (!params.fromField || !params.as) {\n        return\n      }\n      if (fieldInfo.cardinality) {\n        console.warn('[WARN]: params.fromField and params.as not supported with cardinality')\n      }\n\n      const fieldInfoOfAsField = form.getField(params.as)\n      document.data[params.as] = fieldInfoOfAsField.defaultValue\n    }\n\n    async droppedReference(event) {\n      this._droppedReference = {\n        listId: event.list,\n        index: event.index\n      }\n\n      const field = getField()\n\n      field.splice(event.index, 0, {\n        id: event.droppedDocument.id,\n        v: event.droppedDocument.v,\n        client: '',\n        text: ''\n      })\n\n      setField(field)\n    }\n\n    removeDraggedReference(event) {\n      // NOTE: This will break if the list is changed between the droppedReference and removeDraggedReference event processing\n      const { index, listId } = this._droppedReference\n      \n      // If true: Ref was dragged within this list. So we have shall not remove the, previously, newly added ref \n      const newIndex = event.list === listId ? index : null\n\n      const field = getField()\n\n      const refEntryIndex = field.findIndex((ref, i) => {\n        return ref.id === event.droppedDocument.id && i !== newIndex\n      })\n\n      if (refEntryIndex === -1) {\n        return\n      }\n\n      field.splice(refEntryIndex, 1)\n\n      setField(field)\n\n      this._droppedReferenceIndex = null\n    }\n\n    onSelected(selectedDocument, optionsParam) {\n      const options = optionsParam || {}\n//      if (this.readonly && params.handleSelectedReadonly !== true) {\n        //return\n      //}\n\n      let handleIt = true\n\n      if (typeof params.handleSelected === 'boolean' && !params.handleSelected) {\n        handleIt = false\n      }\n\n      if (handleIt) {\n        $refs.preview.$emit('show', { document, selectedDocument, element: options.element, selectedOptions: optionsParam })\n      } else {\n        $emit('selected', selectedDocument, optionsParam)\n\n        $refs.list.$el.dispatchEvent(new CustomEvent('selected', {\n          bubbles: true,\n          detail: selectedDocument\n        }))\n      }\n    }\n\n    onRemove(event) {\n      if (this.readonly) {\n        return\n      }\n\n      let handleIt = true\n\n      if (typeof params.handleRemove === 'boolean' && !params.handleRemove) {\n        handleIt = false\n      }\n\n      if (handleIt) {\n        if (params.confirmRemove) {\n          $refs.removeConfirmDialog.$emit('show', event)\n        } else {\n          this.removeReference(event)\n        }\n      } else {\n        // $emit('remove', event)\n        $refs.list.$el.dispatchEvent(new CustomEvent('remove', {\n          bubbles: true,\n          detail: event\n        }))\n      }\n    }\n\n    get showCreate() {\n      return params.creationLocationLocalSetting || params.creationLocationGlobalSetting || params.creationLocationForm\n    }\n\n    async createAndLink(filter) {\n      if (!filter) {\n        return\n      }\n\n      const location = await this._getCreationLocation()\n\n      if (!location) {\n        throw new Error('Could not retrieve location to create new document to ref in')\n      }\n\n      const newRef = await Aeppic.new(params.creationForm || fieldInfo.formRef, location)\n      \n      if (params.onCreationWriteSearchToField) {\n        newRef.data[params.onCreationWriteSearchToField] = filter\n      } else if (params.onCreationRunAction) {\n        const actionResults = await Aeppic.Actions.run([params.onCreationRunAction], { document: newRef, params: { search: filter }})\n\n\n        if (!actionResults[0].ok) {\n          throw new Error('Could not execute the \"onCreationRunAction\" action', actionResults)\n        }\n        if (actionResults[0].writes.length) {\n          throw new Error('Do not try to persist changes within the \"onCreationRunAction\" action')\n        }\n\n        // only allow data to be overwritten\n        newRef.data = actionResults[0].value.data\n      } else {\n        newRef.data.name = filter\n      }\n\n      Aeppic.save(newRef)\n\n      await this.addReference(newRef)    \n    }\n\n    async _getCreationLocation() {\n      if (params.creationLocationLocalSetting) {\n        return Aeppic.Preferences.getSettingForDocument(params.creationLocationLocalSetting, document)\n      }\n\n      if (params.creationLocationGlobalSetting) {\n        return Aeppic.Preferences.getSetting(params.creationLocationGlobalSetting)\n      }\n\n      if (params.creationLocationForm) {\n        const parts = [`a:${document.a[0]}`]\n\n        if (params.ancestorForm) {\n          parts.push(`a_forms:${params.ancestorForm}`)\n        }\n\n        parts.push(`f.id:${params.creationLocationForm}`)\n\n        return Aeppic.findOne(parts.join(' '))\n      }\n      \n      return null\n    }\n\n    get designParams() {\n      return { \n        ...params.designParams,\n        handleSelectedReadonly: params.handleSelectedReadonly,\n        contextDocument: document, \n        readonly: this.readonly,\n        showRemove: params.showRemove,\n        noRemove: params.disableRemove,\n        disableRemove: params.disableRemove,\n      }\n    }\n\n    moveRef(event) {\n      console.log(event)\n      const { newIndex, oldIndex } = event\n      \n      const field = getField()\n\n      const removedRefs = field.splice(oldIndex, 1)\n      field.splice(newIndex, 0, removedRefs[0])\n\n      setField(field)\n    }\n} \n","css":"._CSS-ref {\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  padding: var(--ae-size-2);\n\n  background: var(--ae-background); \n\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n\n._CSS-tags {\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  padding: var(--ae-size-2);\n\n  background: var(--ae-background); \n\n  display: flex;\n  flex-wrap: wrap;\n}\n\n._CSS-tag {\n  display: flex;\n  flex-flow: row;\n  align-items: center;\n  min-height: 2rem;\n\n  border: var(--ae-border-width) solid var(--ae-border-color);\n  border-radius: var(--ae-border-radius);\n  padding: var(--ae-size-1);\n}\n\n._CSS-tag~._CSS-tag {\n  margin-left: var(--ae-size-2);\n}\n\n._CSS-tag ._CSS-button-remove {\n  padding-left: 6px;\n}\n\n._CSS ._CSS-tags ._CSS-tags__input {\n  flex: 1;\n  width: 100%;\n  border: none;\n  min-width: 4rem;\n}\n\n._CSS-tags__input:focus {\n  outline: none;\n} \n\n._CSS ._CSS-button--required-failed {\n  color: var(--ae-warning);\n}","description":"## parameters\n\nTODO: THIS IS INCOMPLETE @robert\n\n```\nparams.addToBeginning\nparams.ancestor\nparams.ancestorForm\nparams.ancestorSearchLimit\nparams.as\nparams.confirmRemove\nparams.deleteOnRemove\nparams.fromField\nparams.handleRemove\nparams.handleSelected\nparams.handleSelectedReadonly\nparams.noAdd\nparams.parent\nparams.readonly\nparams.searchSize\nparams.design\nparams.useButton\nparams.hidePath\nparams.creationLocationLocalSetting | uses Aeppic.Preferences.getSettingForDocument()\nparams.creationLocationGlobalSetting | uses Aeppic.Preferences.getSetting()\nparams.onCreationWriteSearchToField | requires `creationLocationLocalSetting` or `creationLocationGlobalSetting`. Defaults to data.name\n```","device":"ALL","label":"","limitTypes":true,"name":"ref","namespace":"","packages":[],"parameters":[],"standalone":"FIELD","template":"<!-- LS{{locals.listStyle}} -->\n<!-- { ref }\n{{ locals.key }}\n{{ locals.manualFilter }} -->\n \n<template v-if=\"!params.onlyAdd\">\n  <div v-if=\"params.inline\">\n    <div v-if=\"controller.refs.length\" class=\"ae-form-cell__input\" ref=\"inlineContainer\" :class=\"{ 'ae-form-cell__input--no-border': params.withoutBorder || params.noBorder }\"> \n      <template v-if=\"fieldInfo.cardinality\"> \n        <ae-layout  v-for=\"ref of field\"\n                    v-if=\"ref.id\"\n                  :param-document-id=\"ref.id\"\n                  :layout-id=\"params.inlineLayout || '8088c9e2-670a-425a-9aba-2d9ff9804ef5'\"\n                  @selected=\"controller.onSelected($event, arguments[1])\"\n                  @remove=\"controller.onRemove($event)\"\n                  ref=\"inlines\"\n                  :param-no-remove=\"params.noRemove\"\n                  :param-readonly=\"controller.readonly\"\n                  :param-inline-form=\"params.inlineForm\"\n                  :param-inline-form-layout=\"params.inlineFormLayout\"\n                  :param-only-fields=\"params.inlineOnlyFields\"\n                  :param-no-expand=\"params.inlineNoExpand\"\n        ></ae-layout>\n      </template>\n      <ae-layout v-else-if=\"field.id\"\n                          :param-document-id=\"field.id\"\n                          :layout-id=\"params.inlineLayout || '8088c9e2-670a-425a-9aba-2d9ff9804ef5'\" \n                          @selected=\"controller.onSelected($event, arguments[1])\"\n                          @remove=\"controller.onRemove($event)\"\n                          ref=\"inline\"\n                          :param-no-remove=\"params.noRemove\"\n                          :param-readonly=\"controller.readonly\"\n                          :param-inline-form=\"params.inlineForm\"\n                          :param-inline-form-layout=\"params.inlineFormLayout\"\n                          :param-only-fields=\"params.inlineOnlyFields\"\n                          :param-no-expand=\"params.inlineNoExpand\"\n      ></ae-layout>\n      <!-- <div v-if=\"!params.noAdd && !controller.readonly\" @click.stop.prevent=\"controller.showAdd()\" class=\"_CSS-search\" style=\"cursor:text;min-width:2em;xbackground:red;min-height:2em;\"></div> -->\n    </div> \n  </div> \n  <template v-else>\n    <!-- NOTE: rerender filter when controller is re-initalized, the filter layout cannot know that it's @filter event was reset -->\n    <ae-layout \n      :key=\"locals.key\"\n      v-if=\"params.showFilter\"\n      layout-id=\"7487988f-6580-45ce-ac84-14893a6876b2\"\n      @filter=\"locals.manualFilter = $event\"\n      @sort=\"locals.manualSort = $event\"\n      :params=\"{ document: document, listFormId:fieldInfo.formRef, initialSort: locals.manualSort, disableSort:params.disableSort, disableFilter:params.disableFilter, filterFields:params.filterFields, sortFields:params.sortFields, debug:true, hidePath: params.hidePath}\"\n      style=\"margin-bottom: var(--ae-space-1);\"\n    ></ae-layout> \n\n    <ae-list  :list-id=\"locals.listStyle.listId\"\n              ref=\"list\" \n              :documents=\"controller.refs\"\n              @drop=\"controller.droppedReference($event)\"\n              @dropped=\"controller.removeDraggedReference($event)\"\n              :param-design=\"params.design\"\n              :param-design-params=\"controller.designParams\"\n              :param-listeners=\"['selected', 'remove']\"\n              :param-readonly=\"controller.readonly\"\n              :param-no-add=\"!controller.canAdd\"\n              :param-disable-sorting=\"params.disableSorting || locals.manualSort || params.sort\"\n              :param-no-border=\"params.noBorder\"\n              :param-without-border=\"params.withoutBorder\"\n              @selected=\"controller.onSelected($event, arguments[1])\"\n              @remove=\"controller.onRemove($event)\"\n              :sort=\"params.sort || locals.manualSort\"\n              :filter=\"locals.manualFilter\"\n              @add=\"$refs.selectionButton.$emit('show', { target: $refs.list.$el })\"\n              @move=\"controller.moveRef($event)\"\n              :param-disable-remove=\"params.disableRemove\"\n              :param-collapse-after=\"params.collapseAfter\"\n    ></ae-list> \n    \n  </template>\n</template>\n\n<ae-layout  xv-if=\"controller.canAdd && locals.listStyle.style !== 'tags'\"\n            layout-id=\"36649f27-5869-49e6-9b49-3b5c3b9c795d\"\n            @selected=\"controller.addReference($event)\"\n            @create-and-link=\"controller.createAndLink($event)\"\n            ref=\"selectionButton\"\n            :params=\"{ hidePath: params.hidePath,\n                      settings: locals.refSearchSettings,\n                      keepOpenAfterSelection: params.keepOpenAfterSelection,\n                      showCreate: controller.showCreate,\n                      fullPath: params.showFullPathm,\n                      icon: params.icon || 'fa fa-angle-down', \n                      selectionHistory: params.selectionHistory,\n                      selectionHistorySize: params.selectionHistorySize,\n                      hideButton: !controller.canAdd || (locals.listStyle.style === 'tags' && !params.onlyAdd)\n                    }\" \n></ae-layout> \n\n<span v-if=\"controller.requiredFailed\" style=\"color:var(--ae-warning)\">{{label}} ist erforderlich</span>\n\n\n<ae-layout layout-id=\"6ee77ae8-b2f6-4fce-8fbc-788a55d5f7da\"\nref=\"removeConfirmDialog\"\n@delete=\"controller.removeReference($event)\"\n></ae-layout>\n\n<ae-layout layout-id=\"8ebd91bd-9110-4ee5-b3d8-4248d2dc696d\" ref=\"preview\" @selected=\"Aeppic.drillInto($event, arguments[1])\"></ae-layout>\n\n","types":["ref"]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"4a5a1bb5-c6f0-4554-b3a8-4a78d19dbc82","inheritedLocks":[null,null,null,null],"locks":[],"modified":{"at":"2026-01-08T12:45:25+00:00","backedBy":"","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":""},"p":"91f205bc-9989-4fbe-8966-bd4ba9777c26","previous":{"v":"6a8b4030a201276b"},"stamps":[],"t":1767876325758,"v":"22f728c1ecde877f"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-08-25T11:58:45+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936848,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    locals.refSearchSettings = {\n      filter: {\n        base: fieldInfo.joinedFilter && `${fieldInfo.joinedFilter.replace(/'/g, '\"')}`,\n        targetDocument: document,\n        ancestorForm: params.ancestorForm,\n        queryLimitCommand: params.queryLimitCommand,\n        ancestorSearchLimit: params.ancestorSearchLimit,\n        ancestorId: params.ancestorId,\n        parentId: params.parentId,\n        formIds: params.forms,\n      },\n      search: { \n        size: 20,\n        sort: [{\n          field: 'data.name',\n          descending: false\n        },\n        {\n          field: 'modified.at',\n          descending: true\n        }],\n        searchAllFields: false\n      },\n    }\n  }\n\n\n\n  get canAdd() {\n    return !params.noAdd && !readonly\n  }\n\n  showAdd(clickEvent) {\n    if (this.canAdd) {\n      $refs.selectionButton.$emit('show', clickEvent) \n    }\n  }\n\n   async addReference(singleOrMultipleReferences) {\n      if (!singleOrMultipleReferences) {\n        return\n      }\n\n      if (Array.isArray(singleOrMultipleReferences)) {\n        for (const ref of singleOrMultipleReferences) {\n          await this.addReference(ref)\n        }\n\n        return\n      }\n\n      const refs = []\n\n      if (params.addTransformCommand) {\n        const commandResult = await Aeppic.Commands.execute(params.addTransformCommand, document, { \n          ref: singleOrMultipleReferences,\n          fieldName,\n        })\n\n        if (!commandResult.ok) {\n          throw new Error(`Error executing addTransformCommand ${error.toString()}`)\n        }\n\n        if (Array.isArray(commandResult.value)) {\n          refs.push(...commandResult.value)\n        } else {\n          refs.push(commandResult.value)\n        }\n      } else {\n        refs.push(singleOrMultipleReferences)\n      }\n\n      for (const ref of refs) {\n        if (params.addToBeginning) {\n          document.addReference(fieldName, ref, {\n            addToBeginning: true\n          })\n        } else {\n          document.addReference(fieldName, ref)\n        }\n      }\n\n      await this.addDataFromReferencedDocumentToFieldGivenInAs()\n    }\n\n     async createAndLink(filter) {\n      if (!filter) {\n        return\n      }\n\n      const location = await this._getCreationLocation()\n\n      if (!location) {\n        throw new Error('Could not retrieve location to create new document to ref in')\n      }\n\n      const newRef = await Aeppic.new(params.creationForm || fieldInfo.formRef, location)\n      \n      if (params.onCreationWriteSearchToField) {\n        newRef.data[params.onCreationWriteSearchToField] = filter\n      } else if (params.onCreationRunAction) {\n        const actionResults = await Aeppic.Actions.run([params.onCreationRunAction], { document: newRef, params: { search: filter }})\n\n\n        if (!actionResults[0].ok) {\n          throw new Error('Could not execute the \"onCreationRunAction\" action', actionResults)\n        }\n        if (actionResults[0].writes.length) {\n          throw new Error('Do not try to persist changes within the \"onCreationRunAction\" action')\n        }\n\n        // only allow data to be overwritten\n        newRef.data = actionResults[0].value.data\n      } else {\n        newRef.data.name = filter\n      }\n\n      Aeppic.save(newRef)\n\n      await this.addReference(newRef)    \n    }\n\n    async _getCreationLocation() {\n      if (params.creationLocationLocalSetting) {\n        return Aeppic.Preferences.getSettingForDocument(params.creationLocationLocalSetting, document)\n      }\n\n      if (params.creationLocationGlobalSetting) {\n        return Aeppic.Preferences.getSetting(params.creationLocationGlobalSetting)\n      }\n\n      if (params.creationLocationForm) {\n        const parts = [`a:${document.a[0]}`]\n\n        if (params.ancestorForm) {\n          parts.push(`a_forms:${params.ancestorForm}`)\n        }\n\n        parts.push(`f.id:${params.creationLocationForm}`)\n\n        return Aeppic.findOne(parts.join(' '))\n      }\n      \n      return null\n    }\n\n    async addDataFromReferencedDocumentToFieldGivenInAs() {\n      if (!params.fromField || !params.as) {\n        return\n      }\n      if (fieldInfo.cardinality) {\n        console.warn('[WARN]: params.fromField and params.as not supported with cardinality')\n      }\n      \n      const field = getField()\n      const referencedDocument = await Aeppic.get(field.id)\n      document.data[params.as] = referencedDocument.data[params.fromField]\n    }\n}\n","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"ref-add","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<template v-if=\"controller.canAdd && params.list\">\n  <ae-layout layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n      @selected=\"controller.showAdd({ target: $refs.button.$el })\"\n      param-text=\"Auswählen\"\n      param-classes=\"border border-neutral-200 text-xs rounded-full\"\n      ref=\"button\"\n></ae-layout> \n  <ae-layout  :layout-id=\"params.addLayoutId ? params.addLayoutId : 'ef23b6bd-ec74-4701-ab8d-71a6d67fd9e4'\"\n              @selected=\"controller.addReference($event)\"\n              @create-and-link=\"controller.createAndLink($event)\"\n              ref=\"selectionButton\"\n              :params=\"{ hidePath: params.hidePath,\n                        settings: locals.refSearchSettings,\n                        keepOpenAfterSelection: params.keepOpenAfterSelection,\n                        showCreate: controller.showCreate,\n                        fullPath: params.showFullPath,\n                        icon: params.icon || 'fa fa-angle-down', \n                        selectionHistory: params.selectionHistory,\n                        selectionHistorySize: params.selectionHistorySize,\n                        hideButton: true,\n                        document: document,\n                      }\" \n  ></ae-layout> \n</template>     \n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"03728759-6cfe-40ed-9d44-a9ba4dd9a733","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-04-16T08:14:54+00:00","backedBy":"","by":"729fa027-7e7e-4b06-9602-f535c43824f0","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"c2045676886e7921"},"stamps":[],"t":1776327294489,"v":"31525704a90d18b5"},{"id":"be2822af-cb79-443d-85f3-422b8c5a1e3c","v":"8de6d1a6252d1107","previous":{"v":"0f1c76e523fcd7a5"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"ref-reorder","template":"<ae-layout  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\" \n            @selected=\"controller.reorder()\"\n            :param-text=\"'Ordnen nach ' + params.reorderLabel\"\n            param-icon=\"far fa-sort\"\n            param-classes=\"border border-neutral-200 text-xs rounded-full\"\n            ref=\"button\"\n            :param-disabled=\"!field.length\"\n></ae-layout>  ","css":"","controller":"class RefReorder {\n  async reorder() {\n    if (!params.reorderBy) {\n      throw new Error(`Missing params.reorderBy`)\n    }\n\n    const field = await Aeppic.getAll(getField())\n\n    if (!field.length) {\n      return\n    }\n\n    // const typeOfSortableAttribute = typeof field.find(e => !!e).data[params.reorderBy]\n\n    // if (typeOfSortableAttribute !== 'boolean') {\n    //   throw new Error('Only fields of type boolean can be reordered')\n    // }\n\n    const ordered = Aeppic.sort(field, [{ field: `data.${params.reorderBy}`, descending: params.reorderDescending ?? false }])\n    \n    setField(ordered)\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2023-04-13T09:29:01+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2024-08-21T11:32:46+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1724239966500,"ct":1721209936848,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"cb9ed791-aa59-40b3-8254-219759cdccb6","v":"010b0554ce59138a","previous":{"v":"initial"},"p":"75014663-b40b-4285-bdf9-ad54c6b5964a","f":{"id":"control-form","v":"d87073964fc278a5","text":"Control"},"data":{"namespace":"sct","name":"rueckgabe-signieren","template":"<!-- PACKAGE: rs-signature-for-angular@0.3.4 -->\n\n<div layout=\"column\">\n    <rs-signature on-signed=\"document.executeCommand('68459c9a-b0dc-40f0-a3c0-fc3b75b695ad')\" layout-fill layout=\"column\" file-field=\"context.field\" as=\"signature\" ng-readonly=\"false\">\n        <md-button ng-click=\"signature.sign()\" class=\"md-warn md-raised\" style=\"margin:0;width:100%;min-height:100px;background-color:#83806d\" layout=\"row\" layout-align=\"center center\">\n            <div>\n                <md-icon>assignment_turned_in</md-icon>\n                <span>Rückgabe des Tablet</span>\n            </div>\n        </md-button>\n    </rs-signature>\n</div>","css":"","controller":"","cardinalityLimitation":"SINGLE","standalone":"FIELD","types":[],"packages":[],"supportsMobile":false,"label":"","description":""},"locks":[],"created":{"at":"2022-02-28T15:17:12+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-02-28T15:17:26+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1646061446914,"ct":1646061432111,"a_depth":3,"a":["apps","d4581623-f542-4ade-86be-8b3ecec56f0e","75014663-b40b-4285-bdf9-ad54c6b5964a"],"a_forms":["folder","application-form","folder"],"inheritedLocks":[null,null,null,null],"stamps":[]},{"id":"77a22149-0a89-4e92-a5df-e082a0c75271","v":"3fb01ec9e4a2f601","previous":{"v":"initial"},"p":"75014663-b40b-4285-bdf9-ad54c6b5964a","f":{"id":"control-form","v":"d87073964fc278a5","text":"Control"},"data":{"namespace":"sct","name":"rueckgabe-unterschrift","template":"<!-- PACKAGE: rs-signature-for-angular@0.3.4 -->\n\n<div layout=\"column\">\n     <div ng-if=\"context.field.dataUrl\" layout=\"row\" layout-align=\"left\" style=\"height:200px\">\n        <img style=\"max-height:200px;\" ng-if=\"context.field.dataUrl.substr(0,5) === 'data:'\" ng-src=\"{{context.field.dataUrl}}\" >\n        <img style=\"max-height:200px;\" ng-if=\"context.field.dataUrl.substr(0,6) === 'store:'\" ng-src=\"{{'api/docs/' + context.document.id + '/content?field=' + context.fieldName}}\">\n    </div>\n    <rs-signature ng-if=\"!context.field.dataUrl\" xon-signed=\"rsAccount.logout()\" layout-fill layout=\"column\" file-field=\"context.field\" as=\"signature\" ng-readonly=\"false\">\n        <md-button ng-click=\"signature.sign()\" class=\"md-warn md-raised\" style=\"margin:0;width:100%;min-height:100px;background-color: #64748e\" layout=\"row\" layout-align=\"center center\">\n            <div>\n                <md-icon>assignment_turned_in</md-icon>\n                <span>Unterschrift des Wachdienstes</span>\n            </div>\n        </md-button>\n    </rs-signature>\n</div>","css":"","controller":"","cardinalityLimitation":"SINGLE","standalone":"FIELD","types":[],"packages":[],"supportsMobile":false,"label":"","description":""},"locks":[],"created":{"at":"2022-02-28T15:17:37+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-02-28T15:17:50+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1646061470273,"ct":1646061457385,"a_depth":3,"a":["apps","d4581623-f542-4ade-86be-8b3ecec56f0e","75014663-b40b-4285-bdf9-ad54c6b5964a"],"a_forms":["folder","application-form","folder"],"inheritedLocks":[null,null,null,null],"stamps":[]},{"id":"b65ce4ca-bd96-4deb-a904-ab9b004955d5","v":"faacac6b4ccd11bb","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"select","template":"<div v-if=\"locals.ready\" class=\"relative flex flex-row items-center\">\n  <div v-if=\"fieldInfo.tags['colored-square']\" class=\"w-9 h-9 border border-gray-400 rounded mr-2\" :style=\"'background-color:' + fieldInfo.tags['colored-square']\"></div>\n  <div ref=\"input\" \n    v-if=\"locals.isMultiSelect\"\n    tabindex=\"0\"\n    @click=\"controller.open($event)\"\n    @keydown.enter.prevent.stop=\"controller.enterOnInput($event)\"\n    @keydown.down.prevent.stop=\"controller.downOnInput($event)\"\n    @keydown=\"controller.find($event)\"       \n    class=\"mt-1 relative w-full cursor-default min-h-9 rounded-md py-2 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6 flex flex-wrap gap-2 hover:bg-gray-50\"\n    :class=\"(params.readonly ?? readonly) ? '' : 'bg-white'\"\n    aria-haspopup=\"listbox\" \n    :aria-expanded=\"locals.popupOpen\"\n    >\n    <div \n      v-for=\"selectedOption of controller.getSelectedOptions()\"\n      class=\"inline-flex items-center gap-x-0.5 rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10\">\n      <label v-if=\"selectedOption.tags?.['colored-pill']\" aria-label=\"Pink\" class=\"relative mr-2 text-sm -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 ring-current focus:outline-none\"\n              :style=\"{ 'color': selectedOption.tags?.['colored-pill'] }\"\n        >\n        <span aria-hidden=\"true\" class=\"h-4 w-4 rounded-full border border-black border-opacity-10 bg-current\"></span>\n      </label>\n      <span>{{selectedOption.display}}</span>\n      <button type=\"button\"  \n        v-if=\"!(params.readonly ?? readonly)\"\n        class=\"group relative -mr-1 h-3.5 w-3.5 rounded-sm hover:bg-gray-500/20\"\n        @click=\"controller.deselectOption(selectedOption)\"\n        @keydown.enter.prevent.stop=\"controller.deselectOption(selectedOption)\"\n        > \n        <span class=\"sr-only\">Remove</span>\n        <svg viewBox=\"0 0 14 14\" class=\"h-3.5 w-3.5 stroke-gray-600/50 group-hover:stroke-gray-600/75\">\n          <path d=\"M4 4l6 6m0-6l-6 6\" />\n        </svg>\n        <span class=\"absolute -inset-1\" />\n      </button>\n      <span class=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2\">\n        <svg class=\"h-5 w-5 text-gray-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n          <path fill-rule=\"evenodd\" d=\"M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z\" clip-rule=\"evenodd\" />\n        </svg>\n      </span>\n    </div>\n  </div>\n  <button ref=\"input\" \n    v-else\n    type=\"button\" \n    @click=\"controller.open($event)\"\n    @keydown.enter.prevent.stop=\"controller.enterOnInput($event)\"\n    @keydown.down.prevent.stop=\"controller.downOnInput($event)\"\n    @keydown=\"controller.find($event)\"       \n    class=\"relative w-full cursor-default min-h-8 rounded-md py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6 hover:bg-gray-50\"\n    :class=\"(params.readonly ?? readonly) ? '' : 'bg-white'\"\n    aria-haspopup=\"listbox\" \n    :aria-expanded=\"locals.popupOpen\">\n    <div class=\"flex gap-2\">\n      <div class=\"flex flex-col flex-1 overflow-hidden\">\n        <div class=\"flex flex-row items-center\">\n          <label v-if=\"controller.getCurrentOption().tags?.['colored-pill']\" aria-label=\"Pink\" class=\"relative mr-2 text-sm -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 ring-current focus:outline-none\"\n            :style=\"{ 'color': controller.getCurrentOption().tags?.['colored-pill'] }\"\n          >\n            <span aria-hidden=\"true\" class=\"h-4 w-4 rounded-full border border-black border-opacity-10 bg-current\"></span>\n          </label>\n          <span v-if=\"!controller.isEmptySelection(controller.getCurrentOption())\" class=\"block truncate font-normal flex-1 \">{{controller.getCurrentValueLabel()}}</span>\n          <span v-else class=\"truncate italic font-normal text-gray-400 flex-1\">{{locals.emptyOptionText}}</span>\n        </div>\n        <template v-if=\"controller.getShortHelpForOption(controller.getCurrentOption())\">\n          <span class=\"truncate text-ellipsis text-gray-400\" v-html=\"controller.getShortHelpForOption(controller.getCurrentOption()).html\"></span>\n        </template>\n        <span class=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2\">\n          <svg class=\"h-5 w-5 text-gray-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n            <path fill-rule=\"evenodd\" d=\"M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z\" clip-rule=\"evenodd\" />\n          </svg>\n        </span>\n      </div>\n      <button  v-if=\"controller.doesOptionHaveLongHelp(controller.getCurrentOption())\" \n        tabindex=\"-1\"\n        class=\"flex flex-col block m-auto p-2\"\n        :class=\"'text-gray-400'\"\n        @mouseenter.prevent.stop=\"controller.openDetailedHelp(controller.getCurrentOption(), $event)\"\n        @mouseleave.prevent.stop=\"$refs.detail.close()\"\n        @click.prevent.stop=\"controller.openDetailedHelp(controller.getCurrentOption(), $event)\"\n        >\n        <ae-icon class=\"far fa-info-circle\"></ae-icon>\n      </button>\n    </div>\n  </button>   \n  <ae-popup ref=\"popup\" theme=\"\"\n    :arrow=\"false\"\n    placement=\"bottom-start\"\n    :close-delay=\"150\"\n    :max-width=\"1600\"\n    @close=\"locals.popupOpen = false\"\n    @show=\"controller.onOpen()\"\n    >\n    <!--\n      Select popover, show/hide based on select state.\n\n      Entering: \"\"\n        From: \"\"\n        To: \"\"\n      Leaving: \"transition ease-in duration-100\"\n        From: \"opacity-100\"\n        To: \"opacity-0\"\n    --> \n    <ul  slot=\"popup\"\n      class=\"max-h-80 z-20 border shadow-lg overflow-y-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm\"\n      tabindex=\"-1\"\n      role=\"listbox\"\n      aria-activedescendant=\"listbox-option-3\"\n      @mouseenter=\"controller.popupEnter()\"\n      @blur=\"controller.close()\"\n      @keydown.escape.prevent.stop=\"controller.close()\"\n      @keydown.down.prevent.stop=\"controller.down($event)\"\n      @keydown.up.prevent.stop=\"controller.up($event)\"\n      @keydown.enter.prevent.stop=\"controller.select()\"\n      @keydown=\"controller.find($event)\"\n      >\n\n      <!--\n        Select option, manage highlight styles based on mouseenter/mouseleave and keyboard navigation.\n\n        Highlighted: \"bg-indigo-600 text-white\", Not Highlighted: \"text-gray-900\"\n      -->\n      <li v-for=\"(option,index) of controller.getOptions()\"\n        ref=\"option\"\n        class=\"relative min-h-8 grid grid-flow-col gap-1 items-center cursor-default select-none py-2 pl-3 pr-1\"\n        :class=\"(controller.doOptionsHaveLargeHelp() ? 'grid-cols-[32px_1fr_32px]' : 'grid-cols-[32px_1fr]') + (controller.isHighlightedOption(option) ? ' text-white bg-indigo-600' : ' text-gray-900')\"\n        :style=\"{ width: controller.getPopupWidth() }\" \n        :key=\"option.value\"\n        role=\"option\"\n        :tabindex=\"controller.isHighlightedOption(option) ? 0 : -1\"\n        :title=\"option.display\"\n        @focus=\"controller.mark(option)\"\n        @click=\"controller.select(option)\"\n        @keydown.enter.prevent.stop=\"controller.select(option)\"\n        @mouseenter=\"controller.mark(option)\"\n        @mouseleave=\"controller.unmark(option)\"\n        @keydown.f1.prevent.stop=\"controller.openDetailedHelp(option, $event)\"          \n        >\n      \n        <!-- \n          Checkmark, only display for selected option.\n        -->\n        <span v-if=\"controller.isSelectedOption(option)\"\n          class=\"inset-y-0 flex items-center\"\n          :class=\"controller.isHighlightedOption(option) ? 'text-white' : 'text-indigo-600'\">\n          <svg class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n            <path fill-rule=\"evenodd\" d=\"M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z\" clip-rule=\"evenodd\" />\n          </svg>\n        </span>\n        <span v-else>&nbsp;</span>\n\n        <div class=\"flex flex-col overflow-hidden w-full\">\n          <div class=\"flex flex-row\">\n            <label v-if=\"option.tags?.['colored-pill']\" aria-label=\"Pink\" class=\"relative mr-2 text-sm -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 ring-current focus:outline-none\"\n                :style=\"{ 'color': option.tags?.['colored-pill'] }\"\n              >\n              <span aria-hidden=\"true\" class=\"h-4 w-4 rounded-full border border-black border-opacity-10 bg-current\"></span>\n            </label>\n          \n            <span v-if=\"!controller.isEmptySelection(option)\"\n              class=\"block truncate font-normal\"\n              :class=\"controller.isSelectedOption(option) ? 'font-bold': ' font-normal'\"\n              >{{option.display}}</span>\n            <span v-else class=\"block truncate italic font-normal text-gray-400\">{{locals.emptyOptionText}}</span>\n          </div>\n\n          <template v-if=\"controller.getShortHelpForOption(option)\">\n            <span class=\"block truncate text-gray-400\" v-html=\"controller.getShortHelpForOption(option).html\"></span>\n          </template>\n        </div>\n\n          <template v-if=\"controller.doOptionsHaveLargeHelp() && $refs.option\">\n          <button  \n            v-if=\"controller.doesOptionHaveLongHelp(option)\"\n            tabindex=\"-1\"\n            class=\"flex flex-col block m-auto \"\n            :class=\"controller.isHighlightedOption(option) ? 'text-white' : 'text-gray-400'\"\n            @click.prevent.stop=\"controller.openDetailedHelp(option, { target: $refs.option[index] })\"\n            @mouseenter.prevent.stop=\"controller.openDetailedHelp(option, { target: $refs.option[index] })\"\n            >\n              <ae-icon class=\"far fa-info-circle\"></ae-icon>\n          </button>\n          <div v-else></div>\n        </template>\n\n      </li>\n    </ul>\n  </ae-popup>\n\n  <ae-popup ref=\"detail\" :arrow=\"false\" :close-delay=\"50\" :max-width=\"1200\" >\n    <div slot=\"popup\"\n      v-if=\"locals.helpDetailsOption && controller.getLongHelpForOption(locals.helpDetailsOption)\"\n      class=\"w-full overflow-auto rounded-md bg-white p-3 text-base shadow-lg \"\n      @mouseenter.prevent.stop=\"controller.openDetailedHelp(controller.getCurrentOption(), $event)\"\n      v-html=\"controller.getLongHelpForOption(locals.helpDetailsOption).html\">\n    </div>\n  </ae-popup>\n</div>","css":"","controller":"async init() {\n  locals.ready = false\n  locals.forms = null\n  locals.highlightedOption = null\n  locals.emptyOptionText = params.placeholder?.value ?? 'Leer'\n  locals.helpDetailsOption = null\n  locals.popupOpen = false\n  locals.isMultiSelect = Array.isArray(getField())\n  \n  try {\n    locals.form = await Aeppic.getFormForDocument(document)\n  } finally {\n    locals.ready = !!locals.form\n  } \n\n  this.lastKeyFindPress = 0\n  locals.lastKeyFindTerm = ''\n} \n\nasync find(event) {\n  const findTerm = this.buildFindTerm(event)\n\n  if (!findTerm) {\n    return\n  }\n\n  event.preventDefault()\n  event.stopPropagation()\n\n  if (locals.popupOpen) { \n    this.markNextMatch(findTerm)\n    this.pendingMark = null\n  } else {\n    this.pendingMark = findTerm\n    this.open(event)\n  }\n}\n\ngetSelectedOptions() {\n  let selectedValues = getField()\n\n  return selectedValues.map(v => fieldInfo.settings.options.find(o => o.value === v) ?? 'unknown')\n}\n\nmarkNextMatch(findTerm) {\n  console.log('searching for', findTerm)\n  let findWords = findTerm.split(' ').map(t => t.trim()).filter(t => t.length > 0).map(t => t.toLocaleLowerCase())\n\n  if (!findWords || findWords.length == 0) {\n    return\n  }\n\n  let firstWord = findWords[0]\n\n  // Find with prefix search first \n  let fullList = fieldInfo.settings.options\n  let list = fullList.filter(o => o.display.toLocaleLowerCase().startsWith(firstWord))\n\n  if (list.length === 0) { \n    list = fullList\n  }\n\n  for (const term of findWords) {\n    list = list.filter(o => o.display.toLocaleLowerCase().includes(term))\n  }\n\n  if (list.length > 0) {\n    this.focusOption(list[0])    \n  }\n}\n\nbuildFindTerm(event) {\n  const now = Date.now()\n  const timeSinceLastKeyFind = now - this.lastKeyFindPress\n  this.lastKeyFindPress = now\n\n  const MAX_DURATION_BETWEEN_KEY_PRESSES_TO_EXTEND_SEARCH = params.keyClearanceTime ?? 450 \n\n  if (timeSinceLastKeyFind > MAX_DURATION_BETWEEN_KEY_PRESSES_TO_EXTEND_SEARCH) {\n    locals.lastKeyFindTerm = ''\n  }\n\n  if (event.key?.length != 1) {\n    return null\n  }\n\n  locals.lastKeyFindTerm += event.key\n\n  return locals.lastKeyFindTerm\n}\n  \nopenDetailedHelp(option, event) {\n  locals.helpDetailsOption = option\n  $refs.detail?.show(event)\n}\n\ndoOptionsHaveLargeHelp() {\n  return fieldInfo.settings.options.some(o => this.doesOptionHaveLongHelp(o))\n}\n\ndoesOptionHaveLongHelp(option) {\n  let text = this.getLongHelpForOption(option)?.text?.trim() \n\n  if (!text) {\n    return false\n  }\n\n  return text.trim().length > 0\n}\n\ndoesOptionHaveHelp(option) {\n  return this.getShortHelpForOption(option)?.text?.trim() != ''\n}\n\ngetShortHelpForOption(option) {\n  return option.help\n}\n\ngetLongHelpForOption(option) {\n  return option.helpExtended\n}\n\nmark(option) {\n  locals.highlightedOption = option\n  locals.helpDetailsOption = option\n}\n\nunmark(option) {\n  if (option === locals.highlightedOption) {\n    locals.highlightedOption = null\n  }\n\n  if (option == locals.highlightedOption) {\n    locals.helpDetailsOption = null\n  }\n\n}\n\ndown(option) {\n  this.move(1)\n}\n\nup() {\n  this.move(-1)\n}\n\nmove(inc) {\n  let numberOfOptions = fieldInfo.settings.options.length\n  \n  if (numberOfOptions === 0) {\n    return\n  } \n\n  let index = fieldInfo.settings.options.findIndex(o => o === locals.highlightedOption)\n\n  if (index<0) {\n    index = 0\n  }\n\n  if (index === 0 && inc < 0) {\n    $refs.input.focus()\n    this.close()\n    return\n  }\n\n  let newIndex = Math.min(Math.max(0, index + inc), numberOfOptions-1)\n  let option = fieldInfo.settings.options[newIndex]\n\n  $refs.option[newIndex].focus()\n\n  this.mark(option)\n}\n\nfocusFirstOption() {\n  this.focusOption(fieldInfo.settings.options[0])\n}\n\nfocusCurrentOption() {\n  let value = this.getCurrentValue()\n  let option = this.findOption(value)\n\n  this.focusOption(option)\n}\n\nfocusOption(option) {\n  let index = fieldInfo.settings.options.findIndex(o => o === option)\n\n  if (index>=0) {\n    this.mark(fieldInfo.settings.options[index])\n    $refs.option?.[index]?.focus()\n  }\n}\n\nselect(option) {\n  if (params.readonly ?? readonly) {\n    return\n  }\n\n  if (!option) {\n    option = locals.highlightedOption\n  }\n\n  if (option) {\n    if (locals.isMultiSelect) {\n      let values = getField()\n      const alreadySelected = values.some(v => v === option.value)\n\n      if (alreadySelected) {\n        this.deselectOption(option)\n      } else {\n        let values = getField()\n        values.push(option.value)\n\n        // sort\n        let sorted = []\n        for (const o of fieldInfo.settings.options) {\n          if (values.some(v => v === o.value)) {\n            sorted.push(o.value)\n          }\n        }\n\n        setField(sorted)\n      }\n    } else {\n      setField(option.value)\n    }\n  }\n\n  this.closeAfterSelect()\n}\n\ndeselectOption(option) {\n  if (locals.isMultiSelect) {\n    let values = getField()\n    values = values.filter(v => v !== option.value)\n    setField(values)\n\n    if (values.length === 0) {\n      this.close()\n    }\n  }\n}\n\ncloseAfterSelect() {\n  if (params['keep-open-after-selection'] ) {\n    return\n  }\n\n  $refs.input?.focus()\n  this.close()\n}\n\nisEmptySelection(option) {\n  return option.display.trim() === '' && fieldInfo.settings.options[0] === option\n}\n\nisCurrentOption(option) {\n  return option.value === this.getCurrentValue()\n}\n\nisSelectedOption(option) {\n  const value = getField()\n\n  if (Array.isArray(value)) {\n    return value.some(v => v === option.value)\n  } else {\n    return value === option.value\n  }\n\n}\n\nisHighlightedOption(option) {\n  return option === locals.highlightedOption \n}\n\ngetCurrentValue() {\n  return getField()\n}\n\ngetCurrentOption() {\n  let value = this.getCurrentValue()\n  return this.findOption(value)\n}\n\nfindOption(value) {\n  const o = fieldInfo.settings.options.find(o => o.value === value)\n\n  if (!o) {\n    console.log('UNKNOWN SELECT VALUE IN FIELD',value,document)\n    return {\n      label: 'UNKNOWN',\n      value\n    }\n  }\n\n  return o\n}\n\ngetCurrentValueLabel() {\n  const option = this.getCurrentOption()\n  return option?.display ?? ''\n} \n\ngetOptions() {\n  return fieldInfo?.settings?.options ?? []\n}\n\nhasInput() {\n  return !!$refs.input\n}\n\ngetPopupWidth() {\n  const readToRefresh = locals.popupOpen\n  return $refs.input?.offsetWidth + 'px' ?? ''\n}\n\nasync openFromKeyboard(event) {\n  this.open(event)\n  await Aeppic.nextRenderPass()\n  \n  if (!locals.highlightedOption) {\n    this.focusFirstOption()\n  }\n}\n\nasync enterOnInput(event) {\n  locals.highlightedOption = null\n  if (!locals.popupOpen) {\n    if (!locals.isMultiSelect) {\n      locals.highlightedOption = this.findOption(getField())\n    }\n    return this.openFromKeyboard(event)\n  }\n\n  this.select()\n}\n\nasync downOnInput(event) {\n  if (!locals.popupOpen) {\n    return this.openFromKeyboard(event)\n  }\n\n  this.down()\n}\n\nasync open(event) {\n  $refs.popup?.show({ target: $refs.input })\n}\n\nasync close() {\n  locals.highlightedOption = null\n  locals.lastKeyFindTerm = 0\n  locals.lastKeyFindPress = 0\n  locals.helpDetailsOption = null\n\n  $refs.detail?.close()\n  $refs.popup?.close()\n\n  await Aeppic.nextRenderPass()\n\n  $refs.input?.focus()\n}\n\npopupEnter() {\n  $refs.popup?.cancelClose()\n} \n\nonOpen() {\n  locals.popupOpen = true\n\n  if (this.pendingMark) {\n    this.markNextMatch(this.pendingMark)\n    this.pendingMark = null\n  } else if (locals.highlightedOption) {\n    console.log('here')\n    this.focusOption(locals.highlightedOption)\n  } else {\n    this.focusFirstOption()\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-07-17T06:14:41+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"modified":{"at":"2024-07-17T06:15:00+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"t":1721209936848,"ct":1721209936848,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"e272a10e937d77ef","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-08-22T14:26:28+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936848,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    locals.renderPopup = false\n\n    if (this.isRef) {\n      // FIXME: should not needed to remove single quote here\n      const query = `${this.queryLimit} (${fieldInfo.joinedFilter.replace(/'/g, '\"')})`\n      locals.items = await Aeppic.find(query)\n    } else {\n      locals.items = fieldInfo.settings.options\n    }\n\n    if (typeof locals.items === 'undefined') {\n      locals.items = []\n    }\n  }\n\n  get queryLimit() {\n    const parts = []\n\n    if (params.ancestorSearchLimit) {\n      if (document.a.length < params.ancestorSearchLimit) {\n        console.error('ancestorSearchLimit is out of bounds: limit is greater than document ancestors')\n      } else {\n        parts.push(`a:${document.a[document.a.length - params.ancestorSearchLimit]}`)\n      }\n    }\n\n    if (params.ancestor) {\n      parts.push(this._fieldIdsAsQueryString(form.getField(params.ancestor), document.data[params.ancestor], 'a:'))\n    }\n\n    if (params.parent) {\n      parts.push(this._fieldIdsAsQueryString(form.getField(params.parent), document.data[params.parent], 'p:'))\n    }\n\n    return parts.join(' AND ')\n  }\n\n  _fieldIdsAsQueryString(fieldsInfo, values, stringToPrefixValue) {\n    const parts = []\n\n    const isRef = fieldsInfo.subType === 'ref'\n\n    if (fieldsInfo.cardinality) {\n      for (const value of values) {\n        const id = isRef ? value.id : value\n        parts.push(`${stringToPrefixValue}${id}`)\n      }\n    } else {\n      const id = isRef ? values.id : values\n      parts.push(`${stringToPrefixValue}${id}`)\n    }\n\n    return parts.join(' OR ')\n  }\n\n  get isRef() {\n    return fieldInfo.subType === 'ref'\n  }\n\n  get fieldValues() {\n    if (!this.isRef) {\n      return this.fieldValuesOfString\n    }\n\n    return getField()\n  }\n\n  get fieldValuesOfString() {\n    const field = getField()\n\n    if (field === '' || field == null) {\n      return [fieldInfo.defaultValue]\n    }\n    if (typeof field === 'string') {\n      return field.split(',')\n    }\n    if (Array.isArray(field)) {\n      return field\n    }\n    return [field]\n  }\n\n  get fieldItems() {\n    if (!this.isRef) {\n      return this.fieldValues.map((value) => {\n        if (value === null) {\n          return null\n        }\n        return this.item(value)\n      })\n    }\n\n    return this.fieldValues\n  }\n\n  async showPicker(clickEvent) {\n    if (readonly) {\n      return\n    }\n\n    locals.renderPopup = true\n\n    await Aeppic.nextRenderPass()\n    await Aeppic.nextRenderPass()\n\n    $refs.popup?.show(clickEvent)\n  }\n\n  closePicker() {\n    $refs.popup?.close()\n\n    locals.renderPopup = false\n  }\n\n  async togglePicker(clickEvent) {\n    if (locals.renderPopup) {\n      this.closePicker()\n    } else {\n      await this.showPicker(clickEvent)\n    }\n  }\n\n\n  item(itemValue) {\n    const item = fieldInfo.settings.options.find((option) => {\n      return option.value === itemValue\n    })\n    if (item) {\n      return item\n    }\n\n    // TODO: selects with default-values are not really working within the form. The initial default value is missing, so this error message would appear to the user.\n    //       Once the initial select states are correct, one may log/display the error (again)\n    console.debug(`Not a valid value (raw: \"${itemValue}\")`)\n\n    return {\n      display: '',\n      value: itemValue,\n      invalid: true\n    }\n  }\n\n  onInput(value) {\n    if (value === '') {\n      setField(fieldInfo.defaultValue)\n      return\n    }\n\n    if (!this.isRef) {\n      if (fieldInfo.type === 'number') {\n        setField(Number(value))\n      } else {\n        setField(value)\n      }\n      return\n    }\n\n    const ref = locals.items.find(ref => ref.id === value)\n    setField(ref)\n  }\n\n  selected(item) {\n    // FIXME: this.max is buggy? fieldInfo is null\n    // if (this.max === 1 || params.closeOnSelected) {\n      this.closePicker()\n    // } \n\n    if (!this.isRef) {\n      // FIXME: improve this\n      for (const currentItem of this.fieldValues) {\n        if (this.item(currentItem).invalid) {\n          this.removeItem(currentItem)\n        }\n      }\n\n      this.addRemoveItem(item)\n      return\n    }\n\n    this.addRemoveReference(item)\n  }\n\n  addRemoveReference(item) {\n    const field = getField()\n\n    if (!fieldInfo.cardinality) {\n      if (!field.id) {\n        document.removeReference(fieldName)\n      } else {\n        document.addReference(fieldName, item)\n      }\n      return\n    }\n\n    if (field.some(ref => ref.id === item.id)) {\n      document.removeReference(fieldName, item)\n    } else {\n      document.addReference(fieldName, item)\n    }\n  }\n\n  addRemoveItem(item) {\n    console.log('addRemoveItem', { item })\n\n    const field = this.fieldValues\n\n    if (!fieldInfo.cardinality) {\n      setField(item.value)\n    } else {\n      if (field.includes(item.value)) {\n        this.removeItem(item.value)\n      } else {\n        while (field.length >= this.max) {\n          field.pop()\n        }\n\n        field.push(item.value)\n        setField(field)\n      }\n    }\n\n    if (params.deleteFieldsIfChanged) {\n      const fieldNames = params.deleteFieldsIfChanged.split(',')\n\n      for (const nameOfField of fieldNames) {\n        const fieldInfoOfField = form.getField(nameOfField)\n\n        this._unsetField(nameOfField, fieldInfoOfField)\n      }\n    }\n  }\n\n  _unsetField(nameOfField, fieldInfoOfField) {\n    const { type, cardinality, defaultValue } = fieldInfoOfField\n \n    if (cardinality) {\n      document.data[nameOfField] = []\n    } else {\n      document.data[nameOfField] = defaultValue\n    }\n  }\n\n  removeItem(itemValue) {\n    let field = getField()\n\n    if (Array.isArray(field)) {\n      field.splice(field.indexOf(itemValue), 1)\n    } else if (itemValue === field) {\n      field = ''\n    }\n\n    setField(field)\n  }\n\n  get max() {\n    let max = fieldInfo.cardinality.max\n    if (max) {\n      max = max.toLowerCase() === 'n' ? 10000 : parseInt(max)\n    } else {\n      max = 1\n    }\n    return max\n  }\n\n  get min() {\n    const min = fieldInfo.cardinality.min\n    return min ? parseInt(min) : 0\n  }\n\n  icon(value) {\n    return params[`icon_${value}`] || fieldInfo.settings.options.find(option => option.value === value)?.tags?.icon\n  }\n\n  iconColor(value) {\n    const primitiveColor = params[`iconcolor_${value}`] || fieldInfo.settings.options.find(option => option.value === value)?.tags?.iconcolor\n    \n    if (!primitiveColor) {\n      return primitiveColor\n    }\n\n    const colorMap = {\n      green: 'green-500',\n      red: 'red-500',\n      blue: 'blue-500',\n      grey: 'gray-500',\n      gray: 'gray-500',\n    }\n\n    return `text-${colorMap[primitiveColor] || `${primitiveColor}-500`}`\n  }\n} ","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"select","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<!-- <pre >\n{{fieldInfo}}\n{{params}}\n{{ controller.icon(field) }} \n{{ controller.iconColor(field) }}\n</pre>   -->\n<div  ref=\"container\"\n     class=\"min-h-[2em] border rounded-md py-[2px] px-2 flex gap-1 bg-white dark:bg-slate-800\"\n     :class=\"{ 'border-red-500': validationErrors?.required, 'bg-neutral-100': params.appearance === 'toggle-text' }\"\n     @click.stop=\"controller.togglePicker({ target: $refs.container })\"\n     data-ae-form-field-input\n     :data-test=\"fieldName\" \n> \n  <!-- text-[0.75em] almost the height of other simple controls -->\n  <div v-if=\"params.appearance === 'toggle-text'\" class=\"flex gap-1 w-full items-center\"> \n    <div class=\"flex gap-1\" v-for=\"item of locals.items\">\n      <ae-layout v-if=\"item.display\" \n                  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n                  :param-icon=\"controller.icon(item.value) ? [controller.icon(item.value), controller.iconColor(item.value)] : null\"\n                  :param-text=\"Aeppic.translate(item.display)\"\n                  @selected=\"controller.selected(item)\"\n                  :param-border=\"controller.fieldValues.find(fieldValue => fieldValue === item.value) ? 'border border-primary-800' : 'border border-secondary-300'\"\n                  :param-classes=\"controller.fieldValues.find(fieldValue => fieldValue === item.value) ? 'text-primary-900 bg-primary-50' : 'bg-white text-neutral-600'\"\n                  :param-disabled=\"readonly\" \n                  class=\"text-[0.75em]\" \n                  :class=\"{ 'cursor-pointer': !readonly }\"\n      ></ae-layout>\n    </div> \n    <div class=\"flex-1\"></div>\n    <ae-layout v-if=\"locals.items?.find(item => !item.display)\"\n                layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n                param-icon=\"far fa-times text-neutral-500\"\n                @selected=\"controller.selected(locals.items.find(item => !item.display))\"\n                param-border=\"border border-secondary-300\"\n                :param-disabled=\"readonly || controller.fieldValues[0] === fieldInfo.defaultValue\"\n                class=\"text-[0.75em]\" \n    ></ae-layout>     \n  </div>\n  <template v-else> \n    <div class=\"flex gap-1 pr-1 mr-3 truncate\" v-for=\"fieldValue of controller.fieldValues\">\n      <div class=\"self-center\">\n        <ae-icon v-if=\"controller.icon(fieldValue)\" :class=\"[controller.icon(fieldValue), controller.iconColor(fieldValue)]\"></ae-icon>\n      </div>\n\n      <div v-if=\"fieldInfo.settings.options.find(o => o.value === fieldValue)?.display\"\n        class=\"truncate\" :class=\"{ 'cursor-pointer': !readonly }\" :title=\"fieldInfo.settings.options.find(o => o.value === fieldValue).display\"> \n        {{ fieldInfo.settings.options.find(o => o.value === fieldValue).display }}\n      </div>\n      <div v-else class=\"truncate italic text-neutral-700\" title=\"keine Auswahl\" :class=\"{ 'cursor-pointer': !readonly }\">\n        <template v-if=\"readonly\">\n          keine Auswahl\n        </template>\n        <template v-else>\n          auswählen \n        </template>\n      </div>\n    </div>\n    <div v-if=\"!controller.fieldValues.length\" class=\"italic text-neutral-700\" :class=\"{ 'cursor-pointer': !readonly }\">\n      <template v-if=\"readonly\">\n        keine Auswahl\n      </template>\n      <template v-else>\n        auswählen \n      </template>\n    </div>  \n\n    <div class=\"flex-1\"></div>  \n    \n    <div class=\"relative flex flex-col text-neutral-400\">\n      <ae-icon class=\"fa fa-caret-up absolute top-0 right-0\"></ae-icon>\n      <ae-icon class=\"fa fa-caret-down absolute top-2 right-0\"></ae-icon>\n    </div> \n    \n    <!-- <div ref=\"popup\">\n      <div v-if=\"locals.renderPopup\" class=\"w-screen\"></div> -->\n      <ae-popup ref=\"popup\" placement=\"bottom-start\" :max-width=\"800\" >\n        <div v-if=\"locals.renderPopup\" :data-test=\"fieldName + '_popup'\">\n          <div v-if=\"controller.isRef\"> \n            TODO: isRef \n          </div>\n          <div v-else class=\"w-full flex flex-col gap-1\">\n            <section v-for=\"item of locals.items\"\n                @click.stop=\"controller.selected(item)\"\n                class=\"hover:bg-neutral-100 cursor-pointer\" \n                :data-test=\"fieldName + '_option_' + item.value\"\n            >\n              <div class=\" py-1 px-2 flex gap-1\">\n                <div class=\"self-center\">\n                  <ae-icon v-if=\"controller.icon(item.value)\" class=\"\" :class=\"[controller.icon(item.value), controller.iconColor(item.value)]\"></ae-icon>\n                </div>\n\n                <div v-if=\"item.display\">\n                  {{ item.display }} \n                </div>\n                <div v-else class=\"italic\">\n                  keine Auswahl\n                </div>\n              </div>\n            </section> \n          </div>  \n        </div>\n      </ae-popup>\n    <!-- </div>  -->\n  </template>\n</div>   \n\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"def2a5ea-6f33-4a11-986d-1570f382076d","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-05-11T08:19:08+00:00","backedBy":"","by":"09763886-6748-4a56-8e0e-54a2293ee96b","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"355f528291df6321"},"stamps":[],"t":1778487548389,"v":"a7f5aaf91a61c888"},{"a":["system","controls-folder","91f205bc-9989-4fbe-8966-bd4ba9777c26"],"a_depth":3,"a_forms":["system-folder-form","repository-folder-form","repository-folder-form"],"created":{"at":"2019-01-23T12:42:08+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","client":"server","from":""},"ct":1721209936849,"data":{"cardinality":"SINGLE","controller":"class Controller {\n  async init() {\n    locals.ready = false\n \n    try {\n      locals.form = await Aeppic.getFormForDocument(document)\n    } finally {\n      locals.ready = true\n    } \n\n    if (this.isRef) {\n      // FIXME: should not needed to remove single quote here\n      const query = `${this.queryLimit} (${fieldInfo.joinedFilter.replace(/'/g, '\"')})`\n      locals.items = await Aeppic.find(query)\n    } else {\n      locals.items = fieldInfo.settings.options\n    }\n\n    if (typeof locals.items === 'undefined') {\n      locals.items = []\n    }\n  }\n\n  get queryLimit() {\n    const parts = []\n\n    if (params.ancestorSearchLimit) {\n      if (document.a.length < params.ancestorSearchLimit) {\n        console.error('ancestorSearchLimit is out of bounds: limit is greater than document ancestors')\n      } else {\n        parts.push(`a:${document.a[document.a.length - params.ancestorSearchLimit]}`)\n      }\n    }\n\n    if (params.ancestor) {\n      parts.push(this._fieldIdsAsQueryString(locals.form.getField(params.ancestor), document.data[params.ancestor], 'a:'))\n    }\n\n    if (params.parent) {\n      parts.push(this._fieldIdsAsQueryString(locals.form.getField(params.parent), document.data[params.parent], 'p:'))\n    }\n\n    return parts.join(' AND ')\n  }\n\n  _fieldIdsAsQueryString(fieldsInfo, values, stringToPrefixValue) {\n    const parts = []\n\n    const isRef = fieldsInfo.subType === 'ref'\n\n    if (fieldsInfo.cardinality) {\n      for (const value of values) {\n        const id = isRef ? value.id : value\n        parts.push(`${stringToPrefixValue}${id}`)\n      }\n    } else {\n      const id = isRef ? values.id : values\n      parts.push(`${stringToPrefixValue}${id}`)\n    }\n\n    return parts.join(' OR ')\n  }\n\n  get isInvalid() {\n    if (!fieldInfo.required) {\n      return false\n    }\n\n    if (this.fieldValues.some(value => value === 'NONE')) {\n      return true\n    }\n\n    const errors = locals.form.validateDocumentDataField(document.data, fieldName)\n    if (!errors) {\n      return false\n    }  \n\n    return true\n  }\n\n  get isRef() {\n    return fieldInfo.subType === 'ref'\n  }\n\n  get fieldValues() {\n    if (!this.isRef) {\n      return this.fieldValuesOfString\n    }\n\n    return getField()\n  }\n\n  get fieldValuesOfString() {\n    const field = getField()\n\n    if (field === '' || field == null) {\n      return [fieldInfo.defaultValue]\n    }\n    if (typeof field === 'string') {\n      return field.split(',')\n    }\n    if (Array.isArray(field)) {\n      return field\n    }\n    return [field]\n  }\n\n  get fieldItems() {\n    if (!this.isRef) {\n      return this.fieldValues.map((value) => {\n        if (value === null) {\n          return null\n        }\n        return this.item(value)\n      })\n    }\n\n    return this.fieldValues\n  }\n\n  showPicker(clickEvent) {\n    if (readonly) {\n      return\n    }\n\n    if (Aeppic.Device === 'Phone') {\n      $refs.dialog.$emit('show', clickEvent)\n      return\n    }\n\n    $refs.popup.$emit('show', clickEvent)\n  }\n\n  closePicker() {\n    if (Aeppic.Device === 'Phone') {\n      $refs.dialog.$emit('close')\n      return\n    }\n\n    $refs.popup.$emit('close', {\n      target: $refs.select\n    })\n  }\n\n\n  item(itemValue) {\n    const item = fieldInfo.settings.options.find((option) => {\n      return option.value === itemValue\n    })\n    if (item) {\n      return item\n    }\n\n    // TODO: selects with default-values are not really working within the form. The initial default value is missing, so this error message would appear to the user.\n    //       Once the initial select states are correct, one may log/display the error (again)\n    console.debug(`Not a valid value (raw: \"${itemValue}\")`)\n\n    return {\n      display: '',\n      value: itemValue,\n      invalid: true\n    }\n  }\n\n  onInput(value) {\n    if (value === '') {\n      setField(fieldInfo.defaultValue)\n      return\n    }\n\n    if (!this.isRef) {\n      if (fieldInfo.type === 'number') {\n        setField(Number(value))\n      } else {\n        setField(value)\n      }\n      return\n    }\n\n    const ref = locals.items.find(ref => ref.id === value)\n    setField(ref)\n  }\n\n  selected(item) {\n    if (params.closeOnSelected) {\n      this.closePicker()\n    }\n\n    if (!this.isRef) {\n      // FIXME: improve this\n      for (const currentItem of this.fieldValues) {\n        if (this.item(currentItem).invalid) {\n          this.removeItem(currentItem)\n        }\n      }\n\n      this.addRemoveItem(item)\n      return\n    }\n\n    this.addRemoveReference(item)\n  }\n\n  addRemoveReference(item) {\n    const field = getField()\n    if (!fieldInfo.cardinality) {\n      if (!field.id) {\n        document.removeReference(fieldName)\n      } else {\n        document.addReference(fieldName, item)\n      }\n      return\n    }\n\n    if (field.some(ref => ref.id === item.id)) {\n      document.removeReference(fieldName, item)\n    } else {\n      document.addReference(fieldName, item)\n    }\n  }\n\n  addRemoveItem(item) {\n    console.log('addRemoveItem', { item })\n    const field = this.fieldValues\n\n    if (!fieldInfo.cardinality) {\n      setField(item.value)\n      return\n    }\n\n    if (field.includes(item.value)) {\n      this.removeItem(item.value)\n      return\n    }\n    while (field.length >= this.max) {\n      field.pop()\n    }\n\n    field.push(item.value)\n    setField(field)\n  }\n\n  removeItem(itemValue) {\n    const field = getField()\n    field.splice(field.indexOf(itemValue), 1)\n    setField(field)\n  }\n\n  get max() {\n    let max = fieldInfo.cardinality.max\n    if (max) {\n      max = max.toLowerCase() === 'n' ? 10000 : parseInt(max)\n    } else {\n      max = 1\n    }\n    return max\n  }\n\n  get min() {\n    const min = fieldInfo.cardinality.min\n    return min ? parseInt(min) : 0\n  }\n\n}","css":"._CSS-multiselect {\n  display: flex;\n  align-items: center;\n  flex-wrap: wrap;\n}\n\n._CSS-multiselect > * {\n  overflow-x: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n._CSS-button-add {\n  display: inline-block;\n  padding: 0.3rem;\n}\n\n._CSS-button-icon {\n  color: var(--ae-theme-text-icon-on-background);\n}\n\n._CSS_button-caret {\n  margin-left: auto;\n  overflow: visible !important;\n}\n\n._CSS-multiselect__item {\n  display: flex;\n}\n\n._CSS-multiselect__item-text {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n\n._CSS ._CSS-button {\n  background: var(--ae-ui-background);\n  border: var(--ae-border-width) solid var(--ae-border-color);\n}","description":"","device":"ALL","label":"","limitTypes":true,"name":"select","namespace":"","packages":[],"parameters":[],"standalone":"FIELD","template":"\n<!-- <ae-control v-if=\"Aeppic.LocalStorage.getValue('appLayoutId')\" name=\"rd:select\" :document=\"document\" :params=\"params\" :readonly=\"readonly\"></ae-control> -->\n<template v-if=\"locals.ready\">\n  <template v-if=\"!fieldInfo.cardinality\">\n    <!-- <div v-if=\"readonly\" class=\"ae-form-cell__input ae-form-cell__input--disabled\">\n      <ae-design v-if=\"controller.isRef\" name=\"title\" :document-id=\"field.id\"></ae-design>\n      <span v-else>{{ controller.item(field).display }}</span>\n    </div> -->\n\n    <template xv-else>\n      <div v-if=\"locals.items.length <0\" class=\"ae-form-cell__input\" style=\"width: 100%;\">\n        <template v-if=\"controller.isRef\">\n          <button :disabled=\"readonly\" class=\"ae-button _CSS-button\" v-for=\"item in locals.items\" :class=\"{ 'ae-button--secondary': item.id === field.id }\" @click.stop=\"controller.onInput(item.value)\">\n            <ae-design name=\"title\" :document=\"item\" :watch=\"false\"></ae-design>\n          </button>\n        </template>\n        <button :disabled=\"readonly\" class=\"ae-button _CSS-button\" v-else v-for=\"(item, index) in locals.items\" :class=\"{ 'ae-button--secondary': item.value === field && item.display, 'ae-button--tertiary': item.value === field && !item.display }\" @click.stop=\"controller.onInput(item.value)\">\n          <template v-if=\"item.display\">\n            {{ item.display }}\n          </template>\n          <i v-else>\n            leer\n          </i>\n        </button>\n      </div>\n      <select v-else :value=\"field\" style=\"width: 100%;\" class=\"ae-form-cell__input\" @input=\"controller.onInput($event.target.value)\" :disabled=\"readonly\" :required=\"fieldInfo.required\">\n        <template v-if=\"controller.isRef\">\n          <option v-for=\"item in locals.items\" :key=\"item.id\" :value=\"item.id\" :selected=\"item.id === field.id\">\n            <ae-design name=\"title\" :document=\"item\" :watch=\"false\"></ae-design>\n          </option>\n        </template>\n        <option v-else v-for=\"item in locals.items\" :key=\"item.value\" :value=\"item.value\" :selected=\"item.value === field\">\n          {{ item.display }}  \n        </option>\n      </select>\n    </template>\n  </template>\n\n  <template v-else>\n    <div v-if=\"readonly\" class=\"ae-form-cell__input ae-form-cell__input--disabled _CSS-multiselect\" >\n      <template v-for=\"(value, index) in controller.fieldValues\">\n        <ae-design v-if=\"controller.isRef\" name=\"title\" :document-id=\"value.id\"></ae-design>\n        <template v-else>\n          <span class=\"_CSS-multiselect__item-text\">\n            {{ controller.item(value).display }}\n          </span>\n        </template>\n        <span class=\"_CSS-multiselect__item-text\" v-if=\"controller.fieldValues.length > 1 && index !== controller.fieldValues.length -1\">,&nbsp;</span>\n      </template>\n    </div>\n    <div v-else ref=\"select\"\n                @click.stop.prevent=\"controller.showPicker({ target: $refs.select })\"\n                class=\"ae-form-cell__input _CSS-multiselect\"\n                :required=\"fieldInfo.required\"\n                :class=\"{ 'ae-form-cell__input--invalid': controller.isInvalid, 'ae-form-cell__input--disabled': readonly }\"\n    >\n      <template v-for=\"(value, index) in controller.fieldValues\">\n        <div v-if=\"value !== null\" :key=\"value.id || value\" class=\"_CSS-multiselect__item\">\n          <ae-design v-if=\"controller.isRef\" :document-id=\"value.id\" name=\"title\" class=\"_CSS-multiselect_ae-design _CSS-multiselect__item-text\"></ae-design>\n          <span v-else class=\"_CSS-multiselect__item-text\">{{ controller.item(value).display }}</span>\n          <span class=\"_CSS-multiselect__item-text\" v-if=\"controller.fieldValues.length > 1 && index !== controller.fieldValues.length -1\">,&nbsp;</span>\n        </div>\n        <div v-else :key=\"value.id || value\"></div>\n      </template>\n      <div class=\"_CSS_button-caret\" v-show=\"!readonly\">\n        <ae-icon class=\"fa fa-caret-down _CSS-button-icon\"></ae-icon>\n      </div>\n\n    </div>\n  </template>\n\n  <span v-if=\"controller.isInvalid\" style=\"color:var(--ae-warning)\">{{label}} ist erforderlich</span>\n\n  <ae-layout v-if=\"fieldInfo.cardinality\"\n    ref=\"popup\"\n    layout-id=\"ed2cc4c2-d2a0-4969-8da7-49fb3d539cc9\"\n    param-list-id=\"825406d7-f640-4f9b-abef-ad5d370dd732\"\n    :param-nofilter=\"true\"\n    :params=\"{ readonly: readonly, items: locals.items, selectedItems: controller.fieldItems,  keepOpenAfterSelection: params.keepOpenAfterSelection }\"\n    @selected=\"controller.selected($event)\"\n    ></ae-layout>\n\n  <ae-layout v-if=\"fieldInfo.cardinality\"\n    ref=\"dialog\"\n    layout-id=\"32dbb3b6-f883-4647-8d21-6f1e0fb83b90\"\n    param-list-id=\"825406d7-f640-4f9b-abef-ad5d370dd732\"\n    :param-nofilter=\"true\"\n    :params=\"{ readonly: readonly, items: locals.items, selectedItems: controller.fieldItems, label: label,  keepOpenAfterSelection: params.keepOpenAfterSelection }\"\n    @selected=\"controller.selected($event)\"\n    ></ae-layout>\n</template>","types":["select_any"]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"f2dd1e4e-632f-48ec-b835-f5c0a11160df","inheritedLocks":[null,null,null,null],"locks":[],"modified":{"at":"2024-07-17T09:53:27+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"p":"91f205bc-9989-4fbe-8966-bd4ba9777c26","previous":{"v":"0ae790aaf48b289c"},"stamps":[],"t":1721210007473,"v":"9b65aebef22e817b"},{"id":"f5da9a8e-29e9-4f67-813b-f135b282fd3c","v":"87adabd31ca3eea7","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"ae","name":"select","template":"<div v-if=\"locals.ready\" class=\"relative flex flex-row items-center\">\n  <div v-if=\"fieldInfo.tags['colored-square']\" class=\"w-9 h-9 border border-gray-400 rounded mr-2\" :style=\"'background-color:' + fieldInfo.tags['colored-square']\"></div>\n  <div ref=\"input\" \n    v-if=\"locals.isMultiSelect\"\n    tabindex=\"0\"\n    @click=\"controller.open($event)\"\n    @keydown.enter.prevent.stop=\"controller.enterOnInput($event)\"\n    @keydown.down.prevent.stop=\"controller.downOnInput($event)\"\n    @keydown=\"controller.find($event)\"       \n    class=\"mt-1 relative w-full cursor-default min-h-9 rounded-md py-2 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6 flex flex-wrap gap-2 hover:bg-gray-50\"\n    :class=\"(params.readonly ?? readonly) ? '' : 'bg-white'\"\n    aria-haspopup=\"listbox\" \n    :aria-expanded=\"locals.popupOpen\"\n    >\n    <div \n      v-for=\"selectedOption of controller.getSelectedOptions()\"\n      class=\"inline-flex items-center gap-x-0.5 rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10\">\n      <label v-if=\"selectedOption.tags?.['colored-pill']\" aria-label=\"Pink\" class=\"relative mr-2 text-sm -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 ring-current focus:outline-none\"\n              :style=\"{ 'color': selectedOption.tags?.['colored-pill'] }\"\n        >\n        <span aria-hidden=\"true\" class=\"h-4 w-4 rounded-full border border-black border-opacity-10 bg-current\"></span>\n      </label>\n      <span>{{selectedOption.display}}</span>\n      <button type=\"button\"  \n        v-if=\"!(params.readonly ?? readonly)\"\n        class=\"group relative -mr-1 h-3.5 w-3.5 rounded-sm hover:bg-gray-500/20\"\n        @click=\"controller.deselectOption(selectedOption)\"\n        @keydown.enter.prevent.stop=\"controller.deselectOption(selectedOption)\"\n        > \n        <span class=\"sr-only\">Remove</span>\n        <svg viewBox=\"0 0 14 14\" class=\"h-3.5 w-3.5 stroke-gray-600/50 group-hover:stroke-gray-600/75\">\n          <path d=\"M4 4l6 6m0-6l-6 6\" />\n        </svg>\n        <span class=\"absolute -inset-1\" />\n      </button>\n      <span class=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2\">\n        <svg class=\"h-5 w-5 text-gray-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n          <path fill-rule=\"evenodd\" d=\"M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z\" clip-rule=\"evenodd\" />\n        </svg>\n      </span>\n    </div>\n  </div>\n  <button ref=\"input\" \n    v-else\n    type=\"button\" \n    @click=\"controller.open($event)\"\n    @keydown.enter.prevent.stop=\"controller.enterOnInput($event)\"\n    @keydown.down.prevent.stop=\"controller.downOnInput($event)\"\n    @keydown=\"controller.find($event)\"       \n    class=\"relative w-full cursor-default min-h-8 rounded-md py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6 hover:bg-gray-50\"\n    :class=\"(params.readonly ?? readonly) ? '' : 'bg-white'\"\n    aria-haspopup=\"listbox\" \n    :aria-expanded=\"locals.popupOpen\">\n    <div class=\"flex gap-2\">\n      <div class=\"flex flex-col flex-1 overflow-hidden\">\n        <div class=\"flex flex-row items-center\">\n          <label v-if=\"controller.getCurrentOption().tags?.['colored-pill']\" aria-label=\"Pink\" class=\"relative mr-2 text-sm -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 ring-current focus:outline-none\"\n            :style=\"{ 'color': controller.getCurrentOption().tags?.['colored-pill'] }\"\n          >\n            <span aria-hidden=\"true\" class=\"h-4 w-4 rounded-full border border-black border-opacity-10 bg-current\"></span>\n          </label>\n          <span v-if=\"!controller.isEmptySelection(controller.getCurrentOption())\" class=\"block truncate font-normal flex-1 \">{{controller.getCurrentValueLabel()}}</span>\n          <span v-else class=\"truncate italic font-normal text-gray-400 flex-1\">{{locals.emptyOptionText}}</span>\n        </div>\n        <template v-if=\"controller.getShortHelpForOption(controller.getCurrentOption())\">\n          <span class=\"truncate text-ellipsis text-gray-400\" v-html=\"controller.getShortHelpForOption(controller.getCurrentOption()).html\"></span>\n        </template>\n        <span class=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2\">\n          <svg class=\"h-5 w-5 text-gray-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n            <path fill-rule=\"evenodd\" d=\"M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z\" clip-rule=\"evenodd\" />\n          </svg>\n        </span>\n      </div>\n      <button  v-if=\"controller.doesOptionHaveLongHelp(controller.getCurrentOption())\" \n        tabindex=\"-1\"\n        class=\"flex flex-col block m-auto p-2\"\n        :class=\"'text-gray-400'\"\n        @mouseenter.prevent.stop=\"controller.openDetailedHelp(controller.getCurrentOption(), $event)\"\n        @mouseleave.prevent.stop=\"$refs.detail.close()\"\n        @click.prevent.stop=\"controller.openDetailedHelp(controller.getCurrentOption(), $event)\"\n        >\n        <ae-icon class=\"far fa-info-circle\"></ae-icon>\n      </button>\n    </div>\n  </button>   \n  <ae-popup ref=\"popup\" theme=\"\"\n    :arrow=\"false\"\n    placement=\"bottom-start\"\n    :close-delay=\"150\"\n    :max-width=\"1600\"\n    @close=\"locals.popupOpen = false\"\n    @show=\"controller.onOpen()\"\n    >\n    <!--\n      Select popover, show/hide based on select state.\n\n      Entering: \"\"\n        From: \"\"\n        To: \"\"\n      Leaving: \"transition ease-in duration-100\"\n        From: \"opacity-100\"\n        To: \"opacity-0\"\n    --> \n    <ul  slot=\"popup\"\n      class=\"max-h-80 z-20 border shadow-lg overflow-y-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm\"\n      tabindex=\"-1\"\n      role=\"listbox\"\n      aria-activedescendant=\"listbox-option-3\"\n      @mouseenter=\"controller.popupEnter()\"\n      @blur=\"controller.close()\"\n      @keydown.escape.prevent.stop=\"controller.close()\"\n      @keydown.down.prevent.stop=\"controller.down($event)\"\n      @keydown.up.prevent.stop=\"controller.up($event)\"\n      @keydown.enter.prevent.stop=\"controller.select()\"\n      @keydown=\"controller.find($event)\"\n      >\n\n      <!--\n        Select option, manage highlight styles based on mouseenter/mouseleave and keyboard navigation.\n\n        Highlighted: \"bg-indigo-600 text-white\", Not Highlighted: \"text-gray-900\"\n      -->\n      <li v-for=\"(option,index) of controller.getOptions()\"\n        ref=\"option\"\n        class=\"relative min-h-8 grid grid-flow-col gap-1 items-center cursor-default select-none py-2 pl-3 pr-1\"\n        :class=\"(controller.doOptionsHaveLargeHelp() ? 'grid-cols-[32px_1fr_32px]' : 'grid-cols-[32px_1fr]') + (controller.isHighlightedOption(option) ? ' text-white bg-indigo-600' : ' text-gray-900')\"\n        :style=\"{ width: controller.getPopupWidth() }\" \n        :key=\"option.value\"\n        role=\"option\"\n        :tabindex=\"controller.isHighlightedOption(option) ? 0 : -1\"\n        :title=\"option.display\"\n        @focus=\"controller.mark(option)\"\n        @click=\"controller.select(option)\"\n        @keydown.enter.prevent.stop=\"controller.select(option)\"\n        @mouseenter=\"controller.mark(option)\"\n        @mouseleave=\"controller.unmark(option)\"\n        @keydown.f1.prevent.stop=\"controller.openDetailedHelp(option, $event)\"          \n        >\n      \n        <!-- \n          Checkmark, only display for selected option.\n        -->\n        <span v-if=\"controller.isSelectedOption(option)\"\n          class=\"inset-y-0 flex items-center\"\n          :class=\"controller.isHighlightedOption(option) ? 'text-white' : 'text-indigo-600'\">\n          <svg class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n            <path fill-rule=\"evenodd\" d=\"M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z\" clip-rule=\"evenodd\" />\n          </svg>\n        </span>\n        <span v-else>&nbsp;</span>\n\n        <div class=\"flex flex-col overflow-hidden w-full\">\n          <div class=\"flex flex-row\">\n            <label v-if=\"option.tags?.['colored-pill']\" aria-label=\"Pink\" class=\"relative mr-2 text-sm -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 ring-current focus:outline-none\"\n                :style=\"{ 'color': option.tags?.['colored-pill'] }\"\n              >\n              <span aria-hidden=\"true\" class=\"h-4 w-4 rounded-full border border-black border-opacity-10 bg-current\"></span>\n            </label>\n          \n            <span v-if=\"!controller.isEmptySelection(option)\"\n              class=\"block truncate font-normal\"\n              :class=\"controller.isSelectedOption(option) ? 'font-bold': ' font-normal'\"\n              >{{option.display}}</span>\n            <span v-else class=\"block truncate italic font-normal text-gray-400\">{{locals.emptyOptionText}}</span>\n          </div>\n\n          <template v-if=\"controller.getShortHelpForOption(option)\">\n            <span class=\"block truncate text-gray-400\" v-html=\"controller.getShortHelpForOption(option).html\"></span>\n          </template>\n        </div>\n\n          <template v-if=\"controller.doOptionsHaveLargeHelp() && $refs.option\">\n          <button  \n            v-if=\"controller.doesOptionHaveLongHelp(option)\"\n            tabindex=\"-1\"\n            class=\"flex flex-col block m-auto \"\n            :class=\"controller.isHighlightedOption(option) ? 'text-white' : 'text-gray-400'\"\n            @click.prevent.stop=\"controller.openDetailedHelp(option, { target: $refs.option[index] })\"\n            @mouseenter.prevent.stop=\"controller.openDetailedHelp(option, { target: $refs.option[index] })\"\n            >\n              <ae-icon class=\"far fa-info-circle\"></ae-icon>\n          </button>\n          <div v-else></div>\n        </template>\n\n      </li>\n    </ul>\n  </ae-popup>\n\n  <ae-popup ref=\"detail\" :arrow=\"false\" :close-delay=\"50\" :max-width=\"1200\" >\n    <div slot=\"popup\"\n      v-if=\"locals.helpDetailsOption && controller.getLongHelpForOption(locals.helpDetailsOption)\"\n      class=\"w-full overflow-auto rounded-md bg-white p-3 text-base shadow-lg \"\n      @mouseenter.prevent.stop=\"controller.openDetailedHelp(controller.getCurrentOption(), $event)\"\n      v-html=\"controller.getLongHelpForOption(locals.helpDetailsOption).html\">\n    </div>\n  </ae-popup>\n</div>","css":"","controller":"async init() {\n  locals.ready = false\n  locals.forms = null\n  locals.highlightedOption = null\n  locals.emptyOptionText = params.placeholder?.value ?? 'Leer'\n  locals.helpDetailsOption = null\n  locals.popupOpen = false\n  locals.isMultiSelect = Array.isArray(getField())\n  \n  try {\n    locals.form = await Aeppic.getFormForDocument(document)\n  } finally {\n    locals.ready = !!locals.form\n  } \n\n  this.lastKeyFindPress = 0\n  locals.lastKeyFindTerm = ''\n} \n\nasync find(event) {\n  const findTerm = this.buildFindTerm(event)\n\n  if (!findTerm) {\n    return\n  }\n\n  event.preventDefault()\n  event.stopPropagation()\n\n  if (locals.popupOpen) { \n    this.markNextMatch(findTerm)\n    this.pendingMark = null\n  } else {\n    this.pendingMark = findTerm\n    this.open(event)\n  }\n}\n\ngetSelectedOptions() {\n  let selectedValues = getField()\n\n  return selectedValues.map(v => fieldInfo.settings.options.find(o => o.value === v) ?? 'unknown')\n}\n\nmarkNextMatch(findTerm) {\n  console.log('searching for', findTerm)\n  let findWords = findTerm.split(' ').map(t => t.trim()).filter(t => t.length > 0).map(t => t.toLocaleLowerCase())\n\n  if (!findWords || findWords.length == 0) {\n    return\n  }\n\n  let firstWord = findWords[0]\n\n  // Find with prefix search first \n  let fullList = fieldInfo.settings.options\n  let list = fullList.filter(o => o.display.toLocaleLowerCase().startsWith(firstWord))\n\n  if (list.length === 0) { \n    list = fullList\n  }\n\n  for (const term of findWords) {\n    list = list.filter(o => o.display.toLocaleLowerCase().includes(term))\n  }\n\n  if (list.length > 0) {\n    this.focusOption(list[0])    \n  }\n}\n\nbuildFindTerm(event) {\n  const now = Date.now()\n  const timeSinceLastKeyFind = now - this.lastKeyFindPress\n  this.lastKeyFindPress = now\n\n  const MAX_DURATION_BETWEEN_KEY_PRESSES_TO_EXTEND_SEARCH = params.keyClearanceTime ?? 450 \n\n  if (timeSinceLastKeyFind > MAX_DURATION_BETWEEN_KEY_PRESSES_TO_EXTEND_SEARCH) {\n    locals.lastKeyFindTerm = ''\n  }\n\n  if (event.key?.length != 1) {\n    return null\n  }\n\n  locals.lastKeyFindTerm += event.key\n\n  return locals.lastKeyFindTerm\n}\n  \nopenDetailedHelp(option, event) {\n  locals.helpDetailsOption = option\n  $refs.detail?.show(event)\n}\n\ndoOptionsHaveLargeHelp() {\n  return fieldInfo.settings.options.some(o => this.doesOptionHaveLongHelp(o))\n}\n\ndoesOptionHaveLongHelp(option) {\n  let text = this.getLongHelpForOption(option)?.text?.trim() \n\n  if (!text) {\n    return false\n  }\n\n  return text.trim().length > 0\n}\n\ndoesOptionHaveHelp(option) {\n  return this.getShortHelpForOption(option)?.text?.trim() != ''\n}\n\ngetShortHelpForOption(option) {\n  return option.help\n}\n\ngetLongHelpForOption(option) {\n  return option.helpExtended\n}\n\nmark(option) {\n  locals.highlightedOption = option\n  locals.helpDetailsOption = option\n}\n\nunmark(option) {\n  if (option === locals.highlightedOption) {\n    locals.highlightedOption = null\n  }\n\n  if (option == locals.highlightedOption) {\n    locals.helpDetailsOption = null\n  }\n\n}\n\ndown(option) {\n  this.move(1)\n}\n\nup() {\n  this.move(-1)\n}\n\nmove(inc) {\n  let numberOfOptions = fieldInfo.settings.options.length\n  \n  if (numberOfOptions === 0) {\n    return\n  } \n\n  let index = fieldInfo.settings.options.findIndex(o => o === locals.highlightedOption)\n\n  if (index<0) {\n    index = 0\n  }\n\n  if (index === 0 && inc < 0) {\n    $refs.input.focus()\n    this.close()\n    return\n  }\n\n  let newIndex = Math.min(Math.max(0, index + inc), numberOfOptions-1)\n  let option = fieldInfo.settings.options[newIndex]\n\n  $refs.option[newIndex].focus()\n\n  this.mark(option)\n}\n\nfocusFirstOption() {\n  this.focusOption(fieldInfo.settings.options[0])\n}\n\nfocusCurrentOption() {\n  let value = this.getCurrentValue()\n  let option = this.findOption(value)\n\n  this.focusOption(option)\n}\n\nfocusOption(option) {\n  let index = fieldInfo.settings.options.findIndex(o => o === option)\n\n  if (index>=0) {\n    this.mark(fieldInfo.settings.options[index])\n    $refs.option?.[index]?.focus()\n  }\n}\n\nselect(option) {\n  if (params.readonly ?? readonly) {\n    return\n  }\n\n  if (!option) {\n    option = locals.highlightedOption\n  }\n\n  if (option) {\n    if (locals.isMultiSelect) {\n      let values = getField()\n      const alreadySelected = values.some(v => v === option.value)\n\n      if (alreadySelected) {\n        this.deselectOption(option)\n      } else {\n        let values = getField()\n        values.push(option.value)\n\n        // sort\n        let sorted = []\n        for (const o of fieldInfo.settings.options) {\n          if (values.some(v => v === o.value)) {\n            sorted.push(o.value)\n          }\n        }\n\n        setField(sorted)\n      }\n    } else {\n      setField(option.value)\n    }\n  }\n\n  this.closeAfterSelect()\n}\n\ndeselectOption(option) {\n  if (locals.isMultiSelect) {\n    let values = getField()\n    values = values.filter(v => v !== option.value)\n    setField(values)\n\n    if (values.length === 0) {\n      this.close()\n    }\n  }\n}\n\ncloseAfterSelect() {\n  if (params['keep-open-after-selection'] ) {\n    return\n  }\n\n  $refs.input?.focus()\n  this.close()\n}\n\nisEmptySelection(option) {\n  return option.display.trim() === '' && fieldInfo.settings.options[0] === option\n}\n\nisCurrentOption(option) {\n  return option.value === this.getCurrentValue()\n}\n\nisSelectedOption(option) {\n  const value = getField()\n\n  if (Array.isArray(value)) {\n    return value.some(v => v === option.value)\n  } else {\n    return value === option.value\n  }\n\n}\n\nisHighlightedOption(option) {\n  return option === locals.highlightedOption \n}\n\ngetCurrentValue() {\n  return getField()\n}\n\ngetCurrentOption() {\n  let value = this.getCurrentValue()\n  return this.findOption(value)\n}\n\nfindOption(value) {\n  const o = fieldInfo.settings.options.find(o => o.value === value)\n\n  if (!o) {\n    console.log('UNKNOWN SELECT VALUE IN FIELD',value,document)\n    return {\n      label: 'UNKNOWN',\n      value\n    }\n  }\n\n  return o\n}\n\ngetCurrentValueLabel() {\n  const option = this.getCurrentOption()\n  return option?.display ?? ''\n} \n\ngetOptions() {\n  return fieldInfo?.settings?.options ?? []\n}\n\nhasInput() {\n  return !!$refs.input\n}\n\ngetPopupWidth() {\n  const readToRefresh = locals.popupOpen\n  return $refs.input?.offsetWidth + 'px' ?? ''\n}\n\nasync openFromKeyboard(event) {\n  this.open(event)\n  await Aeppic.nextRenderPass()\n  \n  if (!locals.highlightedOption) {\n    this.focusFirstOption()\n  }\n}\n\nasync enterOnInput(event) {\n  locals.highlightedOption = null\n  if (!locals.popupOpen) {\n    if (!locals.isMultiSelect) {\n      locals.highlightedOption = this.findOption(getField())\n    }\n    return this.openFromKeyboard(event)\n  }\n\n  this.select()\n}\n\nasync downOnInput(event) {\n  if (!locals.popupOpen) {\n    return this.openFromKeyboard(event)\n  }\n\n  this.down()\n}\n\nasync open(event) {\n  $refs.popup?.show({ target: $refs.input })\n}\n\nasync close() {\n  locals.highlightedOption = null\n  locals.lastKeyFindTerm = 0\n  locals.lastKeyFindPress = 0\n  locals.helpDetailsOption = null\n\n  $refs.detail?.close()\n  $refs.popup?.close()\n\n  await Aeppic.nextRenderPass()\n\n  $refs.input?.focus()\n}\n\npopupEnter() {\n  $refs.popup?.cancelClose()\n} \n\nonOpen() {\n  locals.popupOpen = true\n\n  if (this.pendingMark) {\n    this.markNextMatch(this.pendingMark)\n    this.pendingMark = null\n  } else if (locals.highlightedOption) {\n    console.log('here')\n    this.focusOption(locals.highlightedOption)\n  } else {\n    this.focusFirstOption()\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-07-17T06:15:46+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"modified":{"at":"2024-07-17T06:15:58+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"t":1721209936849,"ct":1721209936849,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"919d319226a6eb09","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"3f1f5d92-5331-4264-96d0-e70e9de18038","v":"3531776eb2c9dd8e","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"signature","template":"<ae-layout   v-if=\"!readonly\"\n            layout-id=\"a2cd7699-9b46-494f-8f89-cb1f62151d82\"\n            param-text=\"Unterschreiben\"\n            param-icon=\"fa fa-signature\"\n           @selected=\"controller.showSignatureDialog()\"\n           class=\"w-full\"\n           param-classes=\"w-full\"\n></ae-layout>\n\n<div v-if=\"locals.imageSrc\">\n  <img :src=\"locals.imageSrc\" class=\"_CSS-signature-image\">\n</div>\n\n<ae-dialog ref=\"signatureDialog\" :full-screen=\"true\">\n  <signature ref=\"signature\" slot=\"body\" v-model=\"locals.newSignature\" @ok=\"controller.onSignatureOk()\"\n    @cancel=\"controller.onSignatureCancel()\" style=\"position:absolute;margin:0\"></signature>\n</ae-dialog>","css":".ae-signature--button {\n    cursor: pointer;\n    text-decoration: none;\n    color: var(--ae-background-900);\n    border: none;\n    border-radius: var(--ae-border-radius);\n    padding: var(--ae-size-2) var(--ae-size-3);\n    margin: 2px;\n    background-color: transparent;\n\n    text-align: inherit;\n\n    --fa-primary-color: var(--ae-primary-400);\n  }\n\n    .ae-signature--button.ae-signature--button:focus:not(:disabled),\n  .ae-signature--button.ae-signature--button:hover:not(:disabled) {\n    background-color: var(--ae-background-300);\n    /* color: var(--ae-primary-900); */\n  }\n\n  .ae-signature--button.ae-signature--button--save {\n    background-color: var(--ae-primary-400);\n      --fa-primary-color: var(--ae-background-300);\n  }\n\n\n._CSS-signature-image {\n  margin:var(--ae-space-2);\n  max-height:150px;\n  max-width:80%;\n}","controller":"class Controller {\n  init(){ \n    locals.newSignature = null\n    locals.imageSrc = Aeppic.Content.getSrc(document, fieldName)\n  } \n\n  async showSignatureDialog(){\n    $refs.signatureDialog.show() \n    \n    await Aeppic.nextRenderPass()\n    $refs.signature.editSignature()\n  }\n\n  onSignatureOk(){  \n    const field = Object.assign({}, locals.newSignature)\n\n    setField(field)\n    locals.imageSrc = Aeppic.Content.getSrc(document, fieldName)\n    \n    $refs.signatureDialog.close() \n  }\n\n  onSignatureCancel(){\n    $refs.signatureDialog.close()\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"cc8eaf49-0446-4215-b8ef-9465c834b96e","v":"6ae11f3235c0ccec","text":"Signature"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-10-26T13:06:01+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","from":"","client":"server"},"modified":{"at":"2022-04-06T14:24:33+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936849,"ct":1721209936849,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"c340daabba298588","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"7f4e9e44-5e7e-49f4-a75b-6902cbe37609","v":"79596aad9633a9e8","previous":{"v":"ac41484e45e32175"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"signature","template":"<div class=\"border rounded-md p-2 min-h-[2em]\" \n    :class=\"[validationErrors?.required ? 'border-red-500' : '']\"\n    data-ae-form-field-input\n    ref=\"container\"\n    :data-test=\"fieldName\"\n>\n  <div class=\"w-full relative flex flex-col gap-4\">\n    <template v-if=\"!readonly && (!locals.newSignature?.dataUrl && !field.dataUrl)\">\n      <!-- FIXME: width has to be absolute. 100% or changing after mounted does not always work -->\n      <signature   ref=\"signature\" \n                  v-if=\"locals.containerWidth\"  \n                  :value=\"locals.newSignature\"\n                  @input=\"controller.saveSignature($event)\"\n                  height=\"120px\"\n                   :width=\"locals.containerWidth + 'px'\"\n                  class=\"bg-transparent cursor-cell\tw-full\"  \n                  @begin-stroke=\"controller.onStarted()\"     \n      ></signature> \n  \n      <div v-show=\"!locals.started\" class=\"pointer-events-none\t absolute top-0 bottom-0 left-0 right-0 xbg-red-200 text-neutral-500 flex gap-2 justify-center items-center italic\">\n        <ae-icon class=\"far fa-signature\"></ae-icon>\n        {{ Aeppic.translate('signature:sign-here') }}\n      </div>\n    </template>\n    <div v-else-if=\"field.dataUrl\" class=\"h-[120px] flex-1 flex items-center\"\n        :xstyle=\"{ 'background-image': 'url(' + locals.imageSrc + ')' }\"\n    >\n      <img :src=\"locals.imageSrc\" class=\"h-[74px] max-w-[440px]\"> \n    </div>\n \n    <div class=\"flex min-h-[2.1rem]\" v-if=\"!readonly\">  \n      <ae-layout v-if=\"locals.started\"  \n                  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n                  :param-title=\"Aeppic.translate('signature:save')\"\n                  param-icon=\"far fa-save\"\n                  :param-text=\"Aeppic.translate('save')\"\n                  @selected=\"$refs.signature.save()\"\n                  :param-primary=\"true\"\n                  param-border=\"border border-secondary-300\"\n                  data-test=\"save-signature-button\"\n      ></ae-layout>\n      <div class=\"flex-1\"></div>\n\n      <ae-layout v-if=\"locals.started\"  \n                  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n                  :param-text=\"Aeppic.translate('abort')\"\n                  param-icon=\"far fa-times\"\n                  @selected=\"controller.reset()\"\n                  param-border=\"border border-secondary-300\"\n      ></ae-layout>\n      <div v-if=\"field.dataUrl\" class=\"flex-1\"></div>\n      <ae-layout v-if=\"field.dataUrl\"  \n                  layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n                  :param-title=\"Aeppic.translate('signature:delete')\"\n                  param-icon=\"far fa-trash\"\n                @selected=\"controller.deleteSignature()\"\n      ></ae-layout>\n    </div>\n  </div> \n</div>\n\n","css":"","controller":"class Controller {\n  async init(){ \n    locals.newSignature = null\n    locals.started = false\n    locals.containerWidth = 0\n    locals.imageSrc = Aeppic.Content.getSrc(document, fieldName)\n\n    await Aeppic.nextRenderPass()  \n    await Aeppic.nextRenderPass() \n\n    // reserve px for buttons, padding etc. (=150px)\n    const reserverd = 20\n    locals.containerWidth = ($refs.container.clientWidth - reserverd) || reserverd\n  } \n\n  saveSignature(newSignature){   \n    locals.newSignature = newSignature\n\n    if (!locals.newSignature) {\n      console.error('Signature is null')\n      return\n    }\n\n\n    const field = Object.assign({}, locals.newSignature)\n\n    setField(field)\n    locals.imageSrc = Aeppic.Content.getSrc(document, fieldName)\n\n    locals.started = false\n  }\n\n  reset(){\n    $refs.signature.reset()\n    locals.newSignature = null\n    locals.started = false\n  }\n\n  deleteSignature() {\n    setField(fieldInfo.defaultValue)\n    locals.newSignature = null\n    locals.started = false\n  }\n\n  onStarted() {\n    locals.started = true\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"a1424119-29c9-4354-a933-c0f9ac42f193","v":"dda09575f5f49cc8","text":"Signature v2"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2023-04-19T13:11:25+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"modified":{"at":"2026-05-18T08:49:34+00:00","by":"09763886-6748-4a56-8e0e-54a2293ee96b","backedBy":"","impersonatedBy":"","from":"","client":"server"},"t":1779094174389,"ct":1721209936849,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"426b144e-d77e-426c-837e-d2761dd7f647","v":"30a0088838a02708","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"speech-to-text","template":"<button class=\"ae-button\" :class=\"{ '_CSS-button--active': locals.active }\" @click=\"locals.active ? controller.stop() : controller.start()\">\n  <ae-icon class=\"fa fa-microphone\"></ae-icon>\n</button>","css":"@keyframes pulse {\n  0% {\n    background-color: var(--ae-background-100);\n  }\n  25% {\n    color: var(--ae-background-700);\n  }\n  50% {\n    background-color: var(--ae-accent-red-500);\n    color: var(--ae-accent-red-100);\n  }\n  75% {\n    color: var(--ae-background-700);\n  }\n  100% {\n    background-color:var(--ae-background-100);\n  }\n}\n\n._CSS-button--active {\n  animation: pulse 3s infinite;\n}","controller":"class Controller {\n  init() {\n    locals.active = false\n\n    this.recognition = new packages.window.SpeechRecognition({\n      continuous: true,\n      lang: this._getAccountLanguage(),\n    })\n\n    this.recognition.onresult = (event) => {\n      const result = event.results[0][0] \n      console.log(`[${control.data.name}], result:, `, result)\n\n      const field = getField()\n      const value = field + result.transcript\n      setField(value)\n\n    }\n    this.recognition.onstart = (event) => {\n      locals.active = true\n    }\n    this.recognition.onend = (event) => {\n      if (locals.active && !params.once) {\n        this.recognition.start()\n      }\n    }\n  }\n  \n  start() {\n    this.recognition.start()\n  }\n  \n  stop() {\n    locals.active = false\n\n    this.recognition.stop()\n  }\n\n  _getAccountLanguage() {\n    if (!Aeppic.Account) {\n      return\n    }\n\n    const lang = Aeppic.Account.data.uilanguage\n\n    if (lang === 'auto') {\n      return\n    }\n\n    return lang\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"10843fbf-4f0c-416b-be7d-42d6bbd0ae68","v":"5f748569e59edddc","text":"Window"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-03-28T14:02:03+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936849,"ct":1721209936849,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"72df02beec6d7a21","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"bae0e833-1015-48b8-afdb-59e872011f52","v":"cd8ff6b429a7f3fa","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"stamp","template":"<template v-if=\"document.stamps?.filter(stamp => stamp.type.id === params.id).length\">\n  <label  class=\"font-medium flex-1 text-neutral-600 dark:text-neutral-200 flex items-center gap-1\">\n    <span>\n      Stempel\n    </span>\n  </label>\n\n  <div class=\"p-2 flex flex-col gap-1 border rounded-md min-h-[2em]\">\n    <div v-for=\"stamp of document.stamps\" v-if=\"stamp.type.id === params.id\">\n      <ae-icon class=\"far fa-stamp\"></ae-icon> {{ stamp.type.text }} am {{ Aeppic.DateTime.fromISO(stamp.added).toLocaleString(Aeppic.DateTime.DATETIME_SHORT) }}\n    </div>\n  </div>\n\n\n</template>","css":"","controller":"","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-05-15T14:05:23+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-05-15T14:15:30+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936850,"ct":1721209936850,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"4edc0f7f6ee9769c","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"e7f3903b-ee3d-484d-92b3-94b10d7067c2","v":"b61a29183975316b","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"stamp","template":"<div class=\"ae-form-cell__label\">\n  Stempel\n</div>\n\n<div v-for=\"stamp of document.stamps\" v-if=\"stamp.type.id === params.id\">\n  <ae-icon class=\"fa fa-stamp\"></ae-icon> {{ stamp.type.text }} am {{ Aeppic.DateTime.fromISO(stamp.added).toLocaleString(Aeppic.DateTime.DATETIME_SHORT) }}\n</div>\n","css":"","controller":"","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-05-08T09:00:43+00:00","by":"47325375-27fb-4303-8bf5-9438a0524219","from":"","client":"server"},"modified":{"at":"2022-02-11T14:51:39+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936850,"ct":1721209936850,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"0596081089a5f90a","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"2a2684d4-954b-4d43-b81a-82e6cfe8fd33","v":"4f505ef9be574698","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"style","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:40em\" v-model=\"field\" :readonly=\"readonly\" theme=\"vs-dark\" language=\"less\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>\n\n","css":"","controller":"class Controller {\n  async getHoverContent(word) {\n    const infos = await Aeppic.get(word)\n\n    if (!infos || !infos.length) {\n      return []\n    }\n\n    const doc = infos[0]\n\n    if(!doc || !doc.data) {\n      return []\n    }\n\n    const a = doc.a || []\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`/${a.join('/')}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0a99614c3b79c920","text":"Monaco Editor"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-10-12T11:48:04+00:00","by":"8b6a59dd-6a77-47af-a0e5-8c725dc92fa1","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936850,"ct":1721209936850,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"ed7371649a519bb3","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-08-23T13:09:52+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936850,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    locals.isEditable = await this.isEditable()\n  }\n\n   async isEditable() {\n    const enabledCommandId = params.enabledCommand\n\n    if (!enabledCommandId) {\n      return true\n    }\n\n    try {\n      const enabledCommandResult = await Aeppic.Commands.execute(enabledCommandId, document, {\n        Account: Aeppic.Account\n      })\n\n      return enabledCommandResult.ok && enabledCommandResult.value && enabledCommandResult.value.ok === true\n    } catch(e) {\n      console.error(`[${control.data.name}]: enabledCommandId returned error`)\n      return false\n    }\n  }\n\n  get readonly() {\n    return !locals.isEditable || params.readonly || readonly\n  }\n\n  toggle() {\n    if (this.readonly) {\n      return\n    }\n\n    const field = getField()\n    if (field) {\n      setField(false)\n    } else {\n      setField(true)\n    }\n\n    this.unsetFields()\n  }\n\n  unsetFields() {\n    const field = getField()\n\n    const fieldNames = []\n\n    if (field && params.deleteFieldsIfTrue) {\n      fieldNames.push(...params.deleteFieldsIfTrue.split(','))\n    }\n\n    if (!field && params.deleteFieldsIfFalse) {\n      fieldNames.push(...params.deleteFieldsIfFalse.split(','))\n    }\n\n    for (const nameOfField of fieldNames) {\n      const fieldInfoOfField = form.getField(nameOfField)\n\n      this._unsetField(nameOfField, fieldInfoOfField)\n    }\n  }\n\n  _unsetField(nameOfField, fieldInfoOfField) {\n    const { type, cardinality } = fieldInfoOfField\n \n    if (cardinality) {\n      document.data[nameOfField] = []\n    } else {\n      if (type === 'string') {\n        document.data[nameOfField] = ''\n      } else if (type === 'number') {\n        document.data[nameOfField] = 0\n      } else if (type === 'boolean') {\n        document.data[nameOfField] = false\n      } else if (type === 'object') {\n        document.data[nameOfField] = {}\n      } else {\n        console.warn(`Cannot unset field ${nameOfField}`)\n      }\n    }\n  }\n\n  iconStyle(truthy) {\n    if (params.appearence) {\n      console.error('Parameter spelling: `appearence` should be `appearance`')\n    }\n\n    if (params.appearance === 'toggle' || params.appearance === 'toggle-text') {\n      if (truthy) {\n        return 'fas fa-toggle-on '\n      } else { \n        return 'far fa-toggle-off'\n      } \n    } \n\n    if (truthy) {\n      return 'fa fa-check-circle'\n    } else {\n      return 'far fa-circle'\n    }\n  }\n\n  text(truthy) {\n    if (truthy) {\n      const yesText = params.yes || params.appearance === 'toggle-text' ? Aeppic.translate('yes') : ''\n      return yesText\n    }\n\n    const noText = params.no || params.appearance === 'toggle-text' ? Aeppic.translate('no') : ''\n    return noText\n  }\n}\n\n","css":"._CSS {\n  display: flex;\n}\n\n._CSS-input {\n  background: var(--ae-ui-background);\n  width: min-content;\n  border-radius: var(--ae-border-radius);\n}","description":"","device":"ALL","label":"","limitTypes":false,"name":"switch","namespace":"rd","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<div class=\"border rounded-md min-h-[2em] flex items-center\"\n :class=\"{ 'border-red-500': validationErrors?.required, 'min-w-24': params.appearance === 'toggle-text' }\"\n data-ae-form-field-input\n :data-test=\"fieldName\"\n>\n  <div class=\"w-full p-1\"> \n    <ae-layout class=\"w-full\"\n              layout-id=\"80e59dec-29b1-45f1-8391-f5621cf9f0b2\"\n              :param-icon=\"controller.iconStyle(field)\"\n              :param-text=\"controller.text(field)\"\n              @selected=\"controller.toggle()\"\n              :param-disabled=\"controller.readonly\"\n              :data-test=\"fieldName + '_' + field\"\n    ></ae-layout>\n  </div>\n</div>\n\n\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"8070b038-0843-4bfe-884d-85fece10735f","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-07-04T12:30:28+00:00","backedBy":null,"by":"47325375-27fb-4303-8bf5-9438a0524219","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"21533e509d6ab28a"},"stamps":[],"t":1751632228884,"v":"dec0acbdb3eda0ec"},{"a":["system","controls-folder","91f205bc-9989-4fbe-8966-bd4ba9777c26"],"a_depth":3,"a_forms":["system-folder-form","repository-folder-form","repository-folder-form"],"created":{"at":"2017-08-28T17:20:09+02:00","by":"","client":"84a81545-992f-4af1-92b1-0b634ecfde6a","from":""},"ct":1721209936851,"data":{"cardinality":"SINGLE","controller":"","css":"","description":"","device":"ALL","label":"","limitTypes":true,"name":"switch","namespace":"","packages":[],"parameters":[],"standalone":"FIELD","template":"<input type=\"checkbox\" v-model=\"field\" :required=\"fieldInfo.required\" :disabled=\"readonly\" :aria-label=\"label\">","types":["boolean"]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"9eaac9a2-a1d4-4fbd-a8de-8a8aa6a4c34d","inheritedLocks":[null,null,null,null],"locks":[],"modified":{"at":"2024-07-17T09:53:41+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"p":"91f205bc-9989-4fbe-8966-bd4ba9777c26","previous":{"v":"41d668645cdcc8bc"},"stamps":[],"t":1721210021098,"v":"714f6f8660887b0a"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2019-08-22T14:13:49+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","client":"server","from":""},"ct":1721209936850,"data":{"cardinality":"BOTH","controller":"class Controller {\n  async init() {\n    locals.isEditable = await this.isEditable()\n  }\n\n   async isEditable() {\n    const enabledCommandId = params.enabledCommand\n\n    if (!enabledCommandId) {\n      return true\n    }\n\n    try {\n      const enabledCommandResult = await Aeppic.Commands.execute(enabledCommandId, document, {\n        Account: Aeppic.Account\n      })\n\n      return enabledCommandResult.ok && enabledCommandResult.value && enabledCommandResult.value.ok === true\n    } catch(e) {\n      console.error(`[${control.data.name}]: enabledCommandId returned error`)\n      return false\n    }\n  }\n\n  toggle() {\n    if (!locals.isEditable || params.readonly || readonly) {\n      return\n    }\n\n    const field = getField()\n    if (!field) {\n      setField(true)\n      return\n    }\n    setField(false)\n\n    this.unsetFields()\n  }\n\n  unsetFields() {\n    if (params.deleteFieldsIfTrue) {\n      const fieldNames = params.deleteFieldsIfTrue.split(',')\n\n      for (const nameOfField of fieldNames) {\n        const fieldInfo = form.getField(nameOfField)\n\n        this._unsetField(nameOfField, fieldInfo.type)\n      }\n    }\n\n    if (params.deleteFieldsIfFalse) {\n      const fieldNames = params.deleteFieldsIfFalse.split(',')\n\n      for (const nameOfField of fieldNames) {\n        const fieldInfo = form.getField(nameOfField)\n\n        this._unsetField(nameOfField, fieldInfo.type)\n      }\n    }\n  }\n\n  _unsetField(nameOfField, fieldType) {\n    if (fieldType === 'string') {\n      document.data[nameOfField] = ''\n    } else if (fieldType === 'number') {\n      document.data[nameOfField] = 0\n    } else if (fieldType === 'boolean') {\n      document.data[nameOfField] = false\n    } else if (fieldType === 'object') {\n      document.data[nameOfField] = {}\n    }\n  }\n\n  iconStyle(truthy) {\n    if (params.appearence) {\n      console.error('Parameter spelling: `appearence` should be `appearance`')\n    }\n\n    if (params.appearance === 'toggle') {\n      if (truthy) {\n        return 'far fa-toggle-on'\n      } else {\n        return 'far fa-toggle-off'\n      }\n    }\n\n    if (truthy) {\n      return 'fa fa-check-square'\n    } else {\n      return 'far fa-square'\n    }\n  }\n\n  get isInvalid() {\n    if (!fieldInfo.required) {\n      return false\n    }\n\n    const errors = form.validateDocumentDataField(document.data, fieldName)\n    if (!errors) {\n      return false\n    }\n\n    return true\n  }\n}\n\n","css":"._CSS {\n  display: flex;\n}\n\n._CSS-input {\n  width: min-content;\n  border-radius: var(--ae-border-radius);\n}","description":"","device":"ALL","label":"","limitTypes":false,"name":"switch","namespace":"","packages":[],"parameters":[],"standalone":"SUPPORTED","template":"<div class=\"ae-form-cell__input\" :class=\"{ 'ae-form-cell__input--invalid': controller.isInvalid }\">\n  <div class=\"_CSS-input\">\n    <template v-if=\"params.style === 'button'\">\n      <button class=\"ae-button ae-button--primary\"\n              @click.stop=\"controller.toggle()\"\n              :disabled=\"readonly || !locals.isEditable\"\n      >\n        {{ params.buttonText }}\n      </button>\n    </template>\n    <template v-else>\n      <button :disabled=\"readonly || !locals.isEditable\"\n              v-show=\"field\"\n              @click.stop=\"controller.toggle()\"\n              class=\"ae-button ae-button--small\"\n      >\n        <ae-icon class=\"input\" :class=\"{ [controller.iconStyle(true)]: true }\"></ae-icon>\n      </button>\n      <button :disabled=\"readonly || !locals.isEditable\"\n              v-show=\"!field\"\n              @click.stop=\"controller.toggle()\"\n              class=\"ae-button ae-button--small\"\n      >\n        <ae-icon class=\"input\" :class=\"{ [controller.iconStyle(false)]: true }\"></ae-icon>\n      </button>\n    </template>\n  </div>\n</div>\n\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"b78447f6-1ff5-40d8-9bcb-9d0c94dac46f","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2024-07-17T10:14:41+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"d405ec00955ea093"},"stamps":[],"t":1721211281195,"v":"bedd7b115932897d"},{"id":"47f92de7-4363-4b1c-8048-d58967bd2964","v":"9cc5258359336158","previous":{"v":"cb98761991fc81e6"},"p":"controls-folder","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"},"data":{"namespace":"","name":"tags","template":"<div class=\"flex flex-wrap gap-1 relative flex-row bg-white border border-gray-200 rounded-e-sm focus:ring-blue-500 focus:border-blue-500 block w-full dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 p-1\">\n  <span v-for=\"tag in controller.tags\" id=\"badge-dismiss-default\" class=\"inline-flex items-center px-2 py-1 text-sm font-medium text-blue-800 bg-blue-100 rounded dark:bg-blue-900 dark:text-blue-300 w-auto\">\n    {{tag}}\n    <button v-if=\"!readonly\" @click=\"controller.remove(tag)\" type=\"button\" class=\"inline-flex items-center p-1 ms-2 text-sm bg-transparent rounded-sm hover:bg-blue-200 hover:text-blue-900 dark:hover:bg-blue-800 dark:hover:text-blue-300\" data-dismiss-target=\"#badge-dismiss-default\" aria-label=\"Remove\">\n      <ae-icon class=\"fa fa-times\"></ae-icon>\n      <span class=\"sr-only\">Remove tag</span>\n    </button>\n  </span>\n  <input v-if=\"!readonly\" class=\"min-w-4\"  style=\"flex:1;width:auto;background:none;border-width:0px;border-style:solid;hover:bg-gray-700;focus:border-0\"  @blur=\"controller.update($event, true)\" @keydown=\"controller.update($event)\" @keydown.delete=\"controller.deleteLast()\" ref=\"entry\"></input>\n\n</div>\n","css":"","controller":"init() {\n\n}\n\nget tags() {\n  return getField()\n}\n\ndeleteLast() {\n  const values = getField()\n  values.pop()\n  setField(values)\n}\n\nupdate(evt, leave) {\n  if (leave || evt.key == 'Tab' || evt.key === ',' || evt.key == 'Enter') {\n    const value = $refs.entry.value\n    const tags = value.split(',').map(s => s.trim()).filter(s => !!s)\n    const knownTags = new Set([...tags, ...getField()])\n    setField(Array.from(knownTags.values()))\n    $refs.entry.value = ''\n\n    if (!leave && evt.key != 'Tab') {\n      $refs.entry.focus()\n      evt.preventDefault()\n    }\n  }  \n}\n\nremove(tag) {\n  const set = new Set(getField())\n  set.delete(tag)\n  setField(Array.from(set.values()))\n}","parameters":[],"standalone":"FIELD","cardinality":"MULTIPLE","limitTypes":true,"types":["text"],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-07-18T15:36:18+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-07-18T16:30:33+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721313033281,"ct":1721309777955,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"cloneOf":{"id":"cabe0226-5fff-4f6b-9b5a-995d802b277d","text":"text","v":"58889e5e7db6f507"},"created":{"at":"2025-07-15T08:39:36+00:00","backedBy":null,"by":"729fa027-7e7e-4b06-9602-f535c43824f0","client":"server","from":"","impersonatedBy":null},"ct":1752568776799,"data":{"cardinality":"BOTH","controller":"class Controller {\r\n  init() {\r\n    this.debug = false\r\n    this.debug && console.log(fieldInfo.cardinality)\r\n    this.debug && console.log('Initializing')\r\n    locals.add = ''\r\n    locals.values = []\r\n    locals.indexSelectedForDelete = null\r\n\r\n    this.isTyping = false\r\n    this._scheduleToForgetWeAreTyping = Aeppic.debounce(() => {\r\n      {\r\n        this.debug && console.log('We forgot we are typing')\r\n        this.isTyping = false\r\n      }\r\n    }, 5_000)\r\n \r\n    this._initializeValues(document) \r\n\r\n    Aeppic.watch(document.id, (newDocument, reason) => {\r\n      if (!reason.updated) {\r\n        return\r\n      }\r\n\r\n      const changeDoesNotAffectControlField = !newDocument.previous?.patch?.[fieldName]\r\n      const isClientSideChange = newDocument.t == null\r\n\r\n      if (changeDoesNotAffectControlField || isClientSideChange) {\r\n        return\r\n      }\r\n\r\n      // From here on, the change must have come from the server\r\n\r\n      if (this.isTyping) {\r\n        this.debug && console.log('Watcher dismissed, isTyping = true')\r\n        return\r\n      }\r\n\r\n      this.debug && console.log('Watcher Activated')\r\n\r\n      this._initializeValues(newDocument)\r\n    })\r\n  }\r\n\r\n  _initializeValues(doc) {\r\n    if (fieldInfo.cardinality) {\r\n      locals.values = doc.data[fieldName].map(t => {return { text: t }})\r\n\r\n      if (locals.values.length < fieldInfo.cardinality.min) {\r\n        const linesMissing = fieldInfo.cardinality.min - locals.values.length\r\n        const defaultValue = typeof fieldInfo.defaultValue === 'string' ? fieldInfo.defaultValue : ''\r\n        for (let i = 0; i < linesMissing; i++) {\r\n          locals.values.push({ text: defaultValue });\r\n        }\r\n      }\r\n\r\n    } else {\r\n      locals.values = [{ text: doc.data[fieldName] ?? '' }]\r\n    }\r\n    this.debug && console.log('Initialising with:', locals.values.map(value => value.text))\r\n  } \r\n\r\n  // only cardinality\r\n  add() {\r\n    if (locals.add.length === 0) {\r\n      return\r\n    }\r\n\r\n    locals.values.push({text: locals.add})\r\n\r\n    this.update()\r\n\r\n    locals.add = ''\r\n    $refs.add.focus()\r\n  }\r\n\r\n  remove(index) {\r\n    if (locals.indexSelectedForDelete === index) {\r\n      locals.indexSelectedForDelete = null\r\n      locals.values.splice(index, 1)\r\n      this.update()\r\n    } else {\r\n      locals.indexSelectedForDelete = index\r\n    }\r\n  }\r\n\r\n  _sanitizeInput(value) {\r\n    this.debug && console.log('sanitizeFunction', params.valueSanitizerFunction)\r\n\r\n    if (params.valueSanitizerFunction) {\r\n      return params.valueSanitizerFunction(value)\r\n    }\r\n\r\n    return value\r\n  }\r\n\r\n  update() {\r\n    this.debug && console.log('updating')\r\n\r\n    this._rememberWeAreTyping()\r\n\r\n    const newValues = locals.values.map(v => this._sanitizeInput(v.text))\r\n\r\n    if (fieldInfo.cardinality) {\r\n      setField(newValues)\r\n      this.debug && console.log('Setting field to:', newValues)\r\n    } else { \r\n      setField(newValues[0] || '')\r\n      this.debug && console.log('Setting field to:', newValues[0] || '')\r\n    }\r\n\r\n    //locals.values = newValues.map(v => ({ text: v }))\r\n    locals.values.forEach((value, index) => {\r\n      if (value.text !== newValues[index]) {\r\n        value.text = newValues[index];\r\n      }\r\n    })\r\n  }\r\n\r\n  clean() {\r\n    locals.values = getField().map(t => {return { text: t }})\r\n  }\r\n\r\n  _rememberWeAreTyping() {\r\n    this.debug && console.log('Remember we are typing')\r\n\r\n    this.isTyping = true\r\n    this._scheduleToForgetWeAreTyping()\r\n  }\r\n\r\n  get mayCreateLine() {\r\n    if (readonly) {\r\n      return false\r\n    }\r\n    \r\n    if (readonly || params.noAdd || !fieldInfo.cardinality) {\r\n      return false\r\n    }\r\n\r\n    const currentNumberOfLines = locals.values.length\r\n    if (currentNumberOfLines < fieldInfo.cardinality.max || fieldInfo.cardinality.max === 'n') {\r\n      return true\r\n    } else {\r\n      return false\r\n    }\r\n  }\r\n\r\n  get mayRemoveLine() {\r\n    if (readonly || params.disableRemove || !fieldInfo.cardinality) {\r\n      return false\r\n    }\r\n\r\n    const currentNumberOfLines = locals.values.length\r\n    if (currentNumberOfLines > fieldInfo.cardinality.min) {\r\n      return true\r\n    } else {\r\n      return false\r\n    }\r\n  }\r\n\r\n  get mayDrag() {\r\n    if (readonly || locals.values.length < 2 || Aeppic.Device === 'Phone') {\r\n      return false\r\n    }\r\n    return true\r\n  }\r\n\r\n  destroy() {\r\n\r\n  }\r\n\r\n}","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"text","namespace":"rd","packages":[{"id":"ed1e09cd-963e-4f7b-afce-fb70e1f84312","text":"vue-draggable","v":"33bfa1ff66dd725e"}],"parameters":[],"standalone":"FIELD","template":"<div>\r\n  <draggable \r\n    v-model=\"locals.values\" \r\n    handle=\".drag-handle\"\r\n    @change=\"controller.update()\"\r\n    class=\"flex flex-col xgap-1\"\r\n    ghost-class=\"opacity-30\"\r\n  >\r\n    <template\r\n      v-for=\"(value, index) in locals.values\"\r\n    >\r\n      <div\r\n        class=\"flex h-10 p-1\"\r\n      >\r\n        <div class=\"flex-1 flex rounded-md bg-white gap-2 h-full border ae-form-cell__input relative min-w-0\"\r\n            :class=\"[\r\n                      params.withoutBorder ? 'border-transparent' : 'border', \r\n                      readonly ? 'ae-form-cell__input--readonly' : 'focus-within:outline focus-within:outline-[2px]',\r\n                      validationErrors?.required && 'ae-form-cell__input--invalid'\r\n                    ]\"\r\n        >\r\n          <input\r\n            class=\"flex-1 h-full py-1 px-2 rounded-md min-w-0 focus:outline-none\"\r\n            type=\"text\"\r\n            v-model=\"value.text\"\r\n            @input=\"controller.update()\"\r\n            :required=\"validationErrors?.required\"\r\n            :readonly=\"readonly\"\r\n            :aria-label=\"label\"\r\n            :autocomplete=\"params.autocomplete || 'off'\"\r\n            :name=\"params.autocomplete\"\r\n            :data-test=\"fieldInfo.cardinality ? fieldName + '_' + index : fieldName\"\r\n            :maxlength=\"params.max\"\r\n          />\r\n          <div\r\n            v-if=\"params.max\"\r\n            :class=\"[Number(params.max) === value.text.length ? 'text-red-600' : 'text-secondary-400', params.max < 100 && 'w-[60px]', params.max < 1000 && params.max >= 100 && 'w-[70px]', params.max >= 1000 && 'w-[90px]']\"\r\n            class=\"text-xs pointer-events-none h-full flex items-center px-2\"\r\n          >\r\n            <span>\r\n              {{ value.text.length }} / {{ params.max }}\r\n            </span>\r\n          </div>\r\n        </div>\r\n\r\n        <div\r\n          v-if=\"controller.mayRemoveLine\"\r\n          class=\"ml-1 rounded-md flex items-center justify-center w-8 hover:bg-secondary-200\"\r\n          :class=\"[params.withoutBorder ? 'border-transparent' : 'border', locals.indexSelectedForDelete === index && 'text-red-500']\"\r\n          @click=\"controller.remove(index)\"\r\n          @mouseleave=\"locals.indexSelectedForDelete = null\"\r\n        >\r\n          <ae-icon\r\n            class=\"far fa-trash\"\r\n          >\r\n          </ae-icon>\r\n        </div>\r\n        <div\r\n          v-if=\"controller.mayDrag\"\r\n          class=\"ml-1 rounded-md flex items-center justify-center w-8 cursor-pointer drag-handle\"\r\n          :class=\"[params.withoutBorder ? 'border-transparent' : 'border']\"\r\n        >\r\n          <ae-icon\r\n            class=\"far fa-bars\"\r\n          >\r\n          </ae-icon>\r\n        </div>\r\n      </div>\r\n    </template>\r\n  </draggable>\r\n\r\n  <template\r\n    v-if=\"controller.mayCreateLine\"\r\n  >\r\n    <div\r\n      class=\"flex h-10 py-1\"\r\n    >\r\n      <div class=\"flex-1 flex rounded-md bg-white py-1 px-2 gap-2 h-full focus-within:outline focus-within:outline-[2px]\"\r\n          :class=\"[params.withoutBorder ? 'border-transparent' : 'border']\"\r\n      >\r\n        <input\r\n          ref=\"add\"\r\n          class=\"flex-1 h-full focus:outline-none placeholder:italic\"\r\n          type=\"text\"\r\n          v-model=\"locals.add\"\r\n          @blur=\"controller.add()\"\r\n          @keypress.enter.prevent=\"controller.add()\"\r\n          :required=\"validationErrors?.required\"\r\n          :aria-label=\"label\"\r\n          :autocomplete=\"params.autocomplete || 'off'\"\r\n          :name=\"params.autocomplete\"\r\n          :data-test=\"fieldInfo.cardinality ? fieldName + '_' : fieldName\"\r\n          placeholder=\"Neuen Eintrag erstellen\"\r\n          :maxlength=\"params.max\"\r\n        />\r\n        <div\r\n          v-if=\"params.max\"\r\n          :class=\"[params.max === locals.add.length ? 'text-red-600' : 'text-secondary-400']\"\r\n          class=\"text-xs pointer-events-none h-full flex items-center\"\r\n        >\r\n          <span>\r\n            {{ locals.add.length }} / {{ params.max }}\r\n          </span>\r\n        </div>\r\n      </div>\r\n    </div>\r\n  </template>\r\n</div>","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"5f28c036-ccad-4c38-aa14-475dce86fa84","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-04-13T11:40:36+00:00","backedBy":"","by":"729fa027-7e7e-4b06-9602-f535c43824f0","client":"server","from":"","impersonatedBy":""},"originalCloneOf":{"id":"cabe0226-5fff-4f6b-9b5a-995d802b277d","text":"text","v":"58889e5e7db6f507"},"p":"controls-folder","previous":{"v":"008398057bc0f8e4"},"stamps":[],"t":1776080436451,"v":"215adbaf9112aae1"},{"a":["system","controls-folder","91f205bc-9989-4fbe-8966-bd4ba9777c26"],"a_depth":3,"a_forms":["system-folder-form","repository-folder-form","repository-folder-form"],"created":{"at":"2019-09-17T12:25:22+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","client":"server","from":""},"ct":1721209936851,"data":{"cardinality":"BOTH","controller":"init() {\r\n  console.log()\r\n  locals.add = ''\r\n\r\n  if (fieldInfo.cardinality) {\r\n    locals.values = getField().map(t => {return { text: t }})\r\n  }\r\n}  \r\n\r\nadd(force = false) {\r\n  if (force || locals.add.length > 0) {\r\n    locals.values.push({ text: locals.add })\r\n    this.update()\r\n    locals.add = ''\r\n    $refs.add.focus()\r\n  }  \r\n}\r\n\r\nupdate() {\r\n  setField(locals.values.map(v => v.text).filter(t => t !== ''))  \r\n}\r\n\r\nclean() {\r\n  locals.values = getField().map(t => {return { text: t }})\r\n}\r\n\r\nlog(e){console.log(e)}","css":"._CSS {\r\n  display: flex;\r\n}\r\n._CSS-add--empty {\r\n  opacity: 0.5;\r\n}\r\n\r\n._CSS-add--empty:focus {\r\n  opacity:1;\r\n} ","description":"","device":"ALL","label":"","limitTypes":true,"name":"text","namespace":"","packages":[],"parameters":[],"standalone":"FIELD","template":"<div v-if=\"fieldInfo.tags['colored-square']\" class=\"w-9 h-9 border border-gray-400 rounded mr-2\" :style=\"'background-color:' + fieldInfo.tags['colored-square']\"></div>\r\n<input v-if=\"!fieldInfo.cardinality\" type=\"text\" v-model=\"field\" :required=\"fieldInfo.required\" :readonly=\"readonly\" :aria-label=\"label\" :autocomplete=\"params.autocomplete || 'off'\" :name=\"params.autocomplete\" :data-test=\"fieldName\">\r\n<template v-else>\r\n   <input type=\"text\" v-for=\"value of locals.values\" v-model=\"value.text\" style=\"width:100%\" :readonly=\"readonly\" :aria-label=\"label\"  @blur=\"controller.clean()\" @input=\"controller.update()\" autocomplete=\"off\">\r\n   <template v-if=\"!readonly && !params.noAdd\">\r\n     <h5>Add new entry</h5>\r\n      <input ref=\"add\" type=\"text\" style=\"width:100%\" class=\"_CSS-add\" :class=\"{'_CSS-add--empty': locals.add === ''}\" @blur=\"controller.add()\" @keypress.enter.prevent=\"controller.add()\" v-model=\"locals.add\">\r\n    </template>\r\n</template>     \r\n","types":["string"]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"e19b1580-aabc-49b4-9281-3a7546bd813a","inheritedLocks":[null,null,null,null],"locks":[],"modified":{"at":"2024-07-17T10:17:02+00:00","backedBy":null,"by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","client":"server","from":"","impersonatedBy":null},"p":"91f205bc-9989-4fbe-8966-bd4ba9777c26","previous":{"v":"1da01879e14ad85c"},"stamps":[],"t":1721211422324,"v":"fa01880b458a6499"},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-08-23T13:27:37+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936851,"data":{"cardinality":"BOTH","controller":"class Controller {\r\n  init() {\r\n    this.debug = false\r\n    this.debug && console.log('Initializing')\r\n    locals.add = ''\r\n    locals.values = []\r\n\r\n    this.isTyping = false\r\n    this._scheduleToForgetWeAreTyping = Aeppic.debounce(() => {\r\n      {\r\n        this.debug && console.log('We forgot we are typing')\r\n        this.isTyping = false\r\n      }\r\n    }, 5_000)\r\n \r\n    this._initializeValues(document) \r\n\r\n    Aeppic.watch(document.id, (newDocument, reason) => {\r\n      if (!reason.updated) {\r\n        return\r\n      }\r\n\r\n      const changeDoesNotAffectControlField = !newDocument.previous?.patch[fieldName]\r\n      const isClientSideChange = newDocument.t == null\r\n\r\n      if (changeDoesNotAffectControlField || isClientSideChange) {\r\n        return\r\n      }\r\n\r\n      // From here on, the change must have come from the server\r\n\r\n      if (this.isTyping) {\r\n        this.debug && console.log('Watcher dismissed, isTyping = true')\r\n        return\r\n      }\r\n\r\n      this.debug && console.log('Watcher Activated')\r\n\r\n      this._initializeValues(newDocument)\r\n    })\r\n  } \r\n\r\n  get fieldValues() {\r\n    const field = getField()\r\n\r\n    if (fieldInfo.cardinality) {\r\n      return field.map(t => {return { text: t }})\r\n    } else {\r\n      return [{ text: field }]\r\n    }\r\n  }\r\n\r\n  _initializeValues(doc) {\r\n    if (fieldInfo.cardinality) {\r\n      locals.values = doc.data[fieldName].map(t => {return { text: t }})\r\n    } else {\r\n      locals.values = [{ text: doc.data[fieldName] }]\r\n    }\r\n  } \r\n\r\n  // only cardinality\r\n  add() {\r\n    if (locals.add.length === 0) {\r\n      return\r\n    }\r\n\r\n    locals.values.push(locals.add)\r\n\r\n    this.update()\r\n\r\n    locals.add = ''\r\n    $refs.add.focus()\r\n  }\r\n\r\n  _sanitizeInput(value) {\r\n    if (params.valueSanitizerFunction) {\r\n      return params.valueSanitizerFunction(value)\r\n    }\r\n\r\n    return value\r\n  }\r\n\r\n  update() {\r\n    this._rememberWeAreTyping()\r\n\r\n    const newValues = locals.values.map(v => this._sanitizeInput(v.text))\r\n\r\n    if (fieldInfo.cardinality) {\r\n      setField(newValues)\r\n    } else { \r\n      setField(newValues[0] || '')\r\n    }\r\n\r\n    locals.values = newValues.map(v => ({ text: v }))\r\n  }\r\n\r\n  clean() {\r\n    locals.values = getField().map(t => {return { text: t }})\r\n  }\r\n\r\n  _rememberWeAreTyping() {\r\n    this.debug && console.log('Remember we are typing')\r\n    this.isTyping = true\r\n    this._scheduleToForgetWeAreTyping()\r\n  }\r\n}","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"text old. please delete if no issues in 2 weeks.","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<div>\r\n  <input\r\n    v-if=\"!fieldInfo.cardinality\"\r\n    class=\"w-full rounded-md py-1 px-2 flex gap-1 bg-white\"\r\n    :class=\"[!params.withoutBorder && 'border']\"\r\n    type=\"text\"\r\n    v-model=\"locals.values[0].text\"\r\n    @input=\"controller.update()\"\r\n    :required=\"validationErrors?.required\"\r\n    :readonly=\"readonly\"\r\n    :aria-label=\"label\"\r\n    :autocomplete=\"params.autocomplete || 'off'\"\r\n    :name=\"params.autocomplete\"\r\n    :data-test=\"fieldName\"\r\n  />\r\n  <div v-else class=\"rounded-md w-full\"\r\n      :class=\"[!params.withoutBorder && 'border']\"\r\n  >\r\n    <input\r\n      type=\"text\"\r\n      v-for=\"value of locals.values\"\r\n      v-model=\"value.text\"\r\n      :readonly=\"readonly\"\r\n      :aria-label=\"label\"\r\n      @blur=\"controller.clean()\"\r\n      @input=\"controller.update()\"\r\n      autocomplete=\"off\"\r\n      class=\"w-full border-b rounded-md py-1 px-2 flex gap-1 bg-white\"\r\n    />\r\n    <template v-if=\"!readonly && !params.noAdd\">\r\n      <input\r\n        ref=\"add\"\r\n        type=\"text\"\r\n        class=\"w-full border-b rounded-md py-1 px-2 flex gap-1 bg-white placeholder:italic\"\r\n        @blur=\"controller.add()\"\r\n        @keypress.enter.prevent=\"controller.add()\"\r\n        v-model=\"locals.add\"\r\n        placeholder=\"Neuen Eintrag erstellen\"\r\n      />\r\n    </template>\r\n  </div>\r\n</div>\r\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"cabe0226-5fff-4f6b-9b5a-995d802b277d","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-07-25T12:25:46+00:00","backedBy":null,"by":"729fa027-7e7e-4b06-9602-f535c43824f0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"94696889a5452b6a"},"stamps":[],"t":1753446346464,"v":"9cf38490a6b86dca"},{"id":"9daaeeea-0fb4-48e9-abd7-a72e40699212","v":"d3eb8e1297265210","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"timer","template":"<!-- <ae-control :document=\"document\"\n            :field-name=\"fieldName\"\n            name=\"duration\"\n            :readonly=\"readonly\"\n            :required=\"params.required\"\n            param-format=\"hh:mm:ss\"\n></ae-control> -->\n\n<div class=\"ae-form-cell__input\">\n{{ controller.getField() }}\n</div>\n<!-- <input type=\"text\"\n      :value=\"controller.getField()\"\n      readonly=\"readonly\"\n> -->","css":"","controller":"class Controller {\n  init() {\n    this.interval = null\n    \n    if (params.startedField && params.stoppedField) {\n      this.updateField()\n      this.startUpdatingField()\n    } else {\n      console.error(`[${control.data.name}]: params startedField AND stoppedField required`)\n    }\n  }\n\n  getField() {\n    const field = getField()\n    \n    if (!field) {\n      return ''\n    }\n\n    if (document.data[params.stoppedField]) {\n      params.debug && console.log('stop')\n      Aeppic.clearInterval(this.interval)\n    }\n\n    return Aeppic.DateTimeDuration.fromObject(field).toFormat(params.displayFormat || 'hh:mm:ss')\n  }\n\n  startUpdatingField() {\n    this.interval = Aeppic.setInterval(() => this.updateField(), params.interval || 1000)\n  }\n\n  updateField() {\n    // NOTE: do not use setField() because duration is readonly and thus setField() does not update field\n    document.data[fieldName] = this.getDuration()\n  }\n\n  getDuration() {\n    if (!params.startedField || !params.stoppedField || !document.data[params.startedField]) {\n      return {}\n    }\n\n    const durationFromStarted = Aeppic.DateTime.local().diff(Aeppic.DateTime.fromISO(document.data[params.startedField]))\n\n    params.debug && console.log(new Date(),durationFromStarted, durationFromStarted.toObject())\n\n    return durationFromStarted.shiftTo('hours', 'minutes', 'seconds').toObject() || {}\n  }\n}\n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-09-13T09:18:56+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2022-05-13T13:41:10+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936851,"ct":1721209936851,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"27d555408d387f6a","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2023-09-18T13:30:12+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936851,"data":{"cardinality":"BOTH","controller":"class Controller {\n  init() {\n    this.interval = null\n\n    if (params.startedField && params.stoppedField) {\n      this.updateField()\n      this.startUpdatingField()\n    } else {\n      console.error(`[${control.data.name}]: params startedField AND stoppedField required`)\n    }\n  }\n\n  getField() {\n    const field = getField()\n    \n    if (!field) {\n      return ''\n    }\n\n    if (document.data[params.stoppedField]) {\n      params.debug && console.log('stop')\n      Aeppic.clearInterval(this.interval)\n    }\n\n    return Aeppic.DateTimeDuration.fromObject(field).toFormat(params.displayFormat || 'hh:mm:ss')\n  }\n\n  startUpdatingField() {\n    this.interval = Aeppic.setInterval(() => this.updateField(), params.interval || 1000)\n  }\n\n  updateField() {\n    // NOTE: do not use setField() because duration is readonly and thus setField() does not update field\n    document.data[fieldName] = this.getDuration()\n  }\n\n  getDuration() {\n    if (!params.startedField || !params.stoppedField || !document.data[params.startedField]) {\n      return {}\n    }\n\n    const durationFromStarted = Aeppic.DateTime.local().diff(Aeppic.DateTime.fromISO(document.data[params.startedField]))\n\n    params.debug && console.log(new Date(),durationFromStarted, durationFromStarted.toObject())\n\n    return durationFromStarted.shiftTo('hours', 'minutes', 'seconds').toObject() || {}\n  }\n}\n","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"timer","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<!-- <ae-control :document=\"document\"\n            :field-name=\"fieldName\"\n            name=\"duration\"\n            :readonly=\"readonly\"\n            :required=\"params.required\"\n            param-format=\"hh:mm:ss\"\n></ae-control> -->\n<div  class=\"w-full border rounded-md py-1 px-2 flex gap-1 bg-white min-h-[2em]\"\n      :class=\"{ 'bg-secondary-100' : readonly }\">\n{{ controller.getField() }}\n</div>\n<!-- <input type=\"text\"\n      :value=\"controller.getField()\"\n      readonly=\"readonly\"\n> --> ","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"f49a4ec4-ebeb-4b25-9cd2-0c1c30905427","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2026-05-05T14:23:36+00:00","backedBy":"","by":"b7485498-f1f3-4ab1-87d1-9cb63ea890c8","client":"server","from":"","impersonatedBy":""},"p":"controls-folder","previous":{"v":"a2a9b5266da7a6f6"},"stamps":[],"t":1777991016963,"v":"3a50df47dfd0166f"},{"id":"962fa74e-2685-459b-ba6e-1982a5d1545d","v":"ca0625b93f4af20a","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"timetracking","template":"\n<div class=\"ae-form-cell__input\">\n\t<div class=\"ae-form-cell__label _CSS-label\" style=\"width: 100%;\">\n    Zeiterfassung\n\n<!-- \n  FIXME: DO NOT USE params.readonly directly or this will result in ongoing rerenderings of the ae-list of the entries\n    <ae-layout layout-id=\"cf3b721d-9825-40ba-8076-4e6cbaf164d1\" v-if=\"!params.readonly\" :param-document-id=\"document.id\"></ae-layout>\n\n -->\n    \n    <ae-layout layout-id=\"cf3b721d-9825-40ba-8076-4e6cbaf164d1\" v-if=\"!locals.readonly\" :param-document-id=\"document.id\"></ae-layout>\n    <ae-design name=\"timetracking:progress\" :document=\"document\" style=\"flex: 1;\"></ae-design>\n  </div>\n\n\n  <div>\n    <div class=\"ae-form-cell__label _CSS-label\" style=\"width: 100%;\">\n      <div>Erfasste Zeiten</div>\n    </div> \n    <ae-list list-id=\"2e798091-303f-406b-a42a-93aa1cd8425c\"\n            :documents=\"locals.documents\"\n            @selected=\"Aeppic.drillInto($event)\"\n            :params=\"{ design: 'line-item', designParams: { hideParentInfo: true } }\"\n    ></ae-list>\n    <div v-if=\"!locals.documents.length\" style=\"color: var(--ae-background-500);\">\n      Keine eigenen Zeiten erfasst.\n    </div>\n  </div>\n</div>\n\n","css":"._CSS-label {\n  display: flex;\n  xjustify-content: space-between;\n  align-items: center;\n}\n","controller":"class TimeTrackingController {\n  async init() {\n    locals.documents = []\n    locals.readonly = false\n\n    watchParam('readonly', (newvalue) => {\n      locals.readonly = newvalue\n    })\n     \n    Aeppic.subscribe(`p:${document.id} f.id:f9279010-1935-4139-abbb-82d928aa09dc`, { size: 1000, sort: { field: 'created.at' }}, (documents) => locals.documents = documents)\n  }\n}  \n","parameters":[],"standalone":"REQUIRED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"ef7e8b10-f09a-47a0-ba25-2aabe9661396","v":"65e30bc2db6771f6","text":"vuejs-datepicker"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2019-09-13T09:18:54+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2021-09-17T15:48:01+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936851,"ct":1721209936851,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"cab1d746f7c2dc89","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"5f0dd9ae-d208-469a-8f81-f048f99c86c4","v":"f7963047ee204080","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"title","template":"<input :title=\"field\" type=\"text\" v-model=\"field\" :required=\"fieldInfo.required\" :readonly=\"readonly\" :aria-label=\"label\" style=\"width:100%\" @input=\"controller.changed()\">\r\n","css":"._CSS > input {\r\n  background: none;\r\n  border-width: 0 0 2px 0;\r\n  border-color: var(--ae-background-300);\r\n  padding: 4px;\r\n  border-style: solid;\r\n  font-size: 2em;\r\n  text-overflow: ellipsis;\r\n  color: var(--ae-background-900);\r\n}","controller":"init() {\r\n  if (fieldInfo.cardinality) {\r\n    console.error('title control does not support cardinality')\r\n  }\r\n\r\n  this._commit = Aeppic.debounce(() => {\r\n    console.log('commit')\r\n    Aeppic.commit(document)\r\n  }, 250)\r\n}\r\n\r\nchanged() {\r\n  this._commit()\r\n}  \r\n","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-07-07T11:33:11+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2021-07-07T11:33:11+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936851,"ct":1721209936851,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"454534fa26ad6f96","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"c4bdb2db-2065-44d4-8837-6d4268b97870","v":"8ecf059c98d861e1","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"until","template":"\n<div ref=\"box\">\n  <span class=\"_CSS_to__date\" title=\"Zu erledigen bis\" :title=\"field && Aeppic.DateTime.fromISO(field).toLocaleString()\">\n    <div class=\"_CSS-to__title\">Tage übrig</div>\n    <div v-if=\"field\" class=\"_CSS-to__value\" :class=\"{ '_CSS-to__value--overdue': controller.isOverdue, '_CSS-to__value--warning': controller.daysLeft <= 5 && controller.daysLeft >= 0 }\">{{ controller.daysLeft === 0 ? 'Heute' : controller.daysLeft }}</div>\n    <div v-else class=\"_CSS-to__value\">\n      <ae-icon xstyle=\"color:var(--ae-background-700);\" class=\"far fa-infinity\"></ae-icon>\n    </div>\n  </span>\n</div>\n\n<div ref=\"popup\" v-show=\"locals.showPopup\" style=\"min-width:100px;\">\n  <ae-layout  layout-id=\"3e185751-e4f9-4493-b73d-3c99055d1b86\"\n              :param-value=\"field\"\n              @input=\"controller.onInput($event)\"\n              :param-required=\"fieldInfo.required\"\n              :param-readonly=\"readonly\"\n              :param-inline=\"params.inline\"\n              :param-min=\"params.min\"\n              :param-max=\"params.max\"\n  ></ae-layout>  \n</div>\n\n ","css":"._CSS .tippy-box {\n  background: white;\n  color: black;\n  box-shadow: var(--ae-shadow-1);\n}\n\n._CSS-to__value {\n  display: flex;\n}\n\n._CSS_to__date {\n  font-size: 0.9em;\n}\n\n._CSS-to__title {\n  color: var(--ae-background-600);\n}\n\n._CSS-to__value--overdue {\n  color: #FF7A6D;\n  font-weight: 500;\n}\n\n._CSS-to__value--warning {\n  color: orange;\n  font-weight: 500;\n}","controller":"class Controller {\n  async init() {\n    locals.showPopup = false\n\n    if (!readonly) {\n      await Aeppic.nextRenderPass()\n      this.tippy = Aeppic.Modules.Tippy.default($refs.box, {\n        content: $refs.popup,\n        interactive: true,\n        delay: [50, 100],\n      })\n\n      locals.showPopup = true\n    }\n  }\n\n  get daysLeft() {\n    const field = getField()\n    return -Aeppic.DateTime.local().diff(Aeppic.DateTime.fromISO(field)).toFormat('d')\n  }\n\n  get isOverdue() {\n    if (params.hideOverdue) {\n      return null\n    }\n\n    if (getField()) {\n      return this.daysLeft <= 0\n    }\n\n    return false\n  }\n\n  onInput(value) {\n    setField(value)\n\n    $emit('input')\n  }\n \n  destroy() {\n    if (this.tippy) {\n      this.tippy.destroy()\n      this.tippy = null\n    }\n  }\n}  ","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-07-08T12:17:59+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2021-07-08T12:17:59+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936851,"ct":1721209936851,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"5f9cf0898c8aba40","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"f642751d-cc66-4f5e-9168-b331602be0b2","v":"b54de3c5feaf36d6","p":"controls-folder","f":{"id":"control-form","v":"8f342d57244fe7b5","text":"Control"},"data":{"namespace":"","name":"upgrade-playground","template":"UPGRADE PLAYGROUND","css":"","controller":"","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-07-05T16:46:50+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-07-05T16:47:00+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721144004098,"ct":1721144004098,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"77bde7f07a7bc7fc","f":{"id":"control-form","v":"7672ea0b1b6d4c70","text":"Control"}},"meta":{"modified":{"at":"2024-07-16T17:33:24+02:00","by":"47325375-27fb-4303-8bf5-9438a0524219","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"96d59e36-5378-4b25-8f10-7f28336dca35","v":"1dc58d5faa2ae4be","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"url","template":"<div class=\"_CSS-line\">\n  <input v-if=\"locals.editing\" type=\"text\" v-model=\"field\" ref=\"input\" style=\"flex: 1\" class=\"_CSS-input\">\n\n  <div style=\"flex:1\" v-else class=\"ae-form-cell__input _CSS-input\">\n    <a class=\"ae-link\" :href=\"field\" target=\"_blank\">{{field}}</a>\n  </div>\n\n  <button class=\"ae-button ae-button--small _CSS-button\" :class=\"{ 'ae-button--tertiary': locals.editing }\" @click.stop=\"locals.editing = !locals.editing\"> \n    <ae-icon class=\"fa fa-edit fa-flip-horizontal\" v-if=\"locals.editing\"></ae-icon>\n    <ae-icon class=\"fa fa-edit\" v-else></ae-icon>\n  </button>\n</div>\n","css":"._CSS-line {\n  display: flex;\n  align-items: center;\n}\n\n._CSS-button {\n  position: absolute;\n  right: 1px;\n  top: 1px;\n} \n\n._CSS ._CSS-value,\n._CSS ._CSS-input {\n  padding-right: 40px;\n}","controller":"class Controller {\n  init() {\n    locals.editing = !getField()\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2021-10-13T12:39:29+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2021-12-13T14:00:09+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936851,"ct":1721209936851,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"0bf389f3e4db18f6","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"a":["system","controls-folder"],"a_depth":2,"a_forms":["system-folder-form","repository-folder-form"],"created":{"at":"2022-09-08T14:30:22+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":""},"ct":1721209936851,"data":{"cardinality":"BOTH","controller":"class Controller {\n  get field() {\n    const field = getField() \n\n    let entries = []\n\n    if (Array.isArray(field)) {\n      entries = field\n    } else if (field) {\n      entries = [field]\n    }\n\n    // upgraded concats have [[], '', '']\n    entries =  entries.flat()\n \n    return entries.map(entry => {\n      if (entry.startsWith('http')) {\n        return entry\n      }\n\n      return `http://${entry}`.trim()\n    })\n  }\n\n  add() {\n    this.edit(null)\n\n    let field = this.field\n\n    if (Array.isArray(getField())) {\n      field.push('')\n\n      setField(field)\n\n      this.edit(field.length - 1)\n      return \n    }\n\n    // extranous whitespace will be trimmed on update\n    setField(' ')\n\n    this.edit(0)\n  }\n\n  edit(index) {\n    $refs.layout.$emit('focus-at-index', index)\n  }\n\n  update(args) {\n    const { index, value } = args\n\n    let field = this.field\n    \n    const sanitized = this.sanitizeValue(value)\n\n    if (Array.isArray(getField())) { \n      if (sanitized) {\n        field.splice(index, 1, sanitized)\n      } else {\n        field.splice(index, 1, '') \n      }\n    } else {\n      field = sanitized\n    }\n\n \n    setField(field) \n  }\n\n  sanitizeValue(value) {\n    const raw = `${value}`.trim().replace(/\\s/g, '')\n    if (raw.length === 0) {\n      return ''\n    }\n\n    const sanitized =  raw.startsWith('http') ? raw : `http://${raw}` // raw.replace(/[^0-9|\\+]/g, '')\n\n    return sanitized\n  }\n\n  remove(args) {\n    const { index } = args\n\n    let field = this.field\n\n    if (Array.isArray(getField())) { \n      field.splice(index, 1) \n    } else {\n      field = ''\n    }\n\n    setField(field)\n\n    this.edit(null)\n  }\n}","css":"","description":"","device":"ALL","label":"","limitTypes":false,"name":"url","namespace":"rd","packages":[],"parameters":[],"standalone":"FIELD","template":"<div v-if=\"params.simpleInput\" \n    class=\"border rounded-md flex gap-2 min-h-[2em]\"\n    :class=\"[validationErrors?.required && 'border-red-500']\"\n    data-ae-form-field-input\n>\n  <div class=\"flex items-center rounded-l-md border-r px-2 text-neutral-500 bg-white\" @click.stop>\n    <ae-icon class=\"far fa-globe\"></ae-icon>\n  </div> \n\n  <ae-control class=\"flex-1\" name=\"rd:text\" :document=\"document\" :field-name=\"fieldName\" :param-without-border=\"true\"\n              :param-value-sanitizer-function=\"(value) => controller.sanitizeValue(value)\"\n  ></ae-control>\n</div>\n<ae-layout  v-else \n          layout-id=\"866974ba-6e69-483b-8933-2ec6ac66ef1c\"\n          :param-entries=\"controller.field\"\n          param-icon=\"far fa-globe\"\n          @add=\"controller.add()\"\n          @remove=\"controller.remove($event)\"\n          @update=\"controller.update($event)\"\n          ref=\"layout\"\n          :param-border-color=\"validationErrors?.required && 'border-red-500'\"\n></ae-layout>\n","types":[]},"f":{"id":"control-form","text":"Control","v":"eb9d3505503100c4"},"id":"cb843601-11d3-42ed-990f-cfb813d72b3a","inheritedLocks":[null,null,null],"locks":[],"modified":{"at":"2025-04-03T14:08:34+00:00","backedBy":null,"by":"02409da3-1795-4f78-a2df-be417ea3d6c0","client":"server","from":"","impersonatedBy":null},"p":"controls-folder","previous":{"v":"07041d05b83de938"},"stamps":[],"t":1743689314259,"v":"19dc04d547367cc5"},{"id":"a678752b-4897-48c0-a0f8-6abe6c9ea54a","v":"05a12cf2de14ea36","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"security","name":"users","template":"<div class=\"bp-card bp-card--bordered\" style=\"margin-bottom:var(--ae-space-2)\">\r\n  <div style=\"font-weight:bold\">\r\n    Accounts\r\n  </div>\r\n  <div>\r\n  Aktiv: {{ locals.activeAccountsSize }}\r\n  </div>\r\n  <div>\r\n  Gesamt: {{ locals.sortedProfiles.length }}\r\n  </div>\r\n</div>\r\n\r\n<ae-list  :documents=\"locals.sortedProfiles\"\r\n          list-id=\"2dab232c-2c8a-4aa4-b3f9-6023a7116161\"\r\n          param-gap=\"small\"\r\n          :param-enable-search=\"true\"\r\n          :param-enable-sort=\"true\"\r\n          @selected=\"controller.navigateTo($event)\"\r\n          param-design=\"security:account-card\"\r\n></ae-list>","css":"","controller":"class Controller {\r\n  init() {\r\n    locals.realm = document.p\r\n    // locals.sortedAccounts = []\r\n    locals.sortedProfiles = []\r\n    locals.sortedActiveProfiles = []\r\n    \r\n    // locals.accounts = Aeppic.subscribe(`f.id:account a:${locals.realm} data.isGroupAccount:false`, async (a) => {\r\n    locals.accounts = Aeppic.subscribe(`f.id:account a:${locals.realm}`, { size: 1000 }, async (accounts) => {\r\n      const sorted = [...accounts]\r\n      sorted.sort((a,b) => a.data.name.localeCompare(b.data.name))\r\n\r\n      const profiles = await Promise.all(sorted.map(a => this.getProfile(a)))\r\n      profiles.sort((a,b) => {\r\n        return a && b ? a.data.name.localeCompare(b.data.name)\r\n                : a ? -1\r\n                : b ? 1\r\n                : 0 \r\n      }) \r\n \r\n      // locals.sortedAccounts.splice(0, locals.sortedAccounts.length, ...sorted)\r\n      locals.sortedProfiles.splice(0, locals.sortedProfiles.length, ...profiles)\r\n      locals.activeAccountsSize = accounts.filter(a => !a.data.disabled).length\r\n    })\r\n  }\r\n\r\n  async getProfile(account) {\r\n    if (account.data.parentIsProfile === true) {\r\n      return Aeppic.get(account.p)\r\n    } else if (account.data.profilePage.id) {\r\n      return Aeppic.get(account.data.profilePage.id)\r\n    } else {\r\n      return null\r\n    }\r\n  }\r\n\r\n  async getAccount(profile) {\r\n    const asProfileRef = await Aeppic.findOne(`a:${locals.realm} f.id:account account.data.profilePage.id:${profile.id}`)\r\n\r\n    if (asProfileRef) {\r\n      return asProfileRef\r\n    }\r\n\r\n    const asProfileParent = (await Aeppic.findOne(`a:${locals.realm} f.id:account p:${profile.id}`))\r\n\r\n    if (asProfileParent) {\r\n      return asProfileParent\r\n    }\r\n\r\n    return null\r\n  }\r\n\r\n  async navigateTo(profile) {\r\n    const account = await this.getAccount(profile)\r\n    \r\n    if (!account) {\r\n      return\r\n    }\r\n\r\n    Aeppic.drillInto(account)\r\n  }\r\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2018-12-07T16:09:16+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2023-05-02T09:55:43+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936851,"ct":1721209936851,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"7aa03e34fc8bce17","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"63ba9025-ba51-4fbc-af78-1b5bf3bb2ea5","v":"b87f72e75cdd39e5","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"visible-if-command","template":"<!-- {visible-if-command {{params.id}} {{ locals.visible }}} -->\n<template v-if=\"locals.visible\">\n  <label v-if=\"params.label\" :title=\"Aeppic.translate(params.label)\" class=\"ae-form-cell__label\">\n    {{ Aeppic.translate(params.label) }}\n\n    <ae-control v-if=\"params.help\" name=\"rd:help\" :document=\"document\" :field-name=\"fieldName\" :param-text=\"params.help\"></ae-control>\n  </label>\n\n  <ae-control :document=\"document\" :readonly=\"readonly\" :name=\"params.control ? 'rd:' + params.control : ''\" :field-name=\"fieldName\" :params=\"params\" class=\"ae-form-cell__control\" :class=\"{ 'ae-form-cell__control--readonly': readonly, 'ae-form-cell__control--edit': !readonly }\"></ae-control>\n</template>","css":"","controller":"class Controller {\n  async init() {\n    const isVisible = await this.executeVisiblityCommand()\n    locals.visible = params.negate ? !isVisible : isVisible\n  }\n\n  async executeVisiblityCommand() {\n    if (!params.id) {\n      return false\n    } \n\n    try {\n      const commandResult = await Aeppic.Commands.execute(params.id, document, { \n          fieldName,\n          Account: Aeppic.Account\n      })\n\n      params.debug && console.log(commandResult)\n\n      if (!commandResult || !commandResult.ok || !commandResult.value) {\n        return false\n      }\n\n      return commandResult.value === true || commandResult.value.ok === true\n    } catch (e) {\n      return false\n    }\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2024-01-22T14:04:34+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2024-04-10T13:59:26+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936851,"ct":1721209936851,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"17fe20e23f72bd25","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"d0b955c3-3aad-47cf-b524-1beff34e21f1","v":"439c4da98e341c64","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"visible-if-command","template":"<!-- {visible-if-command {{params.id}} {{ locals.visible }}} -->\n<template v-if=\"locals.visible\">\n  <label v-if=\"params.label\" :title=\"Aeppic.translate(params.label)\" class=\"ae-form-cell__label\">\n    {{ Aeppic.translate(params.label) }}\n\n    <ae-control v-if=\"params.help\" name=\"help\" :document=\"document\" :field-name=\"fieldName\" :param-text=\"params.help\"></ae-control>\n  </label>\n\n  <ae-control :document=\"document\" :readonly=\"readonly\" :name=\"params.control || ''\" :field-name=\"fieldName\" :params=\"params\" class=\"ae-form-cell__control\" :class=\"{ 'ae-form-cell__control--readonly': readonly, 'ae-form-cell__control--edit': !readonly }\"></ae-control>\n</template>","css":"","controller":"class Controller {\n  async init() {\n    const isVisible = await this.executeVisiblityCommand()\n    locals.visible = params.negate ? !isVisible : isVisible\n  }\n\n  async executeVisiblityCommand() {\n    if (!params.id) {\n      return false\n    } \n\n    try {\n      const commandResult = await Aeppic.Commands.execute(params.id, document, { \n          fieldName,\n          Account: Aeppic.Account\n      })\n\n      params.debug && console.log(commandResult)\n\n      if (!commandResult || !commandResult.ok || !commandResult.value) {\n        return false\n      }\n\n      return commandResult.value === true || commandResult.value.ok === true\n    } catch (e) {\n      return false\n    }\n  }\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-05-08T09:00:43+00:00","by":"47325375-27fb-4303-8bf5-9438a0524219","from":"","client":"server"},"modified":{"at":"2022-05-19T14:26:08+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209936851,"ct":1721209936851,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"59c0fa4179c62fbf","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:16+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"3d51a99a-d73c-40bf-8ca4-80a958bbda89","v":"7acbf18ebeeb5729","previous":{"v":"initial"},"p":"75014663-b40b-4285-bdf9-ad54c6b5964a","f":{"id":"control-form","v":"d87073964fc278a5","text":"Control"},"data":{"namespace":"sct","name":"wartungspunkt-unterschrift","template":"<!-- PACKAGE: rs-signature-for-angular@0.3.4 -->\n\n<div layout=\"column\">\n    <div ng-if=\"context.field.dataUrl\" layout=\"row\" layout-align=\"left\" style=\"height:200px\">\n        <img style=\"max-height:200px;\" ng-if=\"context.field.dataUrl.substr(0,5) === 'data:'\" ng-src=\"{{context.field.dataUrl}}\" >\n        <img style=\"max-height:200px;\" ng-if=\"context.field.dataUrl.substr(0,6) === 'store:'\" ng-src=\"{{'api/docs/' + context.document.id + '/content?field=' + context.fieldName}}\">\n    </div>\n    <rs-signature ng-if=\"!context.field.dataUrl\" on-signed=\"document.executeCommand('df0fb114-bd6a-43c4-a6c3-dccc3d1a2268')\" layout-fill layout=\"column\" file-field=\"context.field\" as=\"signature\" ng-readonly=\"false\">\n        <md-button ng-click=\"signature.sign()\" class=\"md-warn md-raised\" style=\"margin:0;width:100%;min-height:100px;background-color:#929eb2\" layout=\"row\" layout-align=\"center center\">\n            <div>\n                <md-icon>assignment_turned_in</md-icon>\n                <span>Unterschreiben</span>\n            </div>\n        </md-button>\n    </rs-signature>\n</div>","css":"","controller":"","cardinalityLimitation":"SINGLE","standalone":"FIELD","types":[],"packages":[],"supportsMobile":false,"label":"","description":""},"locks":[],"created":{"at":"2022-02-28T15:18:08+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","from":"","client":"server"},"modified":{"at":"2022-02-28T15:18:21+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1646061501888,"ct":1646061488572,"a_depth":3,"a":["apps","d4581623-f542-4ade-86be-8b3ecec56f0e","75014663-b40b-4285-bdf9-ad54c6b5964a"],"a_forms":["folder","application-form","folder"],"inheritedLocks":[null,null,null,null],"stamps":[]},{"id":"06c4a755-b0a0-445e-91af-bd0c52b47aab","v":"9cfddc45eb5b0af1","previous":{"v":"15fd2a6129a767c2"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"workflow-diagram","template":"<!-- <WorkflowGraph :workflow-definition=\"document\"></WorkflowGraph> -->\n<div style=\"position:absolute; right:8px; text-align:right; top:4px; height: var(--ae-size-1); display:flex; font-size: smaller; align-items:center; opacity:0.8; grid-gap:4px; white-space:nowrap\">\n  <label :for=\"id('toggle')\">Show Diagram Text</label>\n  <input :id=\"id('toggle')\" style=\"height:1em\" type=\"checkbox\" v-model=\"locals.showRawMermaidText\">\n</div>\n<pre v-if=\"locals.showRawMermaidText\">\n{{locals.mermaidDiagram}}\n</pre>\n<Mermaid v-else :diagram=\"locals.mermaidDiagram\"></Mermaid>","css":"","controller":"async init() {\n  locals.showRawMermaidText = false\n  locals.mermaidDiagram = await packages.generateMermaidDiagram(Aeppic, document.id)\n}\n","parameters":[],"standalone":"REQUIRED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"9980d952-e898-413a-af84-051795281aca","v":"2c312074d47608bb","text":"Workflow"},{"id":"a5ff68d8-6df9-48ef-9f9e-5c9550cee1aa","v":"2868d4ce7e49c58f","text":"Mermaid"}],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2023-04-21T14:46:43+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","from":"","client":"server"},"modified":{"at":"2024-09-16T11:21:27+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1726485687215,"ct":1721209937152,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]},{"id":"18c39cde-4080-4522-adbd-36babcd3655b","v":"0cd1cf1c6738294b","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"workflows","template":"<div class=\"ae-form-cell__label\">Prozesse</div>\r\n<ae-design name=\"info,title\" :document=\"process\" v-for=\"process in locals.processes\" :key=\"process.id\"></ae-design>\r\n\r\n<br>\r\n<button class=\"ae-button\">\r\n  <ae-icon class=\"fa fa-plus\"></ae-icon>\r\n</button> ","css":"","controller":"init() {\r\n  // Find active processes\r\n  locals.activeWorkflows = \"\"\r\n}","parameters":[],"standalone":"FIELD","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[],"device":"ALL","label":"","description":""},"locks":[],"created":{"at":"2020-05-08T09:00:43+00:00","by":"47325375-27fb-4303-8bf5-9438a0524219","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209937153,"ct":1721209937153,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"ac70d0deea44956b","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:17+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"b438676f-a9ac-462d-80b2-39feceae2c6c","v":"9dfbc3776dd6d301","p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"","name":"yaml","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:40em\" v-model=\"field\" theme=\"vs-dark\" :readonly=\"readonly\" language=\"yaml\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>","css":"","controller":"class Controller {\n  async getHoverContent(word) {\n    const infos = await Aeppic.find(`id:${word}`)\n    if (!infos || !infos.length) {\n      return []\n    }\n\n    const doc = infos[0]\n\n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0ea897d653ca79d6","text":"Monaco Editor"}],"device":"ALL","label":"","description":"A control to display YAML source"},"locks":[],"created":{"at":"2018-08-16T09:13:58+00:00","by":"870fac1f-b0d7-4925-b191-a43166960419","from":"","client":"server"},"modified":{"at":"2020-07-06T19:07:57+00:00","by":"8e6c22aa-8c69-4ff8-919a-7032841cf636","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1721209937153,"ct":1721209937153,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[],"previous":{"v":"a24e37c874a10eaf","f":{"id":"control-form","v":"1b0f6a65ebd1cffa","text":"Control"}},"meta":{"modified":{"at":"2024-07-17T09:52:17+00:00","by":"ee99a373-33b1-4c97-8d61-e4a8f53200ba","backedBy":null,"impersonatedBy":null,"from":"","client":"server"}}},{"id":"d17955af-39e9-41a2-9e95-cda923d046e8","v":"bab695e1eacc66c9","cloneOf":{"id":"b438676f-a9ac-462d-80b2-39feceae2c6c","v":"9dfbc3776dd6d301","text":"yaml"},"originalCloneOf":{"id":"b438676f-a9ac-462d-80b2-39feceae2c6c","v":"9dfbc3776dd6d301","text":"yaml"},"previous":{"v":"38212ecb83165a28"},"p":"controls-folder","f":{"id":"control-form","v":"eb9d3505503100c4","text":"Control"},"data":{"namespace":"rd","name":"yaml","template":"<ae-expandable :group=\"params.expandableGroup || 'code'\" :label=\"label\" :document=\"document\">\n  <monaco-editor style=\"min-height:40em\" v-model=\"field\" theme=\"vs-light\" :readonly=\"readonly\" language=\"yaml\" :get-hover-content=\"(word) => controller.getHoverContent(word)\"></monaco-editor>\n</ae-expandable>","css":"","controller":"class Controller {\n  async getHoverContent(word) {\n    const infos = await Aeppic.find(`id:${word}`)\n    if (!infos || !infos.length) {\n      return []\n    }\n\n    const doc = infos[0]\n\n    if(doc == null) {\n      return []\n    }\n\n    const pathDocuments = await Aeppic.getAll(doc.a)\n    const path = '/' + pathDocuments.map((d) => {\n      return d.data.name || d.id\n    }).join('/')\n\n    return [\n      `***Aeppic*** (${doc.id}@${doc.v})`,\n      `Name: \\`${doc.data.name}\\``,\n      `Form (${doc.f.text}): \\`${doc.f.id}@${doc.f.v}\\``,\n      `Path: \\`${path}\\``\n    ]\n  }\n}","parameters":[],"standalone":"SUPPORTED","cardinality":"BOTH","limitTypes":false,"types":[],"packages":[{"id":"e93ffdec-4cfc-4eb2-b7b2-855f82f8a870","v":"0ea897d653ca79d6","text":"Monaco Editor"}],"device":"ALL","label":"","description":"A control to display YAML source"},"locks":[],"created":{"at":"2025-01-10T13:49:03+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"modified":{"at":"2025-01-10T13:49:19+00:00","by":"02409da3-1795-4f78-a2df-be417ea3d6c0","backedBy":null,"impersonatedBy":null,"from":"","client":"server"},"t":1736516959031,"ct":1736516943561,"a_depth":2,"a":["system","controls-folder"],"a_forms":["system-folder-form","repository-folder-form"],"inheritedLocks":[null,null,null],"stamps":[]}]}