packages.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. import $ from 'jquery';
  2. import { config, translations } from 'grav-config';
  3. import request from '../utils/request';
  4. import { Instance as gpm } from '../utils/gpm';
  5. import { Promise } from 'es6-promise';
  6. class Sorter {
  7. getElements(elements, container) {
  8. this.elements = elements || $('[data-gpm-plugin], [data-gpm-theme]');
  9. this.container = container || $('.gpm-plugins > table > tbody, .gpm-themes > .themes.card-row');
  10. return this.elements;
  11. }
  12. static sort(A, B, direction = 'asc') {
  13. if (A > B) { return (direction === 'asc') ? 1 : -1; }
  14. if (A < B) { return (direction === 'asc') ? -1 : 1; }
  15. return 0;
  16. }
  17. byCommon(direction = 'asc', data = '') {
  18. let elements = this.getElements().sort((a, b) => {
  19. let A = $(a).data(data).toString().toLowerCase();
  20. let B = $(b).data(data).toString().toLowerCase();
  21. return Sorter.sort(A, B, direction);
  22. });
  23. return elements.appendTo(this.container);
  24. }
  25. byName(direction = 'asc', data = 'gpm-name') {
  26. return this.byCommon(direction, data);
  27. }
  28. byAuthor(direction = 'asc', data = 'gpm-author') {
  29. return this.byCommon(direction, data);
  30. }
  31. byOfficial(direction = 'asc', data = 'gpm-official') {
  32. return this.byCommon(direction, data);
  33. }
  34. byReleaseDate(direction = 'asc', data = 'gpm-release-date') {
  35. let elements = this.getElements().sort((a, b) => {
  36. let A = new Date($(a).data(data)).getTime();
  37. let B = new Date($(b).data(data)).getTime();
  38. return Sorter.sort(A, B, direction === 'asc' ? 'desc' : 'asc');
  39. });
  40. elements.appendTo(this.container);
  41. }
  42. byUpdatable(direction = 'asc', data = 'gpm-updatable') {
  43. return this.byCommon(direction, data);
  44. }
  45. byEnabled(direction = 'asc', data = 'gpm-enabled') {
  46. return this.byCommon(direction, data);
  47. }
  48. byTesting(direction = 'asc', data = 'gpm-testing') {
  49. return this.byCommon(direction, data);
  50. }
  51. }
  52. class Packages {
  53. constructor() {
  54. this.Sort = new Sorter();
  55. }
  56. static getBackToList(type) {
  57. global.location.href = `${config.base_url_relative}/${type}s`;
  58. }
  59. static addDependencyToList(type, dependency, slug = '') {
  60. if (['admin', 'form', 'login', 'email', 'grav'].indexOf(dependency) !== -1) { return; }
  61. let container = $('.package-dependencies-container');
  62. let text = `${dependency} <a href="#" class="button" data-dependency-slug="${dependency}" data-${type}-action="remove-dependency-package">Remove</a>`;
  63. if (slug) {
  64. text += ` (was needed by ${slug})`;
  65. }
  66. container.append(`<li>${text}</li>`);
  67. }
  68. addDependenciesToList(dependencies, slug = '') {
  69. dependencies.forEach((dependency) => {
  70. Packages.addDependencyToList('plugin', dependency.name || dependency, slug);
  71. });
  72. }
  73. static getTaskUrl(type, task) {
  74. let url = `${config.base_url_relative}`;
  75. url += `/${type}s.json`;
  76. url += `/task${config.param_sep}${task}`;
  77. return url;
  78. }
  79. static getRemovePackageUrl(type) {
  80. return `${Packages.getTaskUrl(type, 'removePackage')}`;
  81. }
  82. static getReinstallPackageUrl(type) {
  83. return `${Packages.getTaskUrl(type, 'reinstallPackage')}`;
  84. }
  85. static getGetPackagesDependenciesUrl(type) {
  86. return `${Packages.getTaskUrl(type, 'getPackagesDependencies')}`;
  87. }
  88. static getInstallDependenciesOfPackagesUrl(type) {
  89. return `${Packages.getTaskUrl(type, 'installDependenciesOfPackages')}`;
  90. }
  91. static getInstallPackageUrl(type) {
  92. return `${Packages.getTaskUrl(type, 'installPackage')}`;
  93. }
  94. removePackage(type, slug) {
  95. let url = Packages.getRemovePackageUrl(type);
  96. request(url, {
  97. method: 'post',
  98. body: {
  99. package: slug
  100. }
  101. }, (response) => {
  102. if (response.status === 'success') {
  103. $('.remove-package-confirm').addClass('hidden');
  104. if (response.dependencies && response.dependencies.length > 0) {
  105. this.addDependenciesToList(response.dependencies);
  106. $('.remove-package-dependencies').removeClass('hidden');
  107. } else {
  108. $('.remove-package-done').removeClass('hidden');
  109. }
  110. // The package was removed. When the modal closes, move to the packages list
  111. $(document).on('closing', '[data-remodal-id="remove-package"]', () => {
  112. Packages.getBackToList(type);
  113. });
  114. } else {
  115. $('.remove-package-confirm').addClass('hidden');
  116. $('.remove-package-error').removeClass('hidden');
  117. }
  118. });
  119. }
  120. reinstallPackage(type, slug, package_name, current_version) {
  121. $('.button-bar button').addClass('hidden');
  122. $('.button-bar .spinning-wheel').removeClass('hidden');
  123. let url = Packages.getReinstallPackageUrl(type);
  124. request(url, {
  125. method: 'post',
  126. body: {
  127. slug: slug,
  128. type: type,
  129. package_name: package_name,
  130. current_version: current_version
  131. }
  132. }, (response) => {
  133. if (response.status === 'success') {
  134. $('.reinstall-package-confirm').addClass('hidden');
  135. $('.reinstall-package-done').removeClass('hidden');
  136. } else {
  137. $('.reinstall-package-confirm').addClass('hidden');
  138. $('.reinstall-package-error').removeClass('hidden');
  139. }
  140. window.location.reload();
  141. });
  142. }
  143. removeDependency(type, slug, button) {
  144. let url = Packages.getRemovePackageUrl(type);
  145. request(url, {
  146. method: 'post',
  147. body: {
  148. package: slug
  149. }
  150. }, (response) => {
  151. if (response.status === 'success') {
  152. button.removeClass('button');
  153. button.replaceWith($('<span>Removed successfully</span>'));
  154. if (response.dependencies && response.dependencies.length > 0) {
  155. this.addDependenciesToList(response.dependencies, slug);
  156. }
  157. }
  158. });
  159. }
  160. static addNeededDependencyToList(action, slug) {
  161. $('.install-dependencies-package-container .type-' + action).removeClass('hidden');
  162. let list = $('.install-dependencies-package-container .type-' + action + ' ul');
  163. if (action !== 'install') {
  164. let current_version = '';
  165. let available_version = '';
  166. let name = '';
  167. let resources = gpm.payload.payload.resources;
  168. if (resources.plugins[slug]) {
  169. available_version = resources.plugins[slug].available;
  170. current_version = resources.plugins[slug].version;
  171. name = resources.plugins[slug].name;
  172. } else if (resources.themes[slug]) {
  173. available_version = resources.themes[slug].available;
  174. current_version = resources.themes[slug].version;
  175. name = resources.themes[slug].name;
  176. }
  177. list.append(`<li>${name ? name : slug}, ${translations.PLUGIN_ADMIN.FROM} v<strong>${current_version}</strong> ${translations.PLUGIN_ADMIN.TO} v<strong>${available_version}</strong></li>`);
  178. } else {
  179. list.append(`<li>${name ? name : slug}</li>`);
  180. }
  181. }
  182. getPackagesDependencies(type, slugs, finishedLoadingCallback) {
  183. let url = Packages.getGetPackagesDependenciesUrl(type);
  184. request(url, {
  185. method: 'post',
  186. body: {
  187. packages: slugs
  188. }
  189. }, (response) => {
  190. finishedLoadingCallback();
  191. if (response.status === 'success') {
  192. if (response.dependencies) {
  193. let hasDependencies = false;
  194. for (var dependency in response.dependencies) {
  195. if (response.dependencies.hasOwnProperty(dependency)) {
  196. if (dependency === 'grav') {
  197. continue;
  198. }
  199. hasDependencies = true;
  200. let dependencyName = dependency;
  201. let action = response.dependencies[dependency];
  202. Packages.addNeededDependencyToList(action, dependencyName);
  203. }
  204. }
  205. if (hasDependencies) {
  206. $('[data-packages-modal] .install-dependencies-package-container').removeClass('hidden');
  207. } else {
  208. $('[data-packages-modal] .install-package-container').removeClass('hidden');
  209. }
  210. } else {
  211. $('[data-packages-modal] .install-package-container').removeClass('hidden');
  212. }
  213. } else {
  214. $('[data-packages-modal] .install-package-error').removeClass('hidden');
  215. }
  216. });
  217. }
  218. installDependenciesOfPackages(type, slugs, callbackSuccess, callbackError) {
  219. let url = Packages.getInstallDependenciesOfPackagesUrl(type);
  220. request(url, {
  221. method: 'post',
  222. body: {
  223. packages: slugs
  224. }
  225. }, callbackSuccess);
  226. }
  227. installPackages(type, slugs, callbackSuccess) {
  228. let url = Packages.getInstallPackageUrl(type);
  229. Promise.all(slugs.map((slug) => {
  230. return new Promise((resolve, reject) => {
  231. request(url, {
  232. method: 'post',
  233. body: {
  234. package: slug,
  235. type: type
  236. }
  237. }, (response) => {
  238. resolve(response);
  239. });
  240. });
  241. })).then(callbackSuccess);
  242. }
  243. static getSlugsFromEvent(event) {
  244. let slugs = '';
  245. if ($(event.target).is('[data-packages-slugs]')) {
  246. slugs = $(event.target).attr('data-packages-slugs');
  247. } else {
  248. slugs = $(event.target).parent('[data-packages-slugs]').attr('data-packages-slugs');
  249. }
  250. if (typeof slugs === 'undefined') {
  251. return null;
  252. }
  253. slugs = slugs.split(',');
  254. return typeof slugs === 'string' ? [slugs] : slugs;
  255. }
  256. handleGettingPackageDependencies(type, event, action = 'update') {
  257. let slugs = Packages.getSlugsFromEvent(event);
  258. if (!slugs) {
  259. alert('No slug set');
  260. return;
  261. }
  262. // Cleanup
  263. $('.packages-names-list').html('');
  264. $('.install-dependencies-package-container li').remove();
  265. slugs.forEach((slug) => {
  266. if (action === 'update') {
  267. let current_version = '';
  268. let available_version = '';
  269. let name = '';
  270. let resources = gpm.payload.payload.resources;
  271. if (resources.plugins[slug]) {
  272. available_version = resources.plugins[slug].available;
  273. current_version = resources.plugins[slug].version;
  274. name = resources.plugins[slug].name;
  275. } else if (resources.themes[slug]) {
  276. available_version = resources.themes[slug].available;
  277. current_version = resources.themes[slug].version;
  278. name = resources.themes[slug].name;
  279. }
  280. $('.packages-names-list').append(`<li>${name ? name : slug}, ${translations.PLUGIN_ADMIN.FROM} v<strong>${current_version}</strong> ${translations.PLUGIN_ADMIN.TO} v<strong>${available_version}</strong></li>`);
  281. } else {
  282. $('.packages-names-list').append(`<li>${name ? name : slug}</li>`);
  283. }
  284. });
  285. event.preventDefault();
  286. event.stopPropagation();
  287. // fix mismatching types when sharing install modal between plugins/themes
  288. const query = '[data-packages-modal] [data-theme-action], [data-packages-modal] [data-plugin-action]';
  289. const data = $(query).data('themeAction') || $(query).data('pluginAction');
  290. $(query).removeAttr('data-theme-action').removeAttr('data-plugin-action').attr(`data-${type}-action`, data);
  291. // Restore original state
  292. $('[data-packages-modal] .loading').removeClass('hidden');
  293. $('[data-packages-modal] .install-dependencies-package-container').addClass('hidden');
  294. $('[data-packages-modal] .install-package-container').addClass('hidden');
  295. $('[data-packages-modal] .installing-dependencies').addClass('hidden');
  296. $('[data-packages-modal] .installing-package').addClass('hidden');
  297. $('[data-packages-modal] .installation-complete').addClass('hidden');
  298. $('[data-packages-modal] .install-package-error').addClass('hidden');
  299. this.getPackagesDependencies(type, slugs, () => {
  300. let slugs_string = slugs.join();
  301. $(`[data-packages-modal] [data-${type}-action="install-dependencies-and-package"]`).attr('data-packages-slugs', slugs_string);
  302. $(`[data-packages-modal] [data-${type}-action="install-package"]`).attr('data-packages-slugs', slugs_string);
  303. $('[data-packages-modal] .loading').addClass('hidden');
  304. });
  305. }
  306. handleInstallingDependenciesAndPackage(type, event) {
  307. let slugs = Packages.getSlugsFromEvent(event);
  308. event.preventDefault();
  309. event.stopPropagation();
  310. $('[data-packages-modal] .install-dependencies-package-container').addClass('hidden');
  311. $('[data-packages-modal] .install-package-container').addClass('hidden');
  312. $('[data-packages-modal] .installing-dependencies').removeClass('hidden');
  313. this.installDependenciesOfPackages(type, slugs, (response) => {
  314. $('[data-packages-modal] .installing-dependencies').addClass('hidden');
  315. $('[data-packages-modal] .installing-package').removeClass('hidden');
  316. this.installPackages(type, slugs, () => {
  317. $('[data-packages-modal] .installing-package').addClass('hidden');
  318. $('[data-packages-modal] .installation-complete').removeClass('hidden');
  319. if (response.status === 'error') {
  320. let remodal = $.remodal.lookup[$('[data-packages-modal]').data('remodal')];
  321. remodal.close();
  322. return;
  323. }
  324. setTimeout(() => {
  325. if (slugs.length === 1) {
  326. global.location.href = `${config.base_url_relative}/${type}s/${slugs[0]}`;
  327. } else {
  328. global.location.href = `${config.base_url_relative}/${type}s`;
  329. }
  330. }, 1000);
  331. });
  332. });
  333. }
  334. handleInstallingPackage(type, event) {
  335. let slugs = Packages.getSlugsFromEvent(event);
  336. event.preventDefault();
  337. event.stopPropagation();
  338. $('[data-packages-modal] .install-package-container').addClass('hidden');
  339. $('[data-packages-modal] .installing-package').removeClass('hidden');
  340. this.installPackages(type, slugs, (response) => {
  341. $('[data-packages-modal] .installing-package').addClass('hidden');
  342. $('[data-packages-modal] .installation-complete').removeClass('hidden');
  343. const errors = Array.from(response).filter((r) => r.status === 'error');
  344. if (errors && errors.length) {
  345. let remodal = $.remodal.lookup[$('[data-packages-modal].remodal-is-opened').data('remodal')];
  346. remodal.close();
  347. return;
  348. }
  349. if (slugs.length === 1) {
  350. global.location.href = `${config.base_url_relative}/${type}s/${slugs[0]}`;
  351. } else {
  352. global.location.href = `${config.base_url_relative}/${type}s`;
  353. }
  354. });
  355. }
  356. handleRemovingPackage(type, event) {
  357. let slug = $(event.target).attr('data-packages-slugs');
  358. event.preventDefault();
  359. event.stopPropagation();
  360. this.removePackage(type, slug);
  361. }
  362. handleReinstallPackage(type, event) {
  363. let target = $(event.target);
  364. let slug = target.attr('data-package-slug');
  365. let package_name = target.attr('data-package-name');
  366. let current_version = target.attr('data-package-current-version');
  367. event.preventDefault();
  368. event.stopPropagation();
  369. this.reinstallPackage(type, slug, package_name, current_version);
  370. }
  371. handleRemovingDependency(type, event) {
  372. let slug = $(event.target).attr('data-dependency-slug');
  373. let button = $(event.target);
  374. event.preventDefault();
  375. event.stopPropagation();
  376. this.removeDependency(type, slug, button);
  377. }
  378. }
  379. export default new Packages();