packages.js 17 KB

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