cardCustomFields.js 8.2 KB

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