cardCustomFields.js 8.2 KB

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