edit-page.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. // force the page to re-load if the back button was used
  2. $(window).bind("pageshow", function(event) {
  3. if (event.originalEvent.persisted) {
  4. window.location.reload();
  5. }
  6. });
  7. function createGetUrl(pageState, pageId) {
  8. let url = "/api/page/";
  9. switch (pageState) {
  10. case "Draft":
  11. url += "draft/";
  12. break;
  13. case "Amendment":
  14. url += "amendment/";
  15. break;
  16. }
  17. url += pageId;
  18. return url;
  19. }
  20. function createPutUrl(pageState) {
  21. let url = "/api/page/";
  22. switch (pageState) {
  23. case "Draft":
  24. url += "draft/";
  25. break;
  26. case "Amendment":
  27. url += "amendment/";
  28. break;
  29. }
  30. return url;
  31. }
  32. function createSubmitUrl(pageState) {
  33. let url = "/api/page/approve/";
  34. switch (pageState) {
  35. case "Draft":
  36. url += "draft/";
  37. break;
  38. case "Amendment":
  39. url += "amendment/";
  40. break;
  41. }
  42. return url;
  43. }
  44. function createPreviewUrl(pageState, pageId) {
  45. let url = "/preview/";
  46. switch (pageState) {
  47. case "Draft":
  48. url += "draft/";
  49. break;
  50. case "Amendment":
  51. url += "amendment/";
  52. break;
  53. }
  54. url += pageId;
  55. return url;
  56. }
  57. var app = new Vue({
  58. el: "#app",
  59. data: {
  60. pageId: "",
  61. pageState: "",
  62. page: {}
  63. },
  64. beforeMount: function() {
  65. this.pageId = $("#pageId").attr("value");
  66. this.pageState = $("#pageState").attr("value");
  67. },
  68. mounted: function() {
  69. this.getPage();
  70. },
  71. updated: function() {
  72. mountControls();
  73. // update the description counters.
  74. if (this.page.reviewState === "None" || this.page.reviewState === "Rejected") {
  75. this.titleKeyUp();
  76. this.shortDescKeyUp();
  77. this.longDescKeyUp();
  78. }
  79. this.buildJitPackHelperLink();
  80. $(".tooltip")
  81. .popup("destroy")
  82. .popup();
  83. },
  84. methods: {
  85. getPage: function() {
  86. let url = createGetUrl(this.pageState, this.pageId);
  87. $.ajax({
  88. url: url,
  89. method: "GET",
  90. success: function(data) {
  91. app.page = data;
  92. },
  93. error: toast.defaultAjaxError
  94. });
  95. },
  96. getCategoryNameFromId: function(id) {
  97. for (let i = 0; i < this.categories.length; i++) {
  98. if (this.categories[i].id === id) {
  99. return this.categories[i].name;
  100. }
  101. }
  102. return "UNKNOWN";
  103. },
  104. updatePage: function(then) {
  105. let url = createPutUrl(this.pageState, this.pageId);
  106. let formElement = $("#form").get(0);
  107. let formData = new FormData(formElement);
  108. $.ajax({
  109. url: url,
  110. method: "PUT",
  111. data: formData,
  112. cache: false,
  113. contentType: false,
  114. processData: false,
  115. success: function(data) {
  116. if (data.areasUpdated.length == 0) {
  117. toast.info("Save Changes", "No changes were detected.", false);
  118. } else {
  119. // clear all screenshot input values.
  120. $("input:file").each(function(index, element) {
  121. $(element).val("");
  122. });
  123. // clear all the deleted images.
  124. $("#deletedImages").empty();
  125. // remove any image that starts with "data:"
  126. $(".sshot-img").each(function(index, element) {
  127. let src = $(element).attr("src");
  128. if (src.startsWith("data:")) {
  129. $(element).attr("src", "");
  130. $(element).addClass("hidden-element");
  131. }
  132. });
  133. app.page = data.page;
  134. // toastVue.showToast("green", "Save Successful", "The following areas were updated:", data.areasUpdated);
  135. let msg = "<p>The following areas were updated:</p>" + toast.arrayToHtmlList(data.areasUpdated);
  136. toast.success("Save Changes", msg, false);
  137. }
  138. if (then != null) {
  139. then();
  140. }
  141. },
  142. error: toast.defaultAjaxError
  143. });
  144. },
  145. saveAndSubmitPage() {
  146. this.updatePage(() => {
  147. this.submitPage();
  148. });
  149. },
  150. submitPage: function() {
  151. let url = createSubmitUrl(this.pageState);
  152. let formElement = $("#submitPageForm").get(0);
  153. let formData = new FormData(formElement);
  154. $.ajax({
  155. url: url,
  156. method: "POST",
  157. data: formData,
  158. cache: false,
  159. contentType: false,
  160. processData: false,
  161. success: function(data) {
  162. let redirectToStorePage = app.page.owner.moderator || app.page.owner.administrator || app.page.owner.trustLevel == 2;
  163. if (redirectToStorePage) {
  164. window.location.href = "/" + data.id;
  165. } else {
  166. window.location.reload();
  167. }
  168. },
  169. error: toast.defaultAjaxError
  170. });
  171. },
  172. previewPage: function() {
  173. let url = createPreviewUrl(this.pageState, this.pageId);
  174. window.open(url, "_blank");
  175. },
  176. screenshotChanged: function(event, elem) {
  177. let input = event.target;
  178. if (input.files && input.files[0]) {
  179. //clearImgError(elem, input);
  180. // the file is not a JPEG
  181. if (input.files[0].type != "image/jpeg") {
  182. // displayImgError(elem, "Not a JPEG.", input);
  183. $(input).val("");
  184. toastVue.showToast("red", "Image Rejected", "Image must be in JPEG format.");
  185. return;
  186. }
  187. let reader = new FileReader();
  188. reader.onload = function(event) {
  189. let img = new Image();
  190. img.onload = function() {
  191. if (this.width != 1280 || this.height != 720) {
  192. $(input).val("");
  193. toastVue.showToast("red", "Image Rejected", "Image dimenstions must be 1280 x 720");
  194. return;
  195. }
  196. $(elem).attr("src", event.target.result);
  197. $(elem).removeClass("hidden-element");
  198. };
  199. img.src = event.target.result;
  200. };
  201. reader.readAsDataURL(input.files[0]);
  202. }
  203. },
  204. clearScreenshot: function(event) {
  205. let imageIndex = $(event.target).attr("data-index");
  206. let imageIds = app.page.mediaLinks.imageIds.split(",");
  207. if (imageIndex < imageIds.length) {
  208. let imageId = imageIds[imageIndex];
  209. if (imageId.length > 0) {
  210. // in order to send data in the form, we add the deleted image to a SELECT MULTIPLE and mark it as selected.
  211. let newOption = new Option(imageId, imageId);
  212. newOption.setAttribute("selected", "selected");
  213. $("#deletedImages").append(newOption);
  214. // remove the file value from the file INPUT
  215. $(event.target)
  216. .parent()
  217. .children("input[type=file]")
  218. .val("");
  219. }
  220. imageIds.splice(imageIndex, 1);
  221. app.page.mediaLinks.imageIds = imageIds.join(",");
  222. }
  223. // if the index is larger than imageIds.length, it hasn't been uploaded.
  224. else {
  225. $(event.target)
  226. .parent()
  227. .children("img")
  228. .attr("src", "")
  229. .addClass("hidden-element");
  230. }
  231. },
  232. titleKeyUp: function() {
  233. let length = $("#titleInput").val().length;
  234. let elem = $("#titleCount");
  235. processInputCounter(elem, 6, length); // com.jayfella.website.core.PageRequirements
  236. },
  237. shortDescKeyUp: function() {
  238. let length = $("#shortDescTextArea").val().length;
  239. let elem = $("#shortDescCount");
  240. processInputCounter(elem, 10, length); // com.jayfella.website.core.PageRequirements
  241. },
  242. longDescKeyUp: function() {
  243. let length = $("#descriptionTextArea").val().length;
  244. let elem = $("#longDescCount");
  245. processInputCounter(elem, 50, length); // com.jayfella.website.core.PageRequirements
  246. },
  247. gitRepoLinkKeyUp: function() {
  248. var isValidLink = isUrlValid($("#gitRepoLink").val());
  249. if (isValidLink) {
  250. $("#gitRepoLinkDiv").removeClass("error");
  251. } else {
  252. $("#gitRepoLinkDiv").addClass("error");
  253. }
  254. },
  255. getArrayLength(input) {
  256. if (input.length === 0) {
  257. return 0;
  258. }
  259. return input.split(",").length;
  260. },
  261. getImageIndex: function(row, col) {
  262. let imges = this.page.mediaLinks.imageIds.split(",");
  263. let rowIndex = row - 1;
  264. let colIndex = col - 1;
  265. return rowIndex * 3 + colIndex;
  266. },
  267. isForkChanged: function(event) {
  268. let checked = !$("#forkCheckbox").hasClass("checked");
  269. if (checked) {
  270. // console.log("checked");
  271. $("#forkedRepoField").removeClass("hidden");
  272. } else {
  273. // console.log("unchecked");
  274. $("#forkedRepoField").addClass("hidden");
  275. }
  276. },
  277. addRepo: function(repo) {
  278. this.page.buildData.repositories = addToStringArray(this.page.buildData.repositories, repo);
  279. },
  280. removeRepo: function(index) {
  281. this.page.buildData.repositories = removeFromStringArray(this.page.buildData.repositories, index);
  282. },
  283. updateRepo: function(index, event) {
  284. this.page.buildData.repositories = updateStringArray(this.page.buildData.repositories, index, event);
  285. },
  286. addStoreDependency: function(dep) {
  287. this.page.buildData.storeDependencies = addToStringArray(this.page.buildData.storeDependencies, dep);
  288. },
  289. removeStoreDependency: function(index) {
  290. this.page.buildData.storeDependencies = removeFromStringArray(this.page.buildData.storeDependencies, index);
  291. },
  292. updateStoreDependency: function(index, event) {
  293. this.page.buildData.storeDependencies = updateStringArray(this.page.buildData.storeDependencies, index, event);
  294. },
  295. addHostedDependency: function(dep) {
  296. this.page.buildData.hostedDependencies = addToStringArray(this.page.buildData.hostedDependencies, dep);
  297. },
  298. removeHostedDependency: function(index) {
  299. this.page.buildData.hostedDependencies = removeFromStringArray(this.page.buildData.hostedDependencies, index);
  300. },
  301. updateHostedDependency: function(index, event) {
  302. this.page.buildData.hostedDependencies = updateStringArray(this.page.buildData.hostedDependencies, index, event);
  303. },
  304. buildJitPackHelperLink: function() {
  305. let repo = app.page.openSourceData.gitRepository;
  306. // https://github.com/jayfella/jme-materialize
  307. // https://bitbucket.org/JavaSabr/jmonkeybuilder/src/master/
  308. // https://gitlab.com/gitlab-org/gitlab-ce
  309. // https://jitpack.io/#jayfella/jme-materialize
  310. let link = "";
  311. if (repo.toLowerCase().startsWith("https://github.com")) {
  312. link = "https://jitpack.io/" + repo.replace("https://github.com/", "#");
  313. } else if (repo.toLowerCase().startsWith("https://bitbucket.org/")) {
  314. link = "https://jitpack.io/" + repo.replace("https://bitbucket.org/", "#").replace("/src/master/", "");
  315. } else if (repo.toLowerCase().startsWith("https://gitlab.com/")) {
  316. link = "https://jitpack.io/" + repo.replace("https://gitlab.com/", "#");
  317. }
  318. $("#jitpackHelperLink").attr("href", link);
  319. },
  320. getBackgroundImageIndex: function(index) {
  321. if (index < 0 || index > 8) {
  322. return 9;
  323. }
  324. return index;
  325. }
  326. }
  327. });
  328. function addToStringArray(stringIn, value) {
  329. if (stringIn.length !== 0) {
  330. stringIn += ",";
  331. }
  332. stringIn += value;
  333. return stringIn;
  334. }
  335. function removeFromStringArray(stringIn, index) {
  336. let values = stringIn.split(",");
  337. values.splice(index, 1);
  338. let newValue = values.join(",");
  339. return newValue;
  340. }
  341. function updateStringArray(stringIn, index, event) {
  342. let value = $(event.target).val();
  343. let values = stringIn.split(",");
  344. values[index] = value;
  345. let newValue = values.join(",");
  346. return newValue;
  347. }
  348. // marks text as red if the length is less than minValue.
  349. function processInputCounter(elem, minValue, length) {
  350. elem.html("" + numberWithCommas(length));
  351. if (length < minValue) {
  352. elem.addClass("ui red text");
  353. } else {
  354. elem.removeClass("ui red text");
  355. }
  356. }
  357. // https://stackoverflow.com/a/2901298
  358. function numberWithCommas(x) {
  359. return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  360. }
  361. function isUrlValid(url) {
  362. return /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(
  363. url
  364. );
  365. }
  366. function mountControls() {
  367. $(".dropdown")
  368. .dropdown("destroy")
  369. .dropdown(); // this must be before the tags dropdown
  370. $(".checkbox")
  371. .dropdown("destroy")
  372. .checkbox();
  373. // stop tag additions form submitting the form.
  374. $("#tagsDropdown")
  375. .dropdown("destroy")
  376. .dropdown({
  377. allowAdditions: true,
  378. delimiter: "|",
  379. keys: {
  380. delimiter: 13 // stop the enter key submitting the form
  381. }
  382. });
  383. }