cardCustomFields.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. import { TAPi18n } from '/imports/i18n';
  2. import { DatePicker } from '/client/lib/datepicker';
  3. import { ReactiveCache } from '/imports/reactiveCache';
  4. import {
  5. formatDateTime,
  6. formatDate,
  7. formatDateByUserPreference,
  8. formatTime,
  9. getISOWeek,
  10. isValidDate,
  11. isBefore,
  12. isAfter,
  13. isSame,
  14. add,
  15. subtract,
  16. startOf,
  17. endOf,
  18. format,
  19. parseDate,
  20. now,
  21. createDate,
  22. fromNow,
  23. calendar
  24. } from '/imports/lib/dateUtils';
  25. import Cards from '/models/cards';
  26. import { CustomFieldStringTemplate } from '/client/lib/customFields'
  27. Template.cardCustomFieldsPopup.helpers({
  28. hasCustomField() {
  29. const card = Utils.getCurrentCard();
  30. const customFieldId = this._id;
  31. return card.customFieldIndex(customFieldId) > -1;
  32. },
  33. });
  34. Template.cardCustomFieldsPopup.events({
  35. 'click .js-select-field'(event) {
  36. const card = Utils.getCurrentCard();
  37. const customFieldId = this._id;
  38. card.toggleCustomField(customFieldId);
  39. event.preventDefault();
  40. },
  41. 'click .js-settings'(event) {
  42. EscapeActions.executeUpTo('detailsPane');
  43. Sidebar.setView('customFields');
  44. event.preventDefault();
  45. },
  46. });
  47. // cardCustomField
  48. const CardCustomField = BlazeComponent.extendComponent({
  49. getTemplate() {
  50. return `cardCustomField-${this.data().definition.type}`;
  51. },
  52. onCreated() {
  53. const self = this;
  54. self.card = Utils.getCurrentCard();
  55. self.customFieldId = this.data()._id;
  56. },
  57. });
  58. CardCustomField.register('cardCustomField');
  59. // cardCustomField-text
  60. (class extends CardCustomField {
  61. onCreated() {
  62. super.onCreated();
  63. }
  64. events() {
  65. return [
  66. {
  67. 'submit .js-card-customfield-text'(event) {
  68. event.preventDefault();
  69. const value = this.currentComponent().getValue();
  70. this.card.setCustomField(this.customFieldId, value);
  71. },
  72. },
  73. ];
  74. }
  75. }.register('cardCustomField-text'));
  76. // cardCustomField-number
  77. (class extends CardCustomField {
  78. onCreated() {
  79. super.onCreated();
  80. }
  81. events() {
  82. return [
  83. {
  84. 'submit .js-card-customfield-number'(event) {
  85. event.preventDefault();
  86. const value = parseInt(this.find('input').value, 10);
  87. this.card.setCustomField(this.customFieldId, value);
  88. },
  89. },
  90. ];
  91. }
  92. }.register('cardCustomField-number'));
  93. // cardCustomField-checkbox
  94. (class extends CardCustomField {
  95. onCreated() {
  96. super.onCreated();
  97. }
  98. toggleItem() {
  99. this.card.setCustomField(this.customFieldId, !this.data().value);
  100. }
  101. events() {
  102. return [
  103. {
  104. 'click .js-checklist-item .check-box-container': this.toggleItem,
  105. },
  106. ];
  107. }
  108. }.register('cardCustomField-checkbox'));
  109. // cardCustomField-currency
  110. (class extends CardCustomField {
  111. onCreated() {
  112. super.onCreated();
  113. this.currencyCode = this.data().definition.settings.currencyCode;
  114. }
  115. formattedValue() {
  116. const locale = TAPi18n.getLanguage();
  117. return new Intl.NumberFormat(locale, {
  118. style: 'currency',
  119. currency: this.currencyCode,
  120. }).format(this.data().value);
  121. }
  122. events() {
  123. return [
  124. {
  125. 'submit .js-card-customfield-currency'(event) {
  126. event.preventDefault();
  127. // To allow input separated by comma, the comma is replaced by a period.
  128. const value = Number(this.find('input').value.replace(/,/i, '.'), 10);
  129. this.card.setCustomField(this.customFieldId, value);
  130. },
  131. },
  132. ];
  133. }
  134. }.register('cardCustomField-currency'));
  135. // cardCustomField-date
  136. (class extends CardCustomField {
  137. onCreated() {
  138. super.onCreated();
  139. const self = this;
  140. self.date = ReactiveVar();
  141. self.now = ReactiveVar(now());
  142. window.setInterval(() => {
  143. self.now.set(now());
  144. }, 60000);
  145. self.autorun(() => {
  146. self.date.set(new Date(self.data().value));
  147. });
  148. }
  149. showWeek() {
  150. return getISOWeek(this.date.get()).toString();
  151. }
  152. showWeekOfYear() {
  153. const user = ReactiveCache.getCurrentUser();
  154. if (!user) {
  155. // For non-logged-in users, week of year is not shown
  156. return false;
  157. }
  158. return user.isShowWeekOfYear();
  159. }
  160. showDate() {
  161. const currentUser = ReactiveCache.getCurrentUser();
  162. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  163. return formatDateByUserPreference(this.date.get(), dateFormat, true);
  164. }
  165. showISODate() {
  166. return this.date.get().toISOString();
  167. }
  168. classes() {
  169. if (
  170. isBefore(this.date.get(), this.now.get(), 'minute') &&
  171. isBefore(this.now.get(), this.data().value, 'minute')
  172. ) {
  173. return 'current';
  174. }
  175. return '';
  176. }
  177. showTitle() {
  178. return `${TAPi18n.__('card-start-on')} ${this.date.get().toLocaleString()}`;
  179. }
  180. events() {
  181. return [
  182. {
  183. 'click .js-edit-date': Popup.open('cardCustomField-date'),
  184. },
  185. ];
  186. }
  187. }.register('cardCustomField-date'));
  188. // cardCustomField-datePopup
  189. (class extends DatePicker {
  190. onCreated() {
  191. super.onCreated();
  192. const self = this;
  193. self.card = Utils.getCurrentCard();
  194. self.customFieldId = this.data()._id;
  195. this.data().value && this.date.set(new Date(this.data().value));
  196. }
  197. _storeDate(date) {
  198. this.card.setCustomField(this.customFieldId, date);
  199. }
  200. _deleteDate() {
  201. this.card.setCustomField(this.customFieldId, '');
  202. }
  203. }.register('cardCustomField-datePopup'));
  204. // cardCustomField-dropdown
  205. (class extends CardCustomField {
  206. onCreated() {
  207. super.onCreated();
  208. this._items = this.data().definition.settings.dropdownItems;
  209. this.items = this._items.slice(0);
  210. this.items.unshift({
  211. _id: '',
  212. name: TAPi18n.__('custom-field-dropdown-none'),
  213. });
  214. }
  215. selectedItem() {
  216. const selected = this._items.find(item => {
  217. return item._id === this.data().value;
  218. });
  219. return selected
  220. ? selected.name
  221. : TAPi18n.__('custom-field-dropdown-unknown');
  222. }
  223. events() {
  224. return [
  225. {
  226. 'submit .js-card-customfield-dropdown'(event) {
  227. event.preventDefault();
  228. const value = this.find('select').value;
  229. this.card.setCustomField(this.customFieldId, value);
  230. },
  231. },
  232. ];
  233. }
  234. }.register('cardCustomField-dropdown'));
  235. // cardCustomField-stringtemplate
  236. class CardCustomFieldStringTemplate extends CardCustomField {
  237. onCreated() {
  238. super.onCreated();
  239. this.customField = new CustomFieldStringTemplate(this.data().definition);
  240. this.stringtemplateItems = new ReactiveVar(this.data().value ?? []);
  241. }
  242. formattedValue() {
  243. const ret = this.customField.getFormattedValue(this.data().value);
  244. return ret;
  245. }
  246. getItems() {
  247. return Array.from(this.findAll('input'))
  248. .map(input => input.value)
  249. .filter(value => !!value.trim());
  250. }
  251. events() {
  252. return [
  253. {
  254. 'submit .js-card-customfield-stringtemplate'(event) {
  255. event.preventDefault();
  256. const items = this.stringtemplateItems.get();
  257. this.card.setCustomField(this.customFieldId, items);
  258. },
  259. 'keydown .js-card-customfield-stringtemplate-item'(event) {
  260. if (event.keyCode === 13) {
  261. event.preventDefault();
  262. if (event.target.value.trim() || event.metaKey || event.ctrlKey) {
  263. const inputLast = this.find('input.last');
  264. let items = this.getItems();
  265. if (event.target === inputLast) {
  266. inputLast.value = '';
  267. } else if (event.target.nextSibling === inputLast) {
  268. inputLast.focus();
  269. } else {
  270. event.target.blur();
  271. const idx = Array.from(this.findAll('input')).indexOf(
  272. event.target,
  273. );
  274. items.splice(idx + 1, 0, '');
  275. Tracker.afterFlush(() => {
  276. const element = this.findAll('input')[idx + 1];
  277. element.focus();
  278. element.value = '';
  279. });
  280. }
  281. this.stringtemplateItems.set(items);
  282. }
  283. if (event.metaKey || event.ctrlKey) {
  284. this.find('button[type=submit]').click();
  285. }
  286. }
  287. },
  288. 'blur .js-card-customfield-stringtemplate-item'(event) {
  289. if (
  290. !event.target.value.trim() ||
  291. event.target === this.find('input.last')
  292. ) {
  293. const items = this.getItems();
  294. this.stringtemplateItems.set(items);
  295. this.find('input.last').value = '';
  296. }
  297. },
  298. 'click .js-close-inlined-form'(event) {
  299. this.stringtemplateItems.set(this.data().value ?? []);
  300. },
  301. },
  302. ];
  303. }
  304. }
  305. CardCustomFieldStringTemplate.register('cardCustomField-stringtemplate');