index.php 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127
  1. <!DOCTYPE html>
  2. <meta name="apple-mobile-web-app-capable" content="yes" />
  3. <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
  4. <html>
  5. <head>
  6. <meta charset="UTF-8">
  7. <script type='text/javascript' charset='utf-8'>
  8. // Hides mobile browser's address bar when page is done loading.
  9. window.addEventListener('load', function(e) {
  10. setTimeout(function() { window.scrollTo(0, 1); }, 1);
  11. }, false);
  12. </script>
  13. <title>WriterA</title>
  14. <script src="../script/jquery.min.js"></script>
  15. <link rel="stylesheet" href="mde/simplemde.min.css">
  16. <script src="../script/ao_module.js"></script>
  17. <script src="mde/simplemde.min.js"></script>
  18. <script src="jspdf.min.js"></script>
  19. <style>
  20. body{
  21. margin:0px;
  22. font-family:Georgia;
  23. background-color:#f9f9f9;
  24. }
  25. .topmenu{
  26. font-size:90%;
  27. margin-bottom: 0px !important;
  28. width:100%;
  29. font-family: Arial, Helvetica, sans-serif;
  30. background-color:#f9f9f9;
  31. }
  32. #status{
  33. font-size:80%;
  34. text-overflow: ellipsis !important;
  35. overflow: hidden !important;
  36. padding-left:20px;
  37. padding-top:5px;
  38. }
  39. .rightcorner{
  40. position:fixed;
  41. top:5px;
  42. right:5px;
  43. z-index:999;
  44. cursor: pointer;
  45. }
  46. .button {
  47. color: black;
  48. border: none;
  49. padding: 1px 10px;
  50. text-align: center;
  51. text-decoration: none;
  52. display: inline;
  53. cursor: pointer;
  54. -webkit-user-select: none; /* Safari */
  55. -moz-user-select: none; /* Firefox */
  56. -ms-user-select: none; /* IE10+/Edge */
  57. user-select: none; /* Standard */
  58. }
  59. .button:hover{
  60. background-color:#efefef;
  61. }
  62. .item{
  63. height: 35px;
  64. display: inline;
  65. }
  66. #main{
  67. top:35px;
  68. font-family: Georgia;
  69. }
  70. #contextMenu{
  71. min-width:100px;
  72. background-color:rgb(247, 247, 247);
  73. position:fixed;
  74. top:0px;
  75. left:0px;
  76. border: 1px solid #9e9e9e;
  77. z-index:999;
  78. padding-left:14px;
  79. padding-top:8px;
  80. display:none;
  81. padding-bottom:10px;
  82. }
  83. .menuItem{
  84. padding-bottom:3px;
  85. padding-left:5px;
  86. padding-right:15px;
  87. border-left: 1px solid #9e9e9e;
  88. cursor:pointer;
  89. }
  90. .menuItem:hover{
  91. background-color:#e3e3e3;
  92. }
  93. .tooltip {
  94. position: relative;
  95. display: inline-block;
  96. border-bottom: 1px dotted black;
  97. }
  98. .tooltip .tooltiptext {
  99. visibility: hidden;
  100. width: 200px;
  101. background-color: rgba(61,61,61, 0.8);
  102. color: #fff;
  103. text-align: center;
  104. border-radius: 6px;
  105. padding: 5px 0;
  106. /* Position the tooltip */
  107. position: absolute;
  108. z-index: 1;
  109. top: 100%;
  110. left: 50%;
  111. margin-left: -150px;
  112. }
  113. .tooltip:hover .tooltiptext {
  114. visibility: visible;
  115. }
  116. .centered{
  117. z-index:999;
  118. position:fixed;
  119. left:20%;
  120. right:20%;
  121. top:10%;
  122. max-height:80%;
  123. padding:20px;
  124. background-color:#f9f9f9;
  125. border: 1px solid #cccccc;
  126. }
  127. #specialCharInsert{
  128. position:fixed !important;
  129. z-index:999 !important;
  130. position:fixed !important;
  131. left:20% !important;
  132. right:20% !important;
  133. top:10% !important;
  134. height:80%;
  135. padding:20px;
  136. background-color:#ededed;
  137. box-shadow: 3px 3px 4px #f9f9f9;
  138. }
  139. .scs{
  140. display:inline-block;
  141. margin: 3px;
  142. padding-left: 3px;
  143. padding-top: 3px;
  144. width: 20px !important;
  145. height:30px;
  146. border: 1px solid #c4c4c4;
  147. cursor:pointer;
  148. font-weight: bold;
  149. }
  150. .scs:hover{
  151. border: 1px solid #6e86a0;
  152. background-color:#a9c7e8;
  153. }
  154. .selectable{
  155. border: 2px solid transparent !important;
  156. margin: 5px !important;
  157. }
  158. .selectable:hover{
  159. border:2px solid #2bb5ff !important;
  160. }
  161. .selected{
  162. border:2px solid #2bb5ff !important;
  163. }
  164. .closebtn{
  165. position:absolute;
  166. top:-10px;
  167. right:-10px;
  168. width:30px !important;
  169. height:30px !important;
  170. border: 1px solid #4c4c4c;
  171. }
  172. .image{
  173. max-height:200px;
  174. }
  175. .fluid{
  176. width: 80% !important;
  177. }
  178. .primary{
  179. border: 1px solid #3b3b3b;
  180. }
  181. </style>
  182. </head>
  183. <body>
  184. <div class="topmenu">
  185. <div style="padding:5px;padding-left:10px;">
  186. <a id="backBtn" class="item" href="../index.php">⬅️</a>
  187. <a class="item" style="font-size:120%;margin-right:15px;">🖋 WriterA</a>
  188. <a class="item button" onClick = "showContextMenu(this);">File</a>
  189. <a class="item button" onClick = "showContextMenu(this);">Edit</a>
  190. <a class="item button" onClick = "showContextMenu(this);">Help</a>
  191. <a id="extInputDisplay" class="rightcorner" style="color:red;" onClick="toggleExternalInputMode();">
  192. ⌨ AIME
  193. </a>
  194. <div style="padding-top:3px;padding-left:5px;">
  195. <a class="item" id="status" style="padding-top:10px !important;"></a>
  196. </div>
  197. </div>
  198. </div>
  199. <div id="main">
  200. <textarea id="mde"></textarea>
  201. </div>
  202. <div id="contextMenu">
  203. <div class="menuItem">Loading...</div>
  204. </div>
  205. <div id="selectImage" style="display:none;" class="centered">
  206. <div id="previewArea" style="height:350px;overflow-y: auto;" align="left">
  207. </div>
  208. <div style="position:absolute;right:30px;bottom:30px;background-color:white;">
  209. <button class="ts tiny primary button" onClick="insertSelectedImages();">Insert</button>
  210. <button class="ts tiny basic button" onClick="$(this).parent().parent().hide();">Cancel</button>
  211. </div>
  212. <button class="closebtn button" onClick="$(this).parent().hide();">X</button>
  213. </div>
  214. <div id="insertImage" style="display:none;" class="centered">
  215. <p><i class="file image outline icon"></i>Insert Image</p>
  216. <iframe width="100%" height="260px" src="../Upload%20Manager/upload_interface_min.php?target=WriterA&filetype=png,jpg,jpeg"> </iframe>
  217. <button class="ts right floated tiny basic button" onClick="$(this).parent().hide();">Cancel</button>
  218. <button class="ts right floated tiny primary button" onClick="initImageSelector();">Insert</button>
  219. <button class="button closebtn" onClick="$(this).parent().hide();">X</button>
  220. </div>
  221. <div id="saveNewDocument" style="display:none;" class="centered">
  222. <p><i class="save icon"></i>💾 Save As New Document</p>
  223. <p>Storage Directory</p>
  224. <div class="ts mini fluid action left icon input">
  225. <input class="fluid" type="text" id="newStorageDirectory" placeholder="Storage Path" onClick="ao_module_focus();">
  226. <button style="display:inline-block;" class="ts primary button" onClick="selectCreatePath();">Open</button>
  227. </div><br><p>Document Filename</p>
  228. <div class="ts left icon mini fluid input">
  229. <input class="fluid" id="createFileName" type="text" placeholder="Filename" onClick="ao_module_focus();">
  230. <i class="file text outline icon"></i>
  231. </div>
  232. <br><br>
  233. <div id="createError" style="display:none;" class="ts inverted mini negative segment">
  234. <p id="createErrorMessage">Loading...</p>
  235. </div>
  236. <button class="ts tiny primary button" onClick="$(this).parent().hide(); saveAs=false;">Cancel</button>
  237. <button class="ts tiny primary button" onClick="confirmCreateNewDocument();">Confirm</button>
  238. <button class="closebtn button" onClick="$(this).parent().hide(); saveAs=false;">X</button>
  239. </div>
  240. <div id="information" style="display:none;" class="centered">
  241. <div style="overflow-y:auto;height:300px">
  242. <h4><i class="write icon"></i>WriterA for ArOZ Online System</h4>
  243. <p>WriterA is developed by Toby Chui for the ArOZ Online System. <br>
  244. Originate from the ArOZ Document (which has been deprecated), the WriterA is a new generation of Mark Down Editor powered by SimpleMDE and ArOZ File System.
  245. Providing the power of simple yet quick editing within the ArOZ Virtual Desktop Environment as well as Document Editing under Normal Web View Mode.<br><br>
  246. WriterA support new generations of ArOZ Online API including ArOZ IME, floatWindows and File Open API from the latest ArOZ Online Standard.
  247. Please reference the README.txt included with the module for development details.<br><br><br>
  248. <small style="font-size:80%">Developed since March 2019, Project under ArOZ Online System feat. IMUS Laboratory</small>
  249. </p>
  250. </div>
  251. <button class="closebtn button" onClick="$(this).parent().hide();">X</button>
  252. </div>
  253. <div id="license" style="display:none;" class="centered">
  254. <div style="overflow-y:auto;height:300px">
  255. <p>Project licensed under MIT License<br> Copyright 2019 Toby Chui</p>
  256. <p style="font-size:80%">MIT License <br>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>
  257. </div>
  258. <button class="button closebtn" onClick="$(this).parent().hide();">X</button>
  259. </div>
  260. <div id="specialCharInsert" class="centered ts segment" style="display:none;">
  261. Insert Special Character
  262. <br>
  263. <div id="iscl" style="max-height:70%;left:0;right:0;overflow-y:scroll;overflow-wrap: break-word;">
  264. </div>
  265. <hr>
  266. <button style="background-color:white;border: 1px solid #707070;padding:3px;cursor:pointer;" onClick="$('#specialCharInsert').hide();">Close</button>
  267. <p style="display:inline-block;padding-left:20px;" id="scid">N/A</p>
  268. </div>
  269. <div style="display:none">
  270. <div id="data_editingFilePath"><?php if (isset($_GET['filepath']) && $_GET['filepath'] != "" && (file_exists($_GET['filepath']) || file_exists("../" . $_GET['filepath']))){
  271. echo $_GET['filepath'];
  272. } ?></div>
  273. <div id="data_modulename"><?php echo dirname(str_replace("\\","/",__FILE__)); ?></div>
  274. <div id="data_umexists"><?php
  275. if (file_exists("../Upload Manager/")){
  276. echo "true";
  277. }else{
  278. echo "false";
  279. }
  280. ?></div>
  281. </div>
  282. <script>
  283. var currentFilepath = $("#data_editingFilePath").text().trim().replace("../","");
  284. var menuItem = {
  285. File: ["New File","Open File","Save","Save As","Download","Reload","Print","Export as HTML","Export as PDF","Export as Plain Text","Close"],
  286. Edit:["Undo","Redo","Insert Special Characters","Toggle External Input Method","Upload Image","Insert Image","Save as HTML","Save as PDF"],
  287. Help:["About WriterA","Markdown Guide","License"]
  288. };
  289. var currentContextMenuItem = "";
  290. var lastSaveContent = "";
  291. var umExists = $("#data_umexists").text().trim() == "true";
  292. var simplemde = new SimpleMDE({
  293. element: document.getElementById("mde"),
  294. spellChecker: false,
  295. showIcons: ["code", "table","clean-block","horizontal-rule"],
  296. hideIcons: ["guide"],
  297. status: ["autosave", "lines", "words", "cursor"],
  298. });
  299. var enableExternalInput = false;
  300. var shiftHolding = false;
  301. var controlHolding = false;
  302. var saveAs = false; //If saveAs = false, after creating the new document, the current page will be updated to that new document. If set to true, new window will pop out for the new document (aka save As)
  303. var lastCursorPosition;
  304. //Run when the document is ready
  305. $(document).ready(function(){
  306. //Initialization function
  307. initWriterA();
  308. loadAllSpecialCharacter();
  309. $(".scs").hover(function(){
  310. var keyid = $(this).attr("keyid");
  311. $("#scid").html("HTML Keycode: #&" + keyid);
  312. });
  313. $(".scs").on("mousedown",function(){
  314. insertTextAtCursor($(this).text());
  315. $("#specialCharInsert").hide();
  316. });
  317. if (ao_module_getStorage("WriterA","enableExternalInput") == "true"){
  318. toggleExternalInputMode();
  319. }
  320. //Bind save check status
  321. setInterval(function(){
  322. var icon = '<i class="home icon"></i>/';
  323. if (currentFilepath.substring(0,6) == "/media"){
  324. //This filepath is in external storage
  325. icon = "";
  326. }
  327. if (currentFilepath == ""){
  328. updateStatus(icon + currentFilepath + " - NOT SAVED");
  329. return;
  330. }
  331. if (!checkIfContentSaved()){
  332. updateStatus(icon + currentFilepath.replace("./","") + " - 💾 ❗ NOT SAVED");
  333. }else{
  334. updateStatus(icon + currentFilepath.replace("./","") + " - 💾 ✔️ Saved");
  335. }
  336. },1000);
  337. });
  338. //Run before the page is rendered
  339. if (ao_module_virtualDesktop){
  340. $("#backBtn").hide();
  341. $("body").css("padding-bottom","30px");
  342. ao_module_setWindowSize(1050,550);
  343. }else{
  344. $("#extInputDisplay").hide();
  345. }
  346. //Functions related to text insert and external input method
  347. function insertTextAtCursor(text,ignoreNodename = false){
  348. if (ignoreNodename){
  349. pos = simplemde.codemirror.getCursor();
  350. simplemde.codemirror.setSelection(pos, pos);
  351. simplemde.codemirror.replaceSelection(text);
  352. return;
  353. }
  354. if (document.activeElement.nodeName == "TEXTAREA"){
  355. //The user is focused on the textarea.
  356. pos = simplemde.codemirror.getCursor();
  357. simplemde.codemirror.setSelection(pos, pos);
  358. simplemde.codemirror.replaceSelection(text);
  359. }else if (document.activeElement.nodeName == "INPUT"){
  360. //The user is focused on an input instead.
  361. $(document.activeElement).val($(document.activeElement).val() + text);
  362. }else{
  363. //Not focused anywhere. Inject into simpleMDE using the last active position
  364. simplemde.codemirror.setSelection(lastCursorPosition,lastCursorPosition);
  365. simplemde.codemirror.replaceSelection(text);
  366. }
  367. }
  368. simplemde.codemirror.on("cursorActivity",function(){
  369. let pos = simplemde.codemirror.getCursor();
  370. lastCursorPosition = pos;
  371. });
  372. function loadAllSpecialCharacter(){
  373. $("#iscl").html("");
  374. for (var i =161; i < 1023; i++){
  375. if (i != 173){
  376. $("#iscl").append("<div class='scs' keyid='" + i +"'>" + String.fromCharCode(i) + "</div>");
  377. }
  378. }
  379. }
  380. function initImageSelector(){
  381. //After uploading the image, allow user to select the image they want to use
  382. $("#insertImage").hide();
  383. $("#previewArea").html("");
  384. $.get("imageLoader.php",function(data){
  385. var images = data;
  386. for (var i =0; i < images.length; i++){
  387. $("#previewArea").append('<img class="ts small rounded image selectable" src="' + images[i] + '" onClick="selectThisImage(this);">');
  388. }
  389. });
  390. $("#selectImage").show();
  391. }
  392. function selectThisImage(object){
  393. if ($(object).hasClass("selected")){
  394. $(object).removeClass("selected");
  395. }else{
  396. $(object).addClass("selected");
  397. }
  398. }
  399. function insertSelectedImages(){
  400. $(".selected").each(function(){
  401. var imagePath = $(this).attr("src");
  402. insertTextAtCursor("![](" + imagePath + ")",true);
  403. });
  404. $("#selectImage").hide();
  405. }
  406. function confirmCreateNewDocument(){
  407. //This is a new document and now it is time to create this document and save it to file system
  408. var mdeContent = JSON.stringify(simplemde.value());
  409. var filename = $("#createFileName").val().trim();
  410. var saveTarget = $("#newStorageDirectory").val().trim();
  411. var error = false;
  412. if (filename == ""){
  413. $("#createFileName").parent().addClass("error");
  414. error = true;
  415. }
  416. if (saveTarget == ""){
  417. $("#newStorageDirectory").parent().addClass("error");
  418. error = true;
  419. }
  420. if (error){
  421. return;
  422. }else{
  423. //Remove the error class if exists
  424. $("#createFileName").parent().removeClass("error");
  425. $("#newStorageDirectory").parent().removeClass("error");
  426. }
  427. if (getExtension(filename) == filename){
  428. //This file do not have an extension. Save as mark down instead.
  429. filename = filename + ".md";
  430. }
  431. //Post the data and create the document
  432. $.post( "documentIO.php", { content: mdeContent,create: saveTarget + filename})
  433. .done(function( data ) {
  434. console.log("[WriterA] Created File: " + data);
  435. if (data.includes("ERROR") == false){
  436. //Create and save success.
  437. if (saveAs){
  438. //save as new document. Open a new document for that.
  439. var moduleName = $("#data_modulename").text().trim().split("/").pop();
  440. var uid = ao_module_utils.getRandomUID();
  441. var result = ao_module_newfw(moduleName + "/index.php?filepath=" + data.replace("../",""),"WriterA","file text outline", uid,1050,550,undefined,undefined,true,false);
  442. if (result == false){
  443. window.open("index.php?filepath=" + data);
  444. }
  445. }else{
  446. //Create new document. Updat the current document to that of the new one
  447. updateStatus('💾 File Saved');
  448. lastSaveContent = simplemde.value();
  449. currentFilepath = data.replace("../","");
  450. setWindowTitle(ao_module_codec.decodeUmFilename(basename(currentFilepath)) + " - WriterA");
  451. }
  452. $("#saveNewDocument").hide();
  453. }else{
  454. $("#createErrorMessage").html(data);
  455. $("#createError").slideDown().delay(5000).slideUp();
  456. }
  457. });
  458. }
  459. function selectCreatePath(){
  460. var fwID = ao_module_utils.getRandomUID();
  461. if (ao_module_virtualDesktop){
  462. ao_module_openFileSelector(fwID,"createDirSelected",undefined,undefined,false,"folder");
  463. }else{
  464. ao_module_openFileSelectorTab(fwID,"../",false,"folder",createDirSelected);
  465. }
  466. }
  467. $("#createFileName").on("keydown",function(e){
  468. if (e.keyCode == 13){
  469. //Enter pressed on the input filename, activate save as well
  470. confirmCreateNewDocument();
  471. }
  472. });
  473. function createDirSelected(object){
  474. result = JSON.parse(object);
  475. for (var i=0; i < result.length; i++){
  476. var filename = result[i].filename;
  477. var filepath = result[i].filepath;
  478. $("#newStorageDirectory").val(filepath + "/");
  479. }
  480. }
  481. $(document).keydown(function(e){
  482. switch(e.keyCode){
  483. case 16:
  484. //Shift
  485. shiftHolding = true;
  486. break;
  487. case 17:
  488. //Ctrl
  489. controlHolding = true;
  490. break;
  491. case 83:
  492. if (controlHolding){
  493. //Ctrl + S
  494. e.preventDefault();
  495. //Save the document here
  496. saveFile();
  497. }else{
  498. ao_module_inputs.hookKeyHandler(e);
  499. }
  500. default:
  501. if (enableExternalInput){
  502. ao_module_inputs.hookKeyHandler(e);
  503. }
  504. break;
  505. }
  506. if (shiftHolding && controlHolding){
  507. toggleExternalInputMode();
  508. }
  509. }).keyup(function(e){
  510. switch(e.keyCode){
  511. case 16:
  512. //Shift
  513. shiftHolding = false;
  514. break;
  515. case 17:
  516. //Ctrl
  517. controlHolding = false;
  518. break;
  519. }
  520. });
  521. function updateStatus(text){
  522. $("#status").html("📄 " + text);
  523. }
  524. function toggleExternalInputMode(){
  525. //Toggle Input Method
  526. enableExternalInput = !enableExternalInput;
  527. if (!enableExternalInput){
  528. //External input mode disabled
  529. $("#extInputDisplay").css("color","red");
  530. ao_module_inputs.hookStdIn(function(text){});
  531. ao_module_saveStorage("WriterA","enableExternalInput","false");
  532. }else{
  533. //External input mode enabled
  534. $("#extInputDisplay").css("color","green");
  535. ao_module_inputs.hookStdIn(function(text){insertTextAtCursor(text);});
  536. ao_module_saveStorage("WriterA","enableExternalInput","true");
  537. }
  538. }
  539. //Run when the editor is ready
  540. function initWriterA(){
  541. //Check if the current editing filepath is empty. If yes, this is a new document.
  542. ao_module_setWindowIcon("file text outline");
  543. if (currentFilepath == ""){
  544. setWindowTitle("Untitiled - WriterA");
  545. updateStatus("Editor Ready!");
  546. }else{
  547. setWindowTitle(ao_module_codec.decodeUmFilename(basename(currentFilepath)) + " - WriterA");
  548. loadDocumentFromPath(currentFilepath);
  549. var icon = '🏠/';
  550. if (currentFilepath.substring(0,6) == "/media"){
  551. //This filepath is in external storage
  552. icon = "";
  553. }
  554. updateStatus(icon + currentFilepath + " - Loaded");
  555. }
  556. }
  557. function setWindowTitle(text){
  558. ao_module_setWindowTitle(text);
  559. document.title = text;
  560. }
  561. function loadDocumentFromPath(path){
  562. $.get("documentIO.php?filepath=" + path,function(data){
  563. data = JSON.parse(data);
  564. simplemde.value(data);
  565. lastSaveContent = data;
  566. });
  567. }
  568. function checkIfContentSaved(){
  569. if (simplemde.value() != lastSaveContent){
  570. return false;
  571. }else{
  572. return true;
  573. }
  574. }
  575. //New SaveAs Handler
  576. function saveAsHandler(fileData){
  577. result = JSON.parse(fileData);
  578. for (var i=0; i < result.length; i++){
  579. //var filename = result[i].filename;
  580. var filepath = result[i].filepath;
  581. var filename = result[i].filename;
  582. var tmp = filepath.split("/")
  583. tmp.pop();
  584. filepath = tmp.join("/") + "/";
  585. var mdeContent = JSON.stringify(simplemde.value());
  586. var saveTarget = filepath;
  587. var error = false;
  588. if (getExtension(filename) == filename){
  589. //This file do not have an extension. Save as mark down instead.
  590. filename = filename + ".md";
  591. }
  592. //Post the data and create the document
  593. $.post( "documentIO.php", { content: mdeContent,create: saveTarget + filename})
  594. .done(function( data ) {
  595. console.log("[WriterA] Created File: " + data);
  596. if (data.includes("ERROR") == false){
  597. //Create and save success.
  598. if (saveAs){
  599. //save as new document. Open a new document for that.
  600. var moduleName = $("#data_modulename").text().trim().split("/").pop();
  601. var uid = ao_module_utils.getRandomUID();
  602. var result = ao_module_newfw(moduleName + "/index.php?filepath=" + data.replace("../",""),"WriterA","file text outline", uid,1050,550,undefined,undefined,true,false);
  603. if (result == false){
  604. window.open("index.php?filepath=" + data);
  605. }
  606. }else{
  607. //Create new document. Updat the current document to that of the new one
  608. updateStatus('💾 File Saved');
  609. lastSaveContent = simplemde.value();
  610. currentFilepath = data.replace("../","");
  611. setWindowTitle(ao_module_codec.decodeUmFilename(basename(currentFilepath)) + " - WriterA");
  612. }
  613. $("#saveNewDocument").hide();
  614. }else{
  615. $("#createErrorMessage").html(data);
  616. $("#createError").slideDown().delay(5000).slideUp();
  617. }
  618. });
  619. }
  620. }
  621. //2020/01/15 ADDED
  622. //for saving the datauri only
  623. var datauri = "";
  624. //END
  625. //Menu item handler
  626. function menuClicked(object){
  627. var text = $(object).text().trim();
  628. switch(text){
  629. case "New File":
  630. newFile();
  631. break;
  632. case "Open File":
  633. openFile();
  634. break;
  635. case "Save":
  636. saveFile();
  637. hideContextMenu();
  638. break
  639. case "Save As":
  640. saveAs = true;
  641. var uid = ao_module_utils.getRandomUID();
  642. if (ao_module_virtualDesktop){
  643. ao_module_openFileSelector(uid,"saveAsHandler",undefined,undefined,false,"new","newdoc.md",true);
  644. }else{
  645. ao_module_openFileSelectorTab(uid,"../",true,"new",saveAsHandler,"newdoc.md",true);
  646. }
  647. //$("#saveNewDocument").fadeIn("fast");
  648. hideContextMenu();
  649. break;
  650. case "Toggle External Input Method":
  651. toggleExternalInputMode();
  652. hideContextMenu();
  653. break;
  654. case "About WriterA":
  655. $("#information").show();
  656. hideContextMenu();
  657. break;
  658. case "License":
  659. $("#license").show();
  660. hideContextMenu();
  661. break;
  662. case "Print":
  663. printFile();
  664. hideContextMenu();
  665. break;
  666. case "Close":
  667. handleWindowClose();
  668. //ao_module_close();
  669. break;
  670. case "Reload":
  671. window.location.reload();
  672. break;
  673. case "Undo":
  674. simplemde.undo();
  675. hideContextMenu();
  676. break;
  677. case "Redo":
  678. simplemde.redo();
  679. hideContextMenu();
  680. break;
  681. case "Insert Special Characters":
  682. $("#specialCharInsert").show();
  683. hideContextMenu();
  684. break;
  685. case "Markdown Guide":
  686. if (ao_module_virtualDesktop){
  687. ao_module_newfw("https://simplemde.com/markdown-guide",'Markdown Guide','sticky note outline',ao_module_utils.getRandomUID(),475,700);
  688. }else{
  689. window.open("https://simplemde.com/markdown-guide");
  690. }
  691. hideContextMenu();
  692. break;
  693. case "Insert Image":
  694. initImageSelector();
  695. hideContextMenu();
  696. break;
  697. case "Upload Image":
  698. if (umExists){
  699. //Allow file upload and insert
  700. $("#insertImage").show();
  701. }else{
  702. //Insert url image bracket to the text
  703. insertTextAtCursor("![](http://)");
  704. }
  705. hideContextMenu();
  706. break;
  707. case "Download":
  708. var filename = "";
  709. if (currentFilepath != ""){
  710. filename = ao_module_codec.decodeUmFilename(basename(currentFilepath));
  711. }else{
  712. filename = prompt("Please enter a filename","Untitled.txt");
  713. if (filename == null) {
  714. filename = "Untitled.txt"
  715. }
  716. }
  717. download(filename,simplemde.value());
  718. hideContextMenu();
  719. break;
  720. case "Export as PDF":
  721. $.post( "documentIO.php", { parseMD: JSON.stringify(simplemde.value())})
  722. .done(function( data ) {
  723. printPDF(data,true);
  724. });
  725. hideContextMenu();
  726. break;
  727. case "Save as PDF":
  728. //Work in progress
  729. //v1.0 2020/1/15 14:46PM PST AlanYeung First version
  730. $.post( "documentIO.php", { parseMD: JSON.stringify(simplemde.value())})
  731. .done(function( data ) {
  732. printPDF(data,false);
  733. if(ao_module_virtualDesktop){
  734. ao_module_openFileSelector(Math.round(Math.random()*10000),"saveas",undefined,undefined,false,"new","Untitled.pdf",false);
  735. //ao_module_openFileSelector(Math.round(Math.random()*10000), "../",allowMultiple = false, selectMode = "folder",callBack=saveasPDF);
  736. }else{
  737. ao_module_openFileSelectorTab(Math.round(Math.random()*10000),"saveas",undefined,undefined,false,"new","Untitled.pdf",false);
  738. //ao_module_openFileSelectorTab(Math.round(Math.random()*10000), "../",allowMultiple = false, selectMode = "folder",callBack=saveasPDF);
  739. }
  740. });
  741. hideContextMenu();
  742. break;
  743. case "Save as HTML":
  744. //Work in progress
  745. //v1.0 2020/1/15 14:46PM PST AlanYeung First version
  746. $.post( "documentIO.php", { parseMD: JSON.stringify(simplemde.value())})
  747. .done(function( data ) {
  748. datauri = data;
  749. if(ao_module_virtualDesktop){
  750. ao_module_openFileSelector(Math.round(Math.random()*10000),"saveas",undefined,undefined,false,"new","Untitled.html",false);
  751. //ao_module_openFileSelector(Math.round(Math.random()*10000), "../",allowMultiple = false, selectMode = "folder",callBack=saveasHTML);
  752. }else{
  753. ao_module_openFileSelectorTab(Math.round(Math.random()*10000),"saveas",undefined,undefined,false,"new","Untitled.html",false);
  754. //ao_module_openFileSelectorTab(Math.round(Math.random()*10000), "../",allowMultiple = false, selectMode = "folder",callBack=saveasHTML);
  755. }
  756. });
  757. hideContextMenu();
  758. break;
  759. case "Export as HTML":
  760. var filename = prompt("Please enter a filename","Untitled.html");
  761. $.post( "documentIO.php", { parseMD: JSON.stringify(simplemde.value())})
  762. .done(function( data ) {
  763. download(filename,data);
  764. });
  765. hideContextMenu();
  766. break;
  767. case "Export as Plain Text":
  768. var filename = "";
  769. if (currentFilepath != ""){
  770. filename = ao_module_codec.decodeUmFilename(basename(currentFilepath));
  771. }else{
  772. filename = prompt("Please enter a filename","Untitled.txt");
  773. if (filename == null) {
  774. filename = "Untitled.txt"
  775. }
  776. }
  777. $.post( "documentIO.php", { parseMD: JSON.stringify(simplemde.value())})
  778. .done(function( data ) {
  779. download(filename,$(data).text());
  780. });
  781. hideContextMenu();
  782. default:
  783. hideContextMenu();
  784. break;
  785. }
  786. }
  787. $(window).bind('beforeunload', function(){
  788. if (!checkIfContentSaved()){
  789. return 'Document is not saved yet. Confirm leaving?';
  790. }
  791. });
  792. //Override function for ao_module_close();
  793. function ao_module_close(){
  794. if (ao_module_virtualDesktop){
  795. handleWindowClose();
  796. return true;
  797. }
  798. return false;
  799. }
  800. function handleWindowClose(){
  801. if (checkIfContentSaved()){
  802. //Content saved. Close window.
  803. if (ao_module_virtualDesktop){
  804. parent.closeWindow(ao_module_windowID);
  805. }
  806. }else{
  807. if (confirm("Document is not saved yet. Confirm leaving?")){
  808. parent.closeWindow(ao_module_windowID);
  809. }
  810. }
  811. }
  812. function printPDF(htmlContent,saveAsFile = true) {
  813. var pdf = new jsPDF('p', 'pt', 'letter');
  814. // source can be HTML-formatted string, or a reference
  815. // to an actual DOM element from which the text will be scraped.
  816. source = htmlContent;
  817. // we support special element handlers. Register them with jQuery-style
  818. // ID selector for either ID or node name. ("#iAmID", "div", "span" etc.)
  819. // There is no support for any other type of selectors
  820. // (class, of compound) at this time.
  821. specialElementHandlers = {
  822. // element with id of "bypass" - jQuery style selector
  823. '#bypassme': function (element, renderer) {
  824. // true = "handled elsewhere, bypass text extraction"
  825. return true
  826. }
  827. };
  828. margins = {
  829. top: 80,
  830. bottom: 60,
  831. left: 40,
  832. width: 522
  833. };
  834. // all coords and widths are in jsPDF instance's declared units
  835. // 'inches' in this case
  836. pdf.fromHTML(
  837. source, // HTML string or DOM elem ref.
  838. margins.left, // x coord
  839. margins.top, { // y coord
  840. 'width': margins.width, // max width of content on PDF
  841. 'elementHandlers': specialElementHandlers
  842. },
  843. function (dispose) {
  844. // dispose: object with X, Y of the last line add to the PDF
  845. // this allow the insertion of new lines after html
  846. var filename = "";
  847. if (currentFilepath != ""){
  848. filename = ao_module_codec.decodeUmFilename(basename(currentFilepath));
  849. }else{
  850. filename = "Untitled"
  851. }
  852. if(saveAsFile){
  853. pdf.save(filename + '.pdf');
  854. }else{
  855. datauri = pdf.output("datauristring");
  856. }
  857. }, margins
  858. );
  859. }
  860. function saveas(path){
  861. try{
  862. console.log(path);
  863. var filepath = JSON.parse(path)[0]["filepath"];
  864. var formd = new FormData();
  865. formd.append('fname', "../" + filepath);
  866. formd.append('data', datauri);
  867. datauri = "";
  868. $.ajax({
  869. type: 'POST',
  870. url: 'save.php',
  871. data: formd,
  872. processData: false,
  873. contentType: false
  874. }).done(function(data) {
  875. alert(data);
  876. });
  877. }catch(err) {
  878. alert("Error! Action can't be performed. Error message:" + err.message);
  879. }
  880. }
  881. function download(filename, text) {
  882. var element = document.createElement('a');
  883. element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
  884. element.setAttribute('download', filename);
  885. element.style.display = 'none';
  886. document.body.appendChild(element);
  887. element.click();
  888. document.body.removeChild(element);
  889. }
  890. //Functions related to menus
  891. function newFile(){
  892. var moduleName = $("#data_modulename").text().trim().split("/").pop();
  893. var uid = (new Date()).getTime();
  894. var result = ao_module_newfw(moduleName + "/index.php","WriterA","file text outline", uid,1050,550,undefined,undefined,true,false);
  895. if (result == false){
  896. window.open("index.php");
  897. }
  898. hideContextMenu();
  899. }
  900. function saveFile(){
  901. if (currentFilepath.trim() != ""){
  902. //This is a file with path. save with savepath function call
  903. var mdeContent = JSON.stringify(simplemde.value());
  904. $.post( "documentIO.php", { content: mdeContent,savepath: currentFilepath.trim()})
  905. .done(function( data ) {
  906. console.log("[WriterA] Save File: " + data);
  907. if (data.includes("ERROR") == false){
  908. updateStatus('💾 File Saved');
  909. lastSaveContent = simplemde.value();
  910. }else{
  911. updateStatus('⚠️ Something went wrong when trying to save this file.');
  912. }
  913. });
  914. }else{
  915. //This is a new file. Let the user choose where to save this file first and use create function call to save
  916. var uid = ao_module_utils.getRandomUID();
  917. if (ao_module_virtualDesktop){
  918. ao_module_openFileSelector(uid,"saveAsHandler",undefined,undefined,false,"new","newdoc.md",true);
  919. }else{
  920. ao_module_openFileSelectorTab(uid,"../",true,"new",saveAsHandler,"newdoc.md",true);
  921. }
  922. }
  923. }
  924. function openFile(){
  925. var uid = ao_module_utils.getRandomUID();
  926. if (ao_module_virtualDesktop){
  927. ao_module_openFileSelector(uid,"openFileFromSelector");
  928. }else{
  929. ao_module_openFileSelectorTab(uid,"../",false,"file",openFileFromSelector);
  930. }
  931. hideContextMenu();
  932. }
  933. function openFileFromSelector(fileData){
  934. result = JSON.parse(fileData);
  935. for (var i=0; i < result.length; i++){
  936. var filename = result[i].filename;
  937. var filepath = result[i].filepath;
  938. //Opening file from Selector. If the current document path is undefined, then open in this windows. Otherwise, open in a new window.
  939. if (currentFilepath == ""){
  940. //Open in this window
  941. window.location.href = window.location.href + "?filepath=" + filepath;
  942. }else{
  943. //Open in new window
  944. var moduleName = $("#data_modulename").text().trim().split("/").pop();
  945. var uid = ao_module_utils.getRandomUID();
  946. var result = ao_module_newfw(moduleName + "/index.php?filepath=" + filepath,"WriterA","file text outline", uid,1050,550,undefined,undefined,true,false);
  947. if (result == false){
  948. window.open("index.php?filepath=" + filepath);
  949. }
  950. }
  951. console.log(filename,filepath);
  952. }
  953. }
  954. function printFile(){
  955. try {
  956. var fileContent = "";
  957. var documentName = "Untitled Document";
  958. if (currentFilepath.trim() != ""){
  959. var documentName = ao_module_codec.decodeUmFilename(basename(currentFilepath));
  960. var ext = getExtension(currentFilepath);
  961. if (ext == "md"){
  962. //Use another function for markdown
  963. parseMarkdownAndPrint(documentName);
  964. return;
  965. }else{
  966. //Just print the content of the simpleMDE
  967. fileContent = simplemde.value()
  968. }
  969. }else{
  970. //Force parse it into markdown
  971. parseMarkdownAndPrint(documentName);
  972. return;
  973. }
  974. var printWindow = window.open("", "", "height=400,width=800");
  975. printWindow.document.write("<html><head><title>" + documentName + "</title>");
  976. printWindow.document.write("</head><xmp>");
  977. printWindow.document.write("Filename: '" + documentName + "' Print-time: " + new Date().toLocaleString() + "\n");
  978. printWindow.document.write(fileContent);
  979. printWindow.document.write("</xmp></html>");
  980. printWindow.document.close();
  981. printWindow.print();
  982. }catch (ex) {
  983. console.error("Error: " + ex.message);
  984. }
  985. }
  986. function parseMarkdownAndPrint(documentName){
  987. var content = simplemde.value();
  988. content = JSON.stringify(content);
  989. $.post( "documentIO.php", { parseMD: content})
  990. .done(function( data ) {
  991. printHTML(documentName,data);
  992. });
  993. }
  994. function printHTML(documentName,fileContent){
  995. var printWindow = window.open("", "", "height=400,width=800");
  996. printWindow.document.write("<html><head><title>" + documentName + "</title>");
  997. printWindow.document.write("</head>");
  998. printWindow.document.write("<small>📎Filename: " + documentName + " Print-time: " + new Date().toLocaleString() + "<br></small>");
  999. printWindow.document.write(fileContent);
  1000. printWindow.document.write("</html>");
  1001. printWindow.document.close();
  1002. printWindow.print();
  1003. }
  1004. function basename(path){
  1005. return path.split("\\").join("/").split("/").pop();
  1006. }
  1007. function getExtension(path){
  1008. if (path.includes("/") || path.includes("\\")){
  1009. path = basename(path);
  1010. }
  1011. return path.split(".").pop(); //Return the last section of the array which split with dot
  1012. }
  1013. function showContextMenu(object){
  1014. if ($("#contextMenu").is(":visible") && currentContextMenuItem == $(object).text().trim()){
  1015. $("#contextMenu").hide();
  1016. return;
  1017. }
  1018. currentContextMenuItem = $(object).text().trim();
  1019. var menu = $("#contextMenu");
  1020. var position = [$(object).offset().left,$(object).offset().top + $(object).height() + 2];
  1021. $("#contextMenu").css("left",position[0]);
  1022. $("#contextMenu").css("top",position[1]);
  1023. $("#contextMenu").html("");
  1024. var items = menuItem[$(object).text().trim()];
  1025. for (var i =0; i < items.length; i++){
  1026. $("#contextMenu").append('<div class="menuItem" onClick="menuClicked(this);">' + items[i] + '</div>')
  1027. }
  1028. $("#contextMenu").show();
  1029. }
  1030. function hideContextMenu(){
  1031. $("#contextMenu").hide();
  1032. }
  1033. //Click on the main edit area.
  1034. $("#main").on("click",function(){
  1035. //If context menu is shown, hide it
  1036. if ($("#contextMenu").is(":visible")){
  1037. $("#contextMenu").hide();
  1038. }
  1039. //focus this floatWindow
  1040. ao_module_focus();
  1041. });
  1042. </script>
  1043. </body>
  1044. </html>