boardsList.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. import { TAPi18n } from '/imports/i18n';
  2. const subManager = new SubsManager();
  3. Template.boardList.helpers({
  4. currentSetting() {
  5. return Settings.findOne();
  6. },
  7. hideCardCounterList() {
  8. // Fix Rare bug: Board Icons random dance https://github.com/wekan/wekan/issues/4214
  9. // by commenting out following line,
  10. // where it probably counts too often at realtime,
  11. // and makes cards that have card count and member list change size
  12. // and order all the time:
  13. //return Utils.isMiniScreen() && Session.get('currentBoard');
  14. return true;
  15. },
  16. hideBoardMemberList() {
  17. // Fix Rare bug: Board Icons random dance https://github.com/wekan/wekan/issues/4214
  18. // by commenting out following line,
  19. // where it probably counts too often at realtime,
  20. // and makes cards that have card count and member list change size
  21. // and order all the time:
  22. //return Utils.isMiniScreen() && Session.get('currentBoard');
  23. return true;
  24. },
  25. })
  26. Template.boardListHeaderBar.events({
  27. 'click .js-open-archived-board'() {
  28. Modal.open('archivedBoards');
  29. },
  30. });
  31. Template.boardListHeaderBar.helpers({
  32. title() {
  33. //if (FlowRouter.getRouteName() === 'template-container') {
  34. // return 'template-container';
  35. //} else {
  36. return FlowRouter.getRouteName() === 'home' ? 'my-boards' : 'public';
  37. //}
  38. },
  39. templatesBoardId() {
  40. return Meteor.user() && Meteor.user().getTemplatesBoardId();
  41. },
  42. templatesBoardSlug() {
  43. return Meteor.user() && Meteor.user().getTemplatesBoardSlug();
  44. },
  45. });
  46. BlazeComponent.extendComponent({
  47. onCreated() {
  48. Meteor.subscribe('setting');
  49. Meteor.subscribe('tableVisibilityModeSettings');
  50. let currUser = Meteor.user();
  51. let userLanguage;
  52. if (currUser && currUser.profile) {
  53. userLanguage = currUser.profile.language
  54. }
  55. if (userLanguage) {
  56. TAPi18n.setLanguage(userLanguage);
  57. }
  58. },
  59. onRendered() {
  60. const itemsSelector = '.js-board:not(.placeholder)';
  61. const $boards = this.$('.js-boards');
  62. $boards.sortable({
  63. connectWith: '.js-boards',
  64. tolerance: 'pointer',
  65. appendTo: '.board-list',
  66. helper: 'clone',
  67. distance: 7,
  68. items: itemsSelector,
  69. placeholder: 'board-wrapper placeholder',
  70. start(evt, ui) {
  71. ui.helper.css('z-index', 1000);
  72. ui.placeholder.height(ui.helper.height());
  73. EscapeActions.executeUpTo('popup-close');
  74. },
  75. stop(evt, ui) {
  76. // To attribute the new index number, we need to get the DOM element
  77. // of the previous and the following card -- if any.
  78. const prevBoardDom = ui.item.prev('.js-board').get(0);
  79. const nextBoardBom = ui.item.next('.js-board').get(0);
  80. const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardBom, 1);
  81. const boardDomElement = ui.item.get(0);
  82. const board = Blaze.getData(boardDomElement);
  83. // Normally the jquery-ui sortable library moves the dragged DOM element
  84. // to its new position, which disrupts Blaze reactive updates mechanism
  85. // (especially when we move the last card of a list, or when multiple
  86. // users move some cards at the same time). To prevent these UX glitches
  87. // we ask sortable to gracefully cancel the move, and to put back the
  88. // DOM in its initial state. The card move is then handled reactively by
  89. // Blaze with the below query.
  90. $boards.sortable('cancel');
  91. // Fix Rare bug: Board Icons random dance https://github.com/wekan/wekan/issues/4214
  92. // by commenting out following line:
  93. //board.move(sortIndex.base);
  94. },
  95. });
  96. // Disable drag-dropping if the current user is not a board member or is comment only
  97. this.autorun(() => {
  98. if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
  99. $boards.sortable({
  100. handle: '.board-handle',
  101. });
  102. }
  103. });
  104. },
  105. userHasTeams() {
  106. if (Meteor.user() != null && Meteor.user().teams && Meteor.user().teams.length > 0)
  107. return true;
  108. else
  109. return false;
  110. },
  111. teamsDatas() {
  112. if (Meteor.user().teams)
  113. return Meteor.user().teams.sort((a, b) => a.teamDisplayName.localeCompare(b.teamDisplayName));
  114. else
  115. return [];
  116. },
  117. userHasOrgs() {
  118. if (Meteor.user() != null && Meteor.user().orgs && Meteor.user().orgs.length > 0)
  119. return true;
  120. else
  121. return false;
  122. },
  123. /*
  124. userHasTemplates(){
  125. if(Meteor.user() != null && Meteor.user().orgs && Meteor.user().orgs.length > 0)
  126. return true;
  127. else
  128. return false;
  129. },
  130. */
  131. orgsDatas() {
  132. if (Meteor.user().orgs)
  133. return Meteor.user().orgs.sort((a, b) => a.orgDisplayName.localeCompare(b.orgDisplayName));
  134. else
  135. return [];
  136. },
  137. userHasOrgsOrTeams() {
  138. let boolUserHasOrgs;
  139. if (Meteor.user() != null && Meteor.user().orgs && Meteor.user().orgs.length > 0)
  140. boolUserHasOrgs = true;
  141. else
  142. boolUserHasOrgs = false;
  143. let boolUserHasTeams;
  144. if (Meteor.user() != null && Meteor.user().teams && Meteor.user().teams.length > 0)
  145. boolUserHasTeams = true;
  146. else
  147. boolUserHasTeams = false;
  148. return (boolUserHasOrgs || boolUserHasTeams);
  149. },
  150. boards() {
  151. let query = {
  152. // { type: 'board' },
  153. // { type: { $in: ['board','template-container'] } },
  154. $and: [
  155. { archived: false },
  156. { type: { $in: ['board', 'template-container'] } },
  157. { $or: [] },
  158. { title: { $not: { $regex: /^\^.*\^$/ } } }
  159. ]
  160. };
  161. let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly');
  162. if (FlowRouter.getRouteName() === 'home') {
  163. query.$and[2].$or.push({ 'members.userId': Meteor.userId() });
  164. if (allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue) {
  165. query.$and.push({ 'permission': 'private' });
  166. }
  167. const currUser = Users.findOne(Meteor.userId());
  168. // const currUser = Users.findOne(Meteor.userId(), {
  169. // fields: {
  170. // orgs: 1,
  171. // teams: 1,
  172. // },
  173. // });
  174. let orgIdsUserBelongs = currUser !== undefined && currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : '';
  175. if (orgIdsUserBelongs && orgIdsUserBelongs != '') {
  176. let orgsIds = orgIdsUserBelongs.split(',');
  177. // for(let i = 0; i < orgsIds.length; i++){
  178. // query.$and[2].$or.push({'orgs.orgId': orgsIds[i]});
  179. // }
  180. //query.$and[2].$or.push({'orgs': {$elemMatch : {orgId: orgsIds[0]}}});
  181. query.$and[2].$or.push({ 'orgs.orgId': { $in: orgsIds } });
  182. }
  183. let teamIdsUserBelongs = currUser !== undefined && currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : '';
  184. if (teamIdsUserBelongs && teamIdsUserBelongs != '') {
  185. let teamsIds = teamIdsUserBelongs.split(',');
  186. // for(let i = 0; i < teamsIds.length; i++){
  187. // query.$or[2].$or.push({'teams.teamId': teamsIds[i]});
  188. // }
  189. //query.$and[2].$or.push({'teams': { $elemMatch : {teamId: teamsIds[0]}}});
  190. query.$and[2].$or.push({ 'teams.teamId': { $in: teamsIds } });
  191. }
  192. }
  193. else if (allowPrivateVisibilityOnly !== undefined && !allowPrivateVisibilityOnly.booleanValue) {
  194. query = {
  195. archived: false,
  196. //type: { $in: ['board','template-container'] },
  197. type: 'board',
  198. permission: 'public',
  199. };
  200. }
  201. return Boards.find(query, {
  202. sort: { sort: 1 /* boards default sorting */ },
  203. });
  204. },
  205. boardLists(boardId) {
  206. /*
  207. let boardLists = [];
  208. const lists = Lists.find({ 'boardId': boardId, 'archived': false },{sort: ['sort','asc']});
  209. lists.forEach(list => {
  210. let cardCount = Cards.find({ 'boardId': boardId, 'listId': list._id }).count()
  211. boardLists.push(`${list.title}: ${cardCount}`);
  212. });
  213. return boardLists;
  214. */
  215. return false;
  216. },
  217. boardMembers(boardId) {
  218. /*
  219. let boardMembers = [];
  220. const lists = Boards.findOne({ '_id': boardId })
  221. let members = lists.members
  222. members.forEach(member => {
  223. boardMembers.push(member.userId);
  224. });
  225. return boardMembers;
  226. */
  227. return false;
  228. },
  229. isStarred() {
  230. const user = Meteor.user();
  231. return user && user.hasStarred(this.currentData()._id);
  232. },
  233. isAdministrable() {
  234. const user = Meteor.user();
  235. return user && user.isBoardAdmin(this.currentData()._id);
  236. },
  237. hasOvertimeCards() {
  238. subManager.subscribe('board', this.currentData()._id, false);
  239. return this.currentData().hasOvertimeCards();
  240. },
  241. hasSpentTimeCards() {
  242. subManager.subscribe('board', this.currentData()._id, false);
  243. return this.currentData().hasSpentTimeCards();
  244. },
  245. isInvited() {
  246. const user = Meteor.user();
  247. return user && user.isInvitedTo(this.currentData()._id);
  248. },
  249. events() {
  250. return [
  251. {
  252. 'click .js-add-board': Popup.open('createBoard'),
  253. 'click .js-star-board'(evt) {
  254. const boardId = this.currentData()._id;
  255. Meteor.user().toggleBoardStar(boardId);
  256. evt.preventDefault();
  257. },
  258. 'click .js-clone-board'(evt) {
  259. let title = getSlug(Boards.findOne(this.currentData()._id).title) || 'cloned-board';
  260. Meteor.call(
  261. 'copyBoard',
  262. this.currentData()._id,
  263. {
  264. sort: Boards.find({ archived: false }).count(),
  265. type: 'board',
  266. title: Boards.findOne(this.currentData()._id).title,
  267. },
  268. (err, res) => {
  269. if (err) {
  270. console.error(err);
  271. } else {
  272. Session.set('fromBoard', null);
  273. subManager.subscribe('board', res, false);
  274. FlowRouter.go('board', {
  275. id: res,
  276. slug: title,
  277. });
  278. }
  279. },
  280. );
  281. evt.preventDefault();
  282. },
  283. 'click .js-archive-board'(evt) {
  284. const boardId = this.currentData()._id;
  285. Meteor.call('archiveBoard', boardId);
  286. evt.preventDefault();
  287. },
  288. 'click .js-accept-invite'() {
  289. const boardId = this.currentData()._id;
  290. Meteor.call('acceptInvite', boardId);
  291. },
  292. 'click .js-decline-invite'() {
  293. const boardId = this.currentData()._id;
  294. Meteor.call('quitBoard', boardId, (err, ret) => {
  295. if (!err && ret) {
  296. Meteor.call('acceptInvite', boardId);
  297. FlowRouter.go('home');
  298. }
  299. });
  300. },
  301. 'click #resetBtn'(event) {
  302. let allBoards = document.getElementsByClassName("js-board");
  303. let currBoard;
  304. for (let i = 0; i < allBoards.length; i++) {
  305. currBoard = allBoards[i];
  306. currBoard.style.display = "block";
  307. }
  308. },
  309. 'click #filterBtn'(event) {
  310. event.preventDefault();
  311. let selectedTeams = document.querySelectorAll('#jsAllBoardTeams option:checked');
  312. let selectedTeamsValues = Array.from(selectedTeams).map(function (elt) { return elt.value });
  313. let index = selectedTeamsValues.indexOf("-1");
  314. if (index > -1) {
  315. selectedTeamsValues.splice(index, 1);
  316. }
  317. let selectedOrgs = document.querySelectorAll('#jsAllBoardOrgs option:checked');
  318. let selectedOrgsValues = Array.from(selectedOrgs).map(function (elt) { return elt.value });
  319. index = selectedOrgsValues.indexOf("-1");
  320. if (index > -1) {
  321. selectedOrgsValues.splice(index, 1);
  322. }
  323. if (selectedTeamsValues.length > 0 || selectedOrgsValues.length > 0) {
  324. const query = {
  325. $and: [
  326. { archived: false },
  327. { type: 'board' },
  328. { $or: [] }
  329. ]
  330. };
  331. if (selectedTeamsValues.length > 0) {
  332. query.$and[2].$or.push({ 'teams.teamId': { $in: selectedTeamsValues } });
  333. }
  334. if (selectedOrgsValues.length > 0) {
  335. query.$and[2].$or.push({ 'orgs.orgId': { $in: selectedOrgsValues } });
  336. }
  337. let filteredBoards = Boards.find(query, {}).fetch();
  338. let allBoards = document.getElementsByClassName("js-board");
  339. let currBoard;
  340. if (filteredBoards.length > 0) {
  341. let currBoardId;
  342. let found;
  343. for (let i = 0; i < allBoards.length; i++) {
  344. currBoard = allBoards[i];
  345. currBoardId = currBoard.classList[0];
  346. found = filteredBoards.find(function (board) {
  347. return board._id == currBoardId;
  348. });
  349. if (found !== undefined)
  350. currBoard.style.display = "block";
  351. else
  352. currBoard.style.display = "none";
  353. }
  354. }
  355. else {
  356. for (let i = 0; i < allBoards.length; i++) {
  357. currBoard = allBoards[i];
  358. currBoard.style.display = "none";
  359. }
  360. }
  361. }
  362. },
  363. },
  364. ];
  365. },
  366. }).register('boardList');