Explorar o código

Work in progress blog module

TC pushbot 5 %!s(int64=4) %!d(string=hai) anos
pai
achega
3bddb85901

+ 1 - 1
mod/agi/agi.file.go

@@ -366,7 +366,7 @@ func (g *Gateway) injectFileLibFunctions(vm *otto.Otto, u *user.User) {
 		}
 
 		if sortMode != "" {
-			if sortMode == "reverse" {
+			if sortMode == "reverse" || sortMode == "descending" {
 				//Sort by reverse name
 				sort.Slice(parsedFilelist, func(i, j int) bool {
 					return strings.ToLower(parsedFilelist[i].Filename) > strings.ToLower(parsedFilelist[j].Filename)

+ 15 - 1
web/Blog/backend/config.js

@@ -7,10 +7,24 @@
 */
 
 //Create the blog table if not exists
+requirelib("filelib");
 newDBTableIfNotExists("blog");
 
 function getBlogPostStore(){
-    return readDBItem("blog", "post-store");
+
+    var blogPostStorage = readDBItem("blog", "post-store");
+
+    if (blogPostStorage == ""){
+        //The blog post storage is not set. Use default
+        blogPostStorage = "user:/Document/Blog/";
+    }
+
+    //Create folder if not exists
+    if (!filelib.fileExists(blogPostStorage)){
+        filelib.mkdir(blogPostStorage)
+    }
+
+    return blogPostStorage;
 }
 
 function setBlogPostStore(newpath){

+ 79 - 8
web/Blog/backend/listPosts.js

@@ -13,26 +13,97 @@ requirelib("filelib");
 includes("config.js");
 
 var blogPostStorage = getBlogPostStore();
-function main(){
-    //Create a directory in user:/Document/ for storing Blog stuffs
-    if (blogPostStorage == ""){
-        //The blog post storage is not set. Use default
-        blogPostStorage = "user:/Document/Blog/Posts/";
+
+function extractPostInfo(filepath){
+    var content = filelib.readFile(filepath);
+    var postObject = JSON.parse(content);
+
+    var title = "Untitled";
+    var content = "(This post has no content)";
+    var tags = [];
+
+    if (typeof postObject.Title != "undefined"){
+        title = postObject.Title
     }
 
+    if (typeof postObject.Content != "undefined"){
+        content = postObject.Content;
+        if (content.split(" ").length > 100){
+            //Trim the content off if it is longer than 100 words
+            var words = content.split(" ");
+            var trimmedContent = "";
+            for (var i = 0; i < 100; i++){
+                trimmedContent = trimmedContent + words[i] + " ";
+            }
+
+            trimmedContent = trimmedContent + "..."
+            content = trimmedContent;
+        }else if (content.length > 300){
+            //To handle lang with no space, like Cantonese
+            var trimmedContent = content.substr(0, 300) + "..."
+            content = trimmedContent;
+        }
+    }
+
+    if (typeof postObject.Tags != "undefined"){
+        tags = postObject.Tags
+    }
+
+    return [title, content, tags];
+}
+
+function main(){
     //Filter out the last / if exists
     if (blogPostStorage.substr(blogPostStorage.length - 1, 1) == "/"){
         blogPostStorage = blogPostStorage.substr(0, blogPostStorage.length - 1);
     }
 
     //If it doesn't exists
-    filelib.mkdir(blogPostStorage);	
+    filelib.mkdir(blogPostStorage + "/public");
+    filelib.mkdir(blogPostStorage + "/private");
 
     //List all the created post
-    var allPosts = filelib.aglob(blogPostStorage + "/*.json");
+    var publicPosts = filelib.aglob(blogPostStorage + "/public/*.json","reverse");
+    var privatePosts = filelib.aglob(blogPostStorage + "/private/*.json","reverse");
+
+
+
+    var posts = [];
+    for(var i = 0; i < publicPosts.length; i++){
+        //Extract post information
+        var postInfo = extractPostInfo( publicPosts[i]);
+
+        //Get the poost modification time
+        var modTime = filelib.mtime(publicPosts[i], false);
+
+        posts.push({
+            "title": postInfo[0],
+            "content":postInfo[1],
+            "tags": postInfo[2],
+            "modtime": modTime,
+            "view": "public",
+            "filepath": publicPosts[i]
+        });
+    }
+
+    for(var i = 0; i < privatePosts.length; i++){
+        var postInfo = extractPostInfo( privatePosts[i]);
+
+        //Get the poost modification time
+        var modTime = filelib.mtime(privatePosts[i], false);
+
+        posts.push({
+            "title": postInfo[0],
+            "content":postInfo[1],
+            "tags": postInfo[2],
+            "modtime": modTime,
+            "view": "private",
+            "filepath": privatePosts[i]
+        });
+    }
 
     //Return the value of the posts
-    sendJSONResp(JSON.stringify(allPosts));
+    sendJSONResp(JSON.stringify(posts));
 }
 
 main();

+ 0 - 0
web/Blog/backend/loadFIle.js


+ 51 - 0
web/Blog/backend/savePost.js

@@ -0,0 +1,51 @@
+/*
+    SavePost.js
+
+    This script save the post to the given location
+    Require paramters: 
+    - filepath
+    - title
+    - tags
+    - content (HTML, not markdown)
+*/
+
+//Require libraries
+includes("config.js")
+
+function main(){
+    //Get a safe title from removing all special characters
+    var safeTitle = title.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '');
+    var saveFilename =  getBlogPostStore() + "private/" +  Date.now() + "-" + safeTitle + ".json"
+    if (filepath == "" || !filelib.fileExists(filepath)){
+        //This is a new file or the original file not exists. Assign a filename for it
+       filepath = saveFilename;
+    }
+
+    //Generate tags array
+    var tagArray = [];
+    tags = tags.split(",");
+    for(var i = 0; i < tags.length; i++){
+        var thisTag = tags[i].trim();
+        if (thisTag != ""){
+            tagArray.push(thisTag);
+        }
+
+    }
+
+    //Generate the blog post storage object
+    var blogObject = {
+        Title: title,
+        Tags: tagArray,
+        Content: content,
+    }
+
+    //Write to file
+    var postObjectEncoded = JSON.stringify(blogObject);
+    filelib.writeFile(filepath, postObjectEncoded);
+
+    //Send back the filepath for save confirmation
+    sendResp(filepath);
+}
+
+main();
+

+ 217 - 0
web/Blog/editor.html

@@ -0,0 +1,217 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="UTF-8">
+		<meta name="apple-mobile-web-app-capable" content="yes" />
+		<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
+		<meta name="theme-color" content="#ff9224">
+		<script src="../script/jquery.min.js"></script>
+		<script src="../script/ao_module.js"></script>
+		<link rel="stylesheet" href="../script/tocas/tocas.css">
+		<script src="../script/tocas/tocas.js"></script>
+
+
+		<!-- Special handler to remove tocas css style-->
+		<script>
+			function clearTocasStyle(){
+				console.log("Cleaning Styles")
+				$("body").find("li, ul").each(function(){
+					$(this).addClass("unstyled");
+				});
+			}
+			window.clearTocasStyle = clearTocasStyle();
+			
+		</script>
+		<!-- Editor -->
+		<link href="script/suneditor/suneditor.min.css" rel="stylesheet">
+	
+		<script src="script/suneditor/suneditor.min.js"></script>
+
+		<script src="script/suneditor/en.js"></script>
+
+		<link rel="manifest" crossorigin="use-credentials" href="manifest.json">
+		<title>Blog</title>
+		<style>
+			body {
+				background-color: white;
+			}
+
+			.blog.main{
+				background-color: #ff9224;
+				border: 1px solid #ff9224;
+				color: white;
+			}
+
+			.blog.main:hover{
+				background-color: #c7721c !important;
+				border: 1px solid #c7721c !important;
+				color: white !important;
+			}
+
+			.blog.green{
+				background-color: #47d191;
+				border: 1px solid #47d191;
+				color: white;
+			}
+
+			.blog.green:hover{
+				background-color: #38a170 !important;
+				border: 1px solid #38a170 !important;
+				color: white !important;
+			}
+
+			.blog.red{
+				background-color: #c4413d;
+				border: 1px solid #c4413d;
+				color: white;
+			}
+
+			.blog.red:hover{
+				background-color: #8c302d !important;
+				border: 1px solid #8c302d !important;
+				color: white !important;
+			}
+
+            .topPad{
+                margin-top: 4px;
+            }
+
+			#editorFrame{
+				width: 100%;
+				border: 0px solid transparent;
+				height: 400px;
+			}
+		</style>
+	</head>
+	<body>
+		<br><br>
+		<div class="ts container">
+			<div>
+				<h4><img class="ts mini spaced image" src="img/module_icon.png" style="margin-right: 12px;">  Blog Writer</h4>
+			</div>
+			<div class="ts divider"></div>
+			<div class="ts stackable grid">
+				<div class="twelve wide column">
+					<iframe id="editorFrame" src="framedEditor.html"></iframe>
+				</div>
+				<div class="four wide column">
+					<form class="ts form">
+						<div class="field">
+							<h5>Post Title & Metatags</h5>
+						</div>
+						<div class="field">
+							<label>Post Title</label>
+							<input id="title" type="text">
+						</div>
+						<div class="field">
+							<label>Tags (Seperate by ",")</label>
+							<input id="tags" type="text">
+						</div>
+						<div class="field">
+							
+						</div>
+					</form>
+					<h5>Post Information</h5>
+					<p><i class="hide icon"></i><span id="viewstate">Private Post</span></p>
+					<p><span id="savepath">Not Saved</span></p>
+					<h5>Actions</h5>
+					<button class="ts fluid small button blog main" onclick="savePost(this);"><i class="save icon"></i> Save</button>
+					<button class="ts fluid small button blog green topPad"><i class="upload icon"></i> Save & Publish</button>
+					<br><br>
+					<button class="ts fluid small button blog red topPad" onclick="discardAndExit();"><i class="remove icon"></i> Discard Changes</button>
+				</div>
+			</div>
+		<br><br><br>
+		
+			
+		</div>
+		<script>
+			var editingFile = "";
+			var inputFiles = ao_module_loadInputFiles();
+			var editorWindow = $("#editorFrame")[0].contentWindow;
+			
+			if (inputFiles != null && inputFiles.length > 0){
+				//Edit  mode
+				inputFiles = inputFiles[0];
+			}else{
+				//New post mode
+				
+			}
+
+			function newEditor(){
+				editorWindow.newEditor();
+			}
+
+			function handleWindowResize(){
+				$(editorWindow.document).find("#suneditor_maineditor").css("width", "100%");
+				var newHeight = window.innerHeight - 300;
+				if (newHeight < 500){
+					newHeight = 500;
+				}
+				$(editorWindow.document).find("#suneditor_maineditor").css("height", newHeight + "px");
+				$(editorWindow.document).find(".se-wrapper").css("height", newHeight - 138 + "px");
+				$(editorWindow.document).find(".se-wrapper-inner").css("height", newHeight - 138 + "px");
+
+				$("#editorFrame").css("height", (newHeight + 100) + "px");
+
+
+			}
+
+			
+			$(window).on("resize", function(){
+				handleWindowResize();
+			});
+
+			function clearTocasStyle(){
+				console.log("Cleaning Styles")
+				$("body").find("li, ul").each(function(){
+					$(this).addClass("unstyled");
+				});
+			}
+
+
+			function savePost(btn){
+				$(btn).addClass("loading");
+				var title = $("#title").val();
+				var tags = $("#tags").val();
+
+				if (title.trim() == ""){
+					title = "Untitled Post"
+				}
+
+				//Get the content of the post
+				var postContent = editorWindow.getContent();
+				console.log(postContent);
+				if (postContent == ""){
+					$(btn).removeClass("loading");
+					alert("Nothing to post :(")
+					return;
+				}
+				
+				//Create post
+				ao_module_agirun("Blog/backend/savePost.js", {
+					filepath: editingFile,
+					title: title,
+					tags: tags,
+					content: postContent
+				}, function(data){
+					if (data.error !== undefined){
+						alert(data.error);
+					}else{
+						//Return the filepath of the post storage
+						editingFile = data;
+						$("#savepath").text(data);
+					}
+
+					$(btn).removeClass("loading");
+				});
+			}
+
+			function discardAndExit(){
+				if (confirm("Confirm Exit?")){
+					window.location.href = "index.html"
+				}
+			}
+		</script>
+	</body>
+</html>

+ 94 - 0
web/Blog/framedEditor.html

@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="UTF-8">
+		<meta name="apple-mobile-web-app-capable" content="yes" />
+		<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
+		<meta name="theme-color" content="#ff9224">
+		<script src="../script/jquery.min.js"></script>
+		
+		<!-- Editor -->
+		<link href="script/suneditor/suneditor.min.css" rel="stylesheet">
+	
+		<script src="script/suneditor/suneditor.min.js"></script>
+
+		<script src="script/suneditor/en.js"></script>
+
+		<style>
+			body{
+				margin: 0px;
+			}
+			#editorWrapper{
+				position: fixed;
+				left: 0px;
+				top: 0px;
+				width: 100%;
+				height: 100%;
+			}
+
+			#maineditor{
+				width: calc(100% - 10px);
+				height: 300px;
+			}
+		</style>
+	</head>
+	<body>
+		<div id="editorWrapper">
+			<textarea id="maineditor"></textarea>
+		</div>
+		
+		<script>
+			//Start a new editor if there is no file ported in
+			var editor;
+			if (parent.inputFiles == null){
+				//New Editor
+				initEditor();
+				parent.handleWindowResize();
+			}else{
+				//Get the filepath and load it
+				var file = parent.inputFiles;
+				//Load the given file
+				$.getJSON("../media?file=" + file.filepath, function(data){
+					$("#maineditor").html(data.Content);
+					$(parent.document).find("#title").val(data.Title);
+					$(parent.document).find("#tags").val(data.Tags.join(", "));
+					$(parent.document).find("#savepath").text(file.filepath);
+					parent.editingFile = file.filepath;
+					
+					initEditor();
+					parent.handleWindowResize();
+				});
+			}
+			
+			
+			function getContent(){
+				return editor.getContents();
+			}
+
+			function initEditor(){
+				editor = SUNEDITOR.create(document.getElementById('maineditor'),
+					{	
+						lang: SUNEDITOR_LANG['en'],
+						buttonList: [
+							['undo', 'redo'],
+							['font', 'fontSize', 'formatBlock'],
+							['paragraphStyle', 'blockquote'],
+							['bold', 'underline', 'italic', 'strike', 'subscript', 'superscript'],
+							['fontColor', 'hiliteColor'],
+							['removeFormat'],
+							'/', // Line break
+							['outdent', 'indent'],
+							['align', 'horizontalRule', 'list', 'lineHeight'],
+							['table', 'link', 'image', 'video', 'audio' /** ,'math' */], // You must add the 'katex' library at options to use the 'math' plugin.
+							/** ['imageGallery'] */ // You must add the "imageGalleryUrl".
+							['showBlocks', 'codeView']
+						],
+						"resizingBar": false,
+					}
+				);
+				return editor;
+			}
+
+		</script>
+	</body>
+</html>

+ 142 - 17
web/Blog/index.html

@@ -16,11 +16,66 @@
 			body{
 				background-color: white;
 			}
+
 			.postControls{
 				position: absolute;
 				top: 0.5em;
 				right: 0.5em;
 			}
+
+			.blog.main{
+				background-color: #ff9224;
+				border: 1px solid #ff9224;
+				color: white;
+			}
+
+			.blog.main:hover{
+				background-color: #c7721c !important;
+				border: 1px solid #c7721c !important;
+				color: white !important;
+			}
+
+			.blog.green{
+				background-color: #47d191;
+				border: 1px solid #47d191;
+				color: white;
+			}
+
+			.blog.green:hover{
+				background-color: #38a170 !important;
+				border: 1px solid #38a170 !important;
+				color: white !important;
+			}
+
+			.blog.red{
+				background-color: #ff3d3d;
+				border: 1px solid #ff3d3d;
+				color: white;
+			}
+
+			.blog.red:hover{
+				background-color: #962d2d !important;
+				border: 1px solid #962d2d !important;
+				color: white !important;
+			}
+
+			.blog.red{
+				background-color: #c4413d;
+				border: 1px solid #c4413d;
+				color: white;
+			}
+
+			.blog.red:hover{
+				background-color: #8c302d !important;
+				border: 1px solid #8c302d !important;
+				color: white !important;
+			}
+
+
+			
+            .topPad{
+                margin-top: 4px;
+            }
 		</style>
 	</head>
 	<body>
@@ -32,43 +87,113 @@
 			<div class="ts divider"></div>
 			<div class="ts stackable grid">
 				<!-- Post container-->
-				<div class="twelve wide column">
-					<div class="ts segment post">
-						<div class="ts header">
-							Awesome Blog Title
-							<div class="sub header"><i class="tag icon"></i> tag1, tag2, tag3, tag4</div>
-						</div>
-						<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus eu libero quis orci ornare scelerisque at vitae tortor. Duis at mauris purus. Donec pharetra orci hendrerit dui auctor facilisis. Praesent et nunc elementum, posuere diam in, varius velit. Integer ac ante blandit lacus ultrices elementum. Fusce sed viverra arcu. Cras laoreet neque turpis, in porta mi ornare vel. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse vitae aliquet libero. Donec risus enim, iaculis quis ornare vel, ornare a dolor. Maecenas a iaculis diam. Integer eget tellus scelerisque, finibus felis nec, mollis sem. Donec euismod turpis elit, quis volutpat nisl imperdiet at. </p>
-						<p><a>Show More</a></p>
-		
-						<div class="postControls">
-							<button class="ts mini icon button"><i class="edit icon"></i></button>
-						</div>
-					</div>
+				<div class="twelve wide column" id="postList">
+					
 				</div>
 				<!-- Setting container-->
 				<div class="four wide column">
 					<div class="ts segment">
-						
+						<a class="ts fluid small button blog main" href="editor.html"><i class="add icon"></i> New Post</a>
+						<button class="ts fluid small button blog green topPad"><i class="text file outline icon"></i> Browse Blog</button>
+						<div class="ts divider"></div>
+						<button class="ts fluid small button"><i class="folder open icon"></i> Open Folder</button>
+						<button class="ts fluid small button topPad"><i class="setting icon"></i> Settings</button>
 					</div>
 				</div>
 			</div>
-			
+			<br><br><br>
 		</div>
 		<script>
 			//List all the post stored in the system
 			listPost();
 
+			function openPost(object){
+				//Parse the file object following ArozOS file descriptor protocol
+				var filepath = $(object).attr("filepath");
+				var filename = filepath.split("/").pop();
+				var file = [{
+					filename: filename,
+					filepath: filepath
+				}]
+				var fd = encodeURIComponent(JSON.stringify(file));
+				window.location.href = "editor.html#" + fd;
+			}
+
 			function listPost(){
 				ao_module_agirun("Blog/backend/listPosts.js", {}, function(data){
 					if (data.length == 0){
 						//There is no post in the system
-
+						$("#postList").html(`<div class="ts segment post">
+							<h5 class="ts header">
+								<div class="content">
+									No Blog Posts
+									<div class="sub header">Create your first post with the "New Post"</div>
+								</div>
+							</h5>
+							
+						</div>`);
 					}else{
 						//Render the post
+						console.log(data);
+						data.forEach(post => {
+							//Generate the tag HTML 
+							var tags = post.tags;
+							var tagHTML = [];
+							tags.forEach(tag => {
+								tagHTML.push(`<a class="section">${tag}</a>`);
+							});
+
+							tagHTML = tagHTML.join(`<div class="divider">,</div>`);
+
+							//Post view permission indicator
+							var postViewIcon = "";
+
+							//Publish or hide post button
+							var publicPostOnly = "";
+							var privatePostOnly = "";
 
+
+							if (post.view == "private"){
+								postViewIcon = `<i class="hide icon"></i> Draft`;
+								publicPostOnly = "display:none;"
+							}else{
+								postViewIcon = `<i class="unhide icon"></i> Published`;
+								privatePostOnly = "display:none;"
+							}
+
+							$("#postList").append(`<div class="ts segment post">
+							<div class="ts header">
+								<a href="#" onclick="openPost(this);" filepath="${post.filepath}">${post.title} </a>
+								<div class="sub header">
+									<div>
+									<span style="padding-right: 1em;">${postViewIcon}</span>
+									<span  style="padding-right: 1em;"><i class="calendar icon"></i> ${post.modtime}</span>
+									<div class="ts breadcrumb">
+										<div class="section">
+											<i class="tag icon"></i>
+										</div>
+										${tagHTML}
+									</div>
+									</div>
+								</div>
+							</div>
+							${stripHtml(post.content)}
+			
+							<div class="postControls">
+								<button style="${publicPostOnly}" class="ts mini icon button blog red" title="Hide Post"><i class="hide icon"></i></button>
+								<button style="${privatePostOnly}" class="ts mini icon button blog green" title="Publish Post"><i class="upload icon"></i></button>
+							</div>
+						</div>`)
+						})
 					}
-				})
+				});
+
+				function stripHtml(html){
+					let tmp = document.createElement("DIV");
+					tmp.innerHTML = html;
+					var result = tmp.textContent || tmp.innerText || "";
+					return result;
+				}
 			}
 		</script>
 	</body>

+ 186 - 0
web/Blog/script/suneditor/en.js

@@ -0,0 +1,186 @@
+/*
+ * wysiwyg web editor
+ *
+ * suneditor.js
+ * Copyright 2017 JiHong Lee.
+ * MIT license.
+ */
+'use strict';
+
+(function (global, factory) {
+    if (typeof module === 'object' && typeof module.exports === 'object') {
+        module.exports = global.document ?
+            factory(global, true) :
+            function (w) {
+                if (!w.document) {
+                    throw new Error('SUNEDITOR_LANG a window with a document');
+                }
+                return factory(w);
+            };
+    } else {
+        factory(global);
+    }
+}(typeof window !== 'undefined' ? window : this, function (window, noGlobal) {
+    const lang = {
+        code: 'en',
+        toolbar: {
+            default: 'Default',
+            save: 'Save',
+            font: 'Font',
+            formats: 'Formats',
+            fontSize: 'Size',
+            bold: 'Bold',
+            underline: 'Underline',
+            italic: 'Italic',
+            strike: 'Strike',
+            subscript: 'Subscript',
+            superscript: 'Superscript',
+            removeFormat: 'Remove Format',
+            fontColor: 'Font Color',
+            hiliteColor: 'Highlight Color',
+            indent: 'Indent',
+            outdent: 'Outdent',
+            align: 'Align',
+            alignLeft: 'Align left',
+            alignRight: 'Align right',
+            alignCenter: 'Align center',
+            alignJustify: 'Align justify',
+            list: 'List',
+            orderList: 'Ordered list',
+            unorderList: 'Unordered list',
+            horizontalRule: 'Horizontal line',
+            hr_solid: 'Solid',
+            hr_dotted: 'Dotted',
+            hr_dashed: 'Dashed',
+            table: 'Table',
+            link: 'Link',
+            math: 'Math',
+            image: 'Image',
+            video: 'Video',
+            audio: 'Audio',
+            fullScreen: 'Full screen',
+            showBlocks: 'Show blocks',
+            codeView: 'Code view',
+            undo: 'Undo',
+            redo: 'Redo',
+            preview: 'Preview',
+            print: 'print',
+            tag_p: 'Paragraph',
+            tag_div: 'Normal (DIV)',
+            tag_h: 'Header',
+            tag_blockquote: 'Quote',
+            tag_pre: 'Code',
+            template: 'Template',
+            lineHeight: 'Line height',
+            paragraphStyle: 'Paragraph style',
+            textStyle: 'Text style',
+            imageGallery: 'Image gallery',
+            mention: 'Mention'
+        },
+        dialogBox: {
+            linkBox: {
+                title: 'Insert Link',
+                url: 'URL to link',
+                text: 'Text to display',
+                newWindowCheck: 'Open in new window',
+                downloadLinkCheck: 'Download link',
+                bookmark: 'Bookmark'
+            },
+            mathBox: {
+                title: 'Math',
+                inputLabel: 'Mathematical Notation',
+                fontSizeLabel: 'Font Size',
+                previewLabel: 'Preview'
+            },
+            imageBox: {
+                title: 'Insert image',
+                file: 'Select from files',
+                url: 'Image URL',
+                altText: 'Alternative text'
+            },
+            videoBox: {
+                title: 'Insert Video',
+                file: 'Select from files',
+                url: 'Media embed URL, YouTube/Vimeo'
+            },
+            audioBox: {
+                title: 'Insert Audio',
+                file: 'Select from files',
+                url: 'Audio URL'
+            },
+            browser: {
+                tags: 'Tags',
+                search: 'Search',
+            },
+            caption: 'Insert description',
+            close: 'Close',
+            submitButton: 'Submit',
+            revertButton: 'Revert',
+            proportion: 'Constrain proportions',
+            basic: 'Basic',
+            left: 'Left',
+            right: 'Right',
+            center: 'Center',
+            width: 'Width',
+            height: 'Height',
+            size: 'Size',
+            ratio: 'Ratio'
+        },
+        controller: {
+            edit: 'Edit',
+            unlink: 'Unlink',
+            remove: 'Remove',
+            insertRowAbove: 'Insert row above',
+            insertRowBelow: 'Insert row below',
+            deleteRow: 'Delete row',
+            insertColumnBefore: 'Insert column before',
+            insertColumnAfter: 'Insert column after',
+            deleteColumn: 'Delete column',
+            fixedColumnWidth: 'Fixed column width',
+            resize100: 'Resize 100%',
+            resize75: 'Resize 75%',
+            resize50: 'Resize 50%',
+            resize25: 'Resize 25%',
+            autoSize: 'Auto size',
+            mirrorHorizontal: 'Mirror, Horizontal',
+            mirrorVertical: 'Mirror, Vertical',
+            rotateLeft: 'Rotate left',
+            rotateRight: 'Rotate right',
+            maxSize: 'Max size',
+            minSize: 'Min size',
+            tableHeader: 'Table header',
+            mergeCells: 'Merge cells',
+            splitCells: 'Split Cells',
+            HorizontalSplit: 'Horizontal split',
+            VerticalSplit: 'Vertical split'
+        },
+        menu: {
+            spaced: 'Spaced',
+            bordered: 'Bordered',
+            neon: 'Neon',
+            translucent: 'Translucent',
+            shadow: 'Shadow',
+            code: 'Code'
+        }
+    };
+
+    if (typeof noGlobal === typeof undefined) {
+        if (!window.SUNEDITOR_LANG) {
+            Object.defineProperty(window, 'SUNEDITOR_LANG', {
+                enumerable: true,
+                writable: false,
+                configurable: false,
+                value: {}
+            });
+        }
+
+        Object.defineProperty(window.SUNEDITOR_LANG, 'en', {
+            enumerable: true,
+            writable: true,
+            configurable: true,
+            value: lang
+        });
+    }
+
+    return lang;
+}));

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web/Blog/script/suneditor/suneditor.min.css


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
web/Blog/script/suneditor/suneditor.min.js


+ 11 - 0
web/Notebook/notebook.html

@@ -63,8 +63,19 @@
                 }
                
                 filepath = files[0].filepath;
+
+                //Check if this is json. If yes, parse it to string before displaying to prevent the [object Object] bug
+                var ext = filepath.split(".").pop();
+                var isJson = false;
+                if (ext == "json"){
+                    isJson = true;
+                }
+
                 //Load the file into the textarea
                 $.get("../../media?file=" + files[0].filepath,function(data){
+                    if (isJson){
+                        data = JSON.stringify(data);
+                    }
                     $("#maintext").text(data);
                     lastSaveContent = data;
                     //Load Markdown Editor

+ 21 - 0
web/SystemAO/info/license/DPlayer.txt

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) DIYgod <[email protected]> (https://www.anotherhome.net/)
+
+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.

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio